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
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Bypass Protection Mechanism - Attackers can use timing analysis to incrementally deduce correct values. |
| Authorization | Scope: 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
Related CWEs
- 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
- MITRE Corporation. "CWE-1254: Incorrect Comparison Logic Granularity." https://cwe.mitre.org/data/definitions/1254.html
- Brumley & Boneh. "Remote Timing Attacks are Practical"
- Percival, Colin. "Cache Missing for Fun and Profit"