Incorrect Comparison Logic Granularity

Description

Incorrect Comparison Logic Granularity occurs when a product performs comparison operations across multiple steps rather than as a single unified operation. This weakness applies to security-critical comparisons involving passwords, Message Authentication Codes (MACs), and challenge-response verifications. Byte-by-byte or step-by-step comparison implementations that terminate early create exploitable timing side-channels. When comparison fails at an intermediate step, attackers can exploit timing differences to identify exactly where the failure occurred, potentially compromising authentication or verification mechanisms.

Risk

Incorrect comparison granularity has severe security implications. Timing side-channels reveal partial secret information. Password comparisons leak character positions. MAC verifications become vulnerable to incremental attacks. Challenge-response systems can be defeated byte-by-byte. Cryptographic signatures may be forged through timing analysis. Authentication systems become vulnerable to brute-force optimization. Multiple attempts allow complete secret recovery. Security tokens can be determined without full key knowledge.

Solution

Ensure comparison logic is implemented to compare in one operation instead of smaller chunks. Use constant-time comparison functions. Compare all bytes regardless of intermediate failures. Avoid early termination on mismatch. Use bitwise OR to accumulate differences. Implement fixed-time algorithms for all security comparisons. Add timing noise only as supplementary protection. Use hardware-assisted constant-time operations where available.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Bypass Protection Mechanism - Attackers can use timing analysis to incrementally deduce correct values.
AuthorizationScope: Authorization

Bypass Protection Mechanism - Authentication controls can be circumvented through timing measurements.

Example Code

Vulnerable Code

// Vulnerable: Early termination password comparison

#include <stdint.h>
#include <string.h>

bool vulnerable_password_check(const char* input, const char* stored, size_t len) {
    // VULNERABLE: Returns immediately on first mismatch
    // Timing reveals position of first incorrect character
    for (size_t i = 0; i < len; i++) {
        if (input[i] != stored[i]) {
            return false;  // <-- Early termination leaks timing
        }
    }
    return true;
}

// VULNERABLE: strcmp also terminates early
bool vulnerable_strcmp_check(const char* input, const char* password) {
    return strcmp(input, password) == 0;  // Early termination
}

// VULNERABLE: Byte-by-byte MAC verification
bool vulnerable_mac_verify(const uint8_t* computed, const uint8_t* received, size_t len) {
    for (size_t i = 0; i < len; i++) {
        if (computed[i] != received[i]) {
            return false;  // VULNERABLE: Timing reveals MAC byte positions
        }
    }
    return true;
}

// Timing attack example
// Attacker measures: password "AAAA" - 100ns (fails at position 0)
// Attacker measures: password "PAAA" - 150ns (fails at position 1)
// First character is 'P'
// Continue for each position to recover full password
// Vulnerable: Sequential comparison in hardware

