Skip to content

Commit 5651758

Browse files
committed
misc: reject a negative length in ConstantCompare()
Returning 0 for a negative length reported "equal" for a comparison that never ran. The sentinel is positive because AES_GCM_decrypt_C() folds this return into a mask that reads a negative value as a match.
1 parent eec4207 commit 5651758

6 files changed

Lines changed: 119 additions & 10 deletions

File tree

‎ChangeLog.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,15 @@
101101
leak must stop doing so, and the returned key is shared with the
102102
certificate, so it must be treated as read only.
103103

104+
* **Behavioral change (`ConstantCompare()` rejects a negative length)**:
105+
`ConstantCompare()` and `wc_ConstantCompare()` now return 1, not 0
106+
("equal"), for a negative `length`, so a caller whose length arithmetic
107+
underflows no longer gets a false match. Differences are normalized to 1,
108+
so wide-byte targets (such as C28x) never return negative on a high-bit
109+
difference. `length == 0` still returns 0. The sentinel is positive
110+
because callers fold the result into constant-time masks that read a
111+
negative value as a match.
112+
104113
## Post-Quantum Cryptography (PQC)
105114

106115
* Added opt-in per-key Falcon signing caches (`--enable-falcon=cache-key`, `cache-basis`), roughly doubling signing speed with the default integer fpr backend. by @Frauschi

‎wolfcrypt/src/aes.c‎

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12602,9 +12602,7 @@ int WARN_UNUSED_RESULT AES_GCM_decrypt_C(
1260212602
return ret;
1260312603
xorbuf(Tprime, EKY0, sizeof(Tprime));
1260412604
#ifdef WC_AES_GCM_DEC_AUTH_EARLY
12605-
/* ConstantCompare returns the cumulative bitwise or of the bitwise xor of
12606-
* the pairwise bytes in the strings.
12607-
*/
12605+
/* ConstantCompare returns 0 when the tags match and 1 otherwise. */
1260812606
res = ConstantCompare(authTag, Tprime, authTagSz);
1260912607
/* convert positive retval from ConstantCompare() to all-1s word, in
1261012608
* constant time.
@@ -12685,9 +12683,7 @@ int WARN_UNUSED_RESULT AES_GCM_decrypt_C(
1268512683
}
1268612684

1268712685
#ifndef WC_AES_GCM_DEC_AUTH_EARLY
12688-
/* ConstantCompare returns the cumulative bitwise or of the bitwise xor of
12689-
* the pairwise bytes in the strings.
12690-
*/
12686+
/* ConstantCompare returns 0 when the tags match and 1 otherwise. */
1269112687
res = ConstantCompare(authTag, Tprime, (int)authTagSz);
1269212688
/* convert positive retval from ConstantCompare() to all-1s word, in
1269312689
* constant time.

‎wolfcrypt/src/misc.c‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -826,18 +826,26 @@ WC_MISC_STATIC WC_INLINE void ForceZero(void* mem, size_t len)
826826

827827

828828
#ifndef WOLFSSL_NO_CONST_CMP
829-
/* check all length bytes for equality, return 0 on success */
829+
/* Compare length bytes of a and b in constant time.
830+
* Returns 0 if equal, 1 if not or if length is negative. */
830831
WC_MISC_STATIC WC_INLINE int ConstantCompare(const byte* a, const byte* b,
831832
int length)
832833
{
833834
int i;
834-
int compareSum = 0;
835+
word32 compareSum = 0;
836+
837+
/* length is public, so branching is safe. Return positive: callers
838+
* such as AES_GCM_decrypt_C() build masks that read negative as equal. */
839+
if (length < 0) {
840+
return 1;
841+
}
835842

836843
for (i = 0; i < length; i++) {
837-
compareSum |= a[i] ^ b[i];
844+
compareSum |= (word32)(a[i] ^ b[i]);
838845
}
839846

840-
return compareSum;
847+
/* Normalize to 0/1 without a compare on secret data. */
848+
return (int)(((compareSum | ((word32)0 - compareSum)) >> 31) & 1U);
841849
}
842850
#endif
843851

‎wolfcrypt/test/test.c‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,9 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t macro_test(void);
864864
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t error_test(void);
865865
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t flags_test(void);
866866
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t octets_test(void);
867+
#ifdef WC_TEST_CONSTANTCOMPARE
868+
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t constantcompare_test(void);
869+
#endif
867870
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base64_test(void);
868871
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base16_test(void);
869872
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t asn_test(void);
@@ -2729,6 +2732,13 @@ options: [-s max_relative_stack_bytes] [-m max_relative_heap_memory_bytes]\n\
27292732
else
27302733
TEST_PASS("MEMORY test passed!\n");
27312734

2735+
#ifdef WC_TEST_CONSTANTCOMPARE
2736+
if ( (ret = constantcompare_test()) != 0)
2737+
TEST_FAIL("constantcompare test failed!\n", ret);
2738+
else
2739+
TEST_PASS("constantcompare test passed!\n");
2740+
#endif
2741+
27322742
if ( (ret = octets_test()) != 0)
27332743
TEST_FAIL("octets test failed!\n", ret);
27342744
else
@@ -5523,6 +5533,75 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t octets_test(void)
55235533
return 0;
55245534
}
55255535