module vulnerable_token_compare (
    input wire clk,
    input wire reset_n,
    input wire start,
    input wire [63:0] input_token,
    input wire [63:0] stored_token,
    output reg match,
    output reg done
);

    reg [2:0] byte_index;
    reg [2:0] state;

    parameter IDLE = 3'h0;
    parameter COMPARE = 3'h1;
    parameter DONE_MATCH = 3'h2;
    parameter DONE_FAIL = 3'h3;

    // VULNERABLE: Byte-by-byte comparison with early termination
    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            state <= IDLE;
            byte_index <= 3'h0;
            match <= 1'b0;
            done <= 1'b0;
        end
        else begin
            case (state)
                IDLE: begin
                    if (start) begin
                        state <= COMPARE;
                        byte_index <= 3'h0;
                        done <= 1'b0;
                    end
                end

                COMPARE: begin
                    // VULNERABLE: Check one byte at a time
                    if (input_token[byte_index*8 +: 8] !=
                        stored_token[byte_index*8 +: 8]) begin
                        // Early termination - timing reveals byte position
                        state <= DONE_FAIL;
                    end
                    else if (byte_index == 3'h7) begin
                        state <= DONE_MATCH;
                    end
                    else begin
                        byte_index <= byte_index + 1;
                    end
                end

                DONE_MATCH: begin
                    match <= 1'b1;
                    done <= 1'b1;
                    state <= IDLE;
                end

                DONE_FAIL: begin
                    match <= 1'b0;
                    done <= 1'b1;
                    // Timing: done signal arrives at different times
                    // based on which byte failed
                    state <= IDLE;
                end
            endcase
        end
    end

endmodule
# Vulnerable: Python early-termination comparison

def vulnerable_hmac_verify(expected: bytes, received: bytes) -> bool:
    """VULNERABLE: Early termination reveals HMAC bytes"""
    if len(expected) != len(received):
        return False

    for i in range(len(expected)):
        if expected[i] != received[i]:
            return False  # VULNERABLE: Leaks position
    return True

def vulnerable_api_key_check(provided: str, valid: str) -> bool:
    """VULNERABLE: String comparison with timing leak"""
    return provided == valid  # Python == may short-circuit

# Attack demonstration
import time

def timing_attack(target_function, known_prefix, charset, length):
    """Exploit timing to recover secret byte-by-byte"""
    secret = known_prefix

    for position in range(len(known_prefix), length):
        best_time = 0
        best_char = None

        for char in charset:
            test = secret + char + 'X' * (length - position - 1)

            start = time.perf_counter_ns()
            target_function(test)
            elapsed = time.perf_counter_ns() - start

            if elapsed > best_time:
                best_time = elapsed
                best_char = char

        secret += best_char
        print(f"Position {position}: '{best_char}' (recovered: {secret})")

    return secret

Fixed Code

// Fixed: Constant-time comparison

#include <stdint.h>
#include <string.h>

bool secure_password_check(const char* input, const char* stored, size_t len) {
    // FIXED: Constant-time comparison
    volatile uint8_t result = 0;

    // Always compare all bytes
    for (size_t i = 0; i < len; i++) {
        // XOR accumulates differences without branching
        result |= input[i] ^ stored[i];
    }

    // Single comparison at the end
    return result == 0;
}

// FIXED: Constant-time memory comparison
int secure_memcmp(const void* a, const void* b, size_t len) {
    const volatile uint8_t* pa = (const volatile uint8_t*)a;
    const volatile uint8_t* pb = (const volatile uint8_t*)b;
    volatile uint8_t diff = 0;

    // FIXED: Compare all bytes, no early termination
    for (size_t i = 0; i < len; i++) {
        diff |= pa[i] ^ pb[i];
    }

    // Return 0 if equal, non-zero otherwise
    return diff;
}

// FIXED: Constant-time MAC verification
bool secure_mac_verify(const uint8_t* computed, const uint8_t* received, size_t len) {
    volatile uint8_t result = 0;

    // FIXED: Check all bytes regardless of intermediate results
    for (size_t i = 0; i < len; i++) {
        result |= computed[i] ^ received[i];
    }

    return result == 0;
}

// FIXED: Double HMAC comparison (prevents length extension too)
bool secure_hmac_verify(const uint8_t* key, size_t key_len,
                        const uint8_t* message, size_t msg_len,
                        const uint8_t* received_mac) {
    uint8_t computed_mac[32];
    uint8_t double_computed[32];
    uint8_t double_received[32];

    // Compute expected MAC
    hmac_sha256(key, key_len, message, msg_len, computed_mac);

    // FIXED: Double HMAC for constant-time comparison
    hmac_sha256(key, key_len, computed_mac, 32, double_computed);
    hmac_sha256(key, key_len, received_mac, 32, double_received);

    return secure_memcmp(double_computed, double_received, 32) == 0;
}
// Fixed: Single-cycle parallel comparison

module secure_token_compare (
    input wire clk,
    input wire reset_n,
    input wire start,
    input wire [63:0] input_token,
    input wire [63:0] stored_token,
    output reg match,
    output reg done
);

    // FIXED: Compare all bytes in parallel in single cycle
    wire [63:0] xor_result;
    wire all_match;

    // Parallel XOR of all bits
    assign xor_result = input_token ^ stored_token;

    // Single comparison: all bits must be zero
    assign all_match = (xor_result == 64'h0);

    // FIXED: Fixed timing regardless of match/mismatch
    reg [3:0] delay_counter;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            match <= 1'b0;
            done <= 1'b0;
            delay_counter <= 4'h0;
        end
        else if (start) begin
            // Start comparison with fixed delay
            delay_counter <= 4'hF;  // Fixed delay cycles
            done <= 1'b0;
        end
        else if (delay_counter > 0) begin
            delay_counter <= delay_counter - 1;

            if (delay_counter == 1) begin
                // FIXED: Output result after fixed delay
                match <= all_match;
                done <= 1'b1;
            end
        end
        else begin
            done <= 1'b0;
        end
    end

endmodule

// Fixed: Constant-time comparison with additional protection
module secure_token_compare_enhanced (
    input wire clk,
    input wire reset_n,
    input wire start,
    input wire [127:0] input_token,
    input wire [127:0] stored_token,
    output reg match,
    output reg done
);

    // FIXED: Multiple parallel comparisons for redundancy
    wire [127:0] xor_result;
    wire [127:0] and_result;
    wire [127:0] or_check;

    assign xor_result = input_token ^ stored_token;
    assign and_result = input_token & stored_token;
    assign or_check = input_token | stored_token;

    // FIXED: Combine multiple checks
    wire primary_match = (xor_result == 128'h0);

    // Secondary verification using different logic
    wire secondary_match = ((and_result ^ or_check) == (input_token ^ stored_token));

    // Both must agree
    wire final_match = primary_match && secondary_match;

    // Fixed delay state machine
    reg [4:0] timer;

    always @(posedge clk or negedge reset_n) begin
        if (!reset_n) begin
            match <= 1'b0;
            done <= 1'b0;
            timer <= 5'h0;
        end
        else if (start) begin
            timer <= 5'h1F;  // Fixed 31-cycle delay
            done <= 1'b0;
        end
        else if (timer > 0) begin
            timer <= timer - 1;
            if (timer == 1) begin
                match <= final_match;
                done <= 1'b1;
            end
        end
    end

endmodule
# Fixed: Constant-time comparison in Python

import hmac
import secrets

def secure_compare(a: bytes, b: bytes) -> bool:
    """FIXED: Constant-time comparison using hmac.compare_digest"""
    # Length check with constant-time padding
    if len(a) != len(b):
        # Pad shorter to prevent length-based timing
        max_len = max(len(a), len(b))
        a = a.ljust(max_len, b'\x00')
        b = b.ljust(max_len, b'\x00')

    # FIXED: Use library constant-time comparison
    return hmac.compare_digest(a, b)

def secure_api_key_check(provided: str, valid: str) -> bool:
    """FIXED: Constant-time string comparison"""
    # Convert to bytes for constant-time comparison
    return hmac.compare_digest(
        provided.encode('utf-8'),
        valid.encode('utf-8')
    )

def secure_hmac_verify(key: bytes, message: bytes, received_mac: bytes) -> bool:
    """FIXED: Constant-time HMAC verification"""
    computed_mac = hmac.new(key, message, 'sha256').digest()

    # FIXED: Use constant-time comparison
    return hmac.compare_digest(computed_mac, received_mac)

# Manual constant-time implementation (educational)
def constant_time_compare_manual(a: bytes, b: bytes) -> bool:
    """Manual constant-time comparison"""
    if len(a) != len(b):
        return False

    result = 0
    for x, y in zip(a, b):
        # XOR accumulates differences
        result |= x ^ y

    # Single final comparison
    return result == 0

CVE Examples

  • CVE-2019-10482: Non-constant-time smartphone OS comparisons allowed timing attacks
  • CVE-2019-10071: Java framework using String.equals() for HMAC validation
  • CVE-2014-0984: Router password function terminating on first mismatch

  • CWE-208: Observable Timing Discrepancy (parent)
  • CWE-697: Incorrect Comparison (parent)
  • CWE-1261: Improper Handling of Single Event Upsets (peer)
  • CWE-1255: Comparison Logic is Vulnerable to Power Side-Channel Attacks (related)

References

  1. MITRE Corporation. "CWE-1254: Incorrect Comparison Logic Granularity." https://cwe.mitre.org/data/definitions/1254.html
  2. Brumley & Boneh. "Remote Timing Attacks are Practical"
  3. Percival, Colin. "Cache Missing for Fun and Profit"