5536+
#ifdef WC_TEST_CONSTANTCOMPARE
5537+
5538+
typedef int (*constantcompare_fn)(const byte* a, const byte* b, int length);
5539+
5540+
/* Run the ConstantCompare() checks against cmp.
5541+
* Returns 0 on success, a test error code on failure. */
5542+
static wc_test_ret_t constantcompare_sweep(constantcompare_fn cmp)
5543+
{
5544+
byte a[9] = { 0, 1, 2, 3, 4, 5, 6, 7, 8 };
5545+
byte b[9];
5546+
int i;
5547+
int length = (int)sizeof(a) - 1; /* leave a[8]/b[8] out of range */
5548+
5549+
XMEMCPY(b, a, sizeof(a));
5550+
if (cmp(a, b, length) != 0)
5551+
return WC_TEST_RET_ENC_NC;
5552+
b[0] ^= 0x01; /* length 0 must ignore even a differing first byte */
5553+
if (cmp(a, b, 0) != 0)
5554+
return WC_TEST_RET_ENC_NC;
5555+
5556+
/* A difference at any index must return exactly 1.
5557+
* Exercise both low and high bits (e.g. bit 15 on WOLFSSL_WIDE_BYTE). */
5558+
for (i = 0; i < length; i++) {
5559+
XMEMCPY(b, a, sizeof(a));
5560+
b[i] ^= 0x01;
5561+
if (cmp(a, b, length) != 1)
5562+
return WC_TEST_RET_ENC_I(i);
5563+
b[i] ^= (byte)~0;
5564+
if (cmp(a, b, length) != 1)
5565+
return WC_TEST_RET_ENC_I(i);
5566+
}
5567+
5568+
/* A difference past length must be ignored. */
5569+
XMEMCPY(b, a, sizeof(a));
5570+
b[length] ^= 0x01;
5571+
if (cmp(a, b, length) != 0)
5572+
return WC_TEST_RET_ENC_NC;
5573+
5574+
/* Negative length must return 1, not 0. */
5575+
XMEMSET(b, (byte)~a[0], sizeof(b));
5576+
if (cmp(a, b, -1) != 1)
5577+
return WC_TEST_RET_ENC_NC;
5578+
5579+
return 0;
5580+
}
5581+
5582+
/* Test ConstantCompare() and wc_ConstantCompare().
5583+
* Returns 0 on success, a test error code on failure. */
5584+
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t constantcompare_test(void)
5585+
{
5586+
wc_test_ret_t ret = 0;
5587+
5588+
WOLFSSL_ENTER("constantcompare_test");
5589+
5590+
/* Inline copy from misc.c. Under NO_INLINE it is hidden in the library
5591+
* and cannot be linked; wc_ConstantCompare() covers the same code. */
5592+
#ifndef NO_INLINE
5593+
ret = constantcompare_sweep(ConstantCompare);
5594+
if (ret != 0)
5595+
return ret;
5596+
#endif
5597+
5598+
#ifdef WC_TEST_HAVE_WC_CONSTANTCOMPARE
5599+
ret = constantcompare_sweep(wc_ConstantCompare);
5600+
#endif
5601+
return ret;
5602+
}
5603+
#endif /* WC_TEST_CONSTANTCOMPARE */
5604+
55265605
#ifndef NO_CODING
55275606

55285607
WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base64_test(void)

‎wolfcrypt/test/test.h‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,18 @@ wc_static_assert(-(long)MIN_CODE_E < 0x7ffL);
138138

139139
#endif /* !WC_TEST_RET_HAVE_CUSTOM_MACROS */
140140

141+
#ifndef WOLFSSL_NO_CONST_CMP
142+
/* wc_ConstantCompare() lives in memory.c, which is not built under
143+
* NO_WOLFSSL_MEMORY or, with autotools, WOLFSSL_LEANPSK. */
144+
#if !defined(NO_WOLFSSL_MEMORY) && !defined(WOLFSSL_LEANPSK)
145+
#define WC_TEST_HAVE_WC_CONSTANTCOMPARE
146+
#endif
147+
/* Test only when ConstantCompare() or wc_ConstantCompare() is linkable. */
148+
#if !defined(NO_INLINE) || defined(WC_TEST_HAVE_WC_CONSTANTCOMPARE)
149+
#define WC_TEST_CONSTANTCOMPARE
150+
#endif
151+
#endif
152+
141153
#ifdef WC_TEST_EXPORT_SUBTESTS
142154

143155
#if defined(NO_FILESYSTEM) || defined(WC_NO_RNG)
@@ -158,6 +170,9 @@ extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t macro_test(void);
158170
extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t error_test(void);
159171
extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t flags_test(void);
160172
extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t octets_test(void);
173+
#ifdef WC_TEST_CONSTANTCOMPARE
174+
extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t constantcompare_test(void);
175+
#endif
161176
extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base64_test(void);
162177
extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t base16_test(void);
163178
extern WOLFSSL_TEST_SUBROUTINE wc_test_ret_t asn_test(void);

‎wolfssl/wolfcrypt/memory.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,8 @@ WOLFSSL_API void wc_ForceZero(void *mem, size_t len);
336336
#endif
337337

338338
#ifndef WOLFSSL_NO_CONST_CMP
339+
/* Compare length bytes of a and b in constant time.
340+
* Returns 0 if equal, 1 if they differ or if length is negative. */
339341
WOLFSSL_API int wc_ConstantCompare(const byte* a, const byte* b, int length);
340342
#endif
341343

0 commit comments

Comments
 (0)