From 600880a0a4930f71884a3207ba39dcaf68c3e698 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Jul 2026 07:20:10 +0200 Subject: [PATCH 1/9] curve25519: fix big-endian public-key order check operand wc_curve25519_check_public's BIG_ENDIAN branch checked pub[i] != 0 in its top-order boundary loop where the mirrored LITTLE_ENDIAN branch checks pub[i] != 0xff. The field prime p = 2^255 - 19 has 0xff middle bytes, so the != 0 test broke out on the first non-0xff byte and the near-prime rejection was effectively non-functional for big-endian inputs. Match the little-endian branch so out-of-range big-endian public keys are rejected. --- wolfcrypt/src/curve25519.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfcrypt/src/curve25519.c b/wolfcrypt/src/curve25519.c index 12efe361f9..8e7afec540 100644 --- a/wolfcrypt/src/curve25519.c +++ b/wolfcrypt/src/curve25519.c @@ -977,7 +977,7 @@ int wc_curve25519_check_public(const byte* pub, word32 pubSz, int endian) /* Check for order-1 or higher. */ if (pub[0] == 0x7f) { for (i = 1; i < CURVE25519_KEYSIZE - 1; i++) { - if (pub[i] != 0) + if (pub[i] != 0xff) break; } if (i == CURVE25519_KEYSIZE - 1 && (pub[i] >= 0xec)) From 30ceba03c553188e18cb46789dd25ec293bdace3 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Jul 2026 07:20:10 +0200 Subject: [PATCH 2/9] dsa: gate CheckDsaLN on err==MP_OKAY in _DsaImportParamsRaw The (L,N) size check ran unconditionally, so after an earlier failure it overwrote the specific error (e.g. DH_CHECK_PUB_E from the p primality check) with BAD_FUNC_ARG, and computed qSz from a q that was never read (the q read is itself gated on err==MP_OKAY). Gate the size check the same way as the surrounding steps so the first, most specific error is returned. --- wolfcrypt/src/dsa.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/wolfcrypt/src/dsa.c b/wolfcrypt/src/dsa.c index 3ee03876fe..c9caf21306 100644 --- a/wolfcrypt/src/dsa.c +++ b/wolfcrypt/src/dsa.c @@ -546,13 +546,17 @@ static int _DsaImportParamsRaw(DsaKey* dsa, const char* p, const char* q, if (err == MP_OKAY) err = mp_read_radix(&dsa->g, g, MP_RADIX_HEX); - /* verify (L,N) pair bit lengths */ - pSz = mp_unsigned_bin_size(&dsa->p); - qSz = mp_unsigned_bin_size(&dsa->q); + /* verify (L,N) pair bit lengths - only when the reads above succeeded, so + * a more specific earlier error (e.g. DH_CHECK_PUB_E from the primality + * check) is not overwritten and qSz is not read from an unset q. */ + if (err == MP_OKAY) { + pSz = mp_unsigned_bin_size(&dsa->p); + qSz = mp_unsigned_bin_size(&dsa->q); - if (CheckDsaLN(pSz * WOLFSSL_BIT_SIZE, qSz * WOLFSSL_BIT_SIZE) != 0) { - WOLFSSL_MSG("Invalid DSA p or q parameter size"); - err = BAD_FUNC_ARG; + if (CheckDsaLN(pSz * WOLFSSL_BIT_SIZE, qSz * WOLFSSL_BIT_SIZE) != 0) { + WOLFSSL_MSG("Invalid DSA p or q parameter size"); + err = BAD_FUNC_ARG; + } } if (err != MP_OKAY) { From 3aecf6f31ef0dadc8dc1e4dabc9d291abe553aaa Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Jul 2026 07:20:10 +0200 Subject: [PATCH 3/9] fe_operations: declare asm curve25519_base to fix strict-C build curve25519.c calls curve25519_base() on the CURVED25519_X64 / aarch64 ARMASM path, but the function is only defined in assembly and had no C prototype in any header, so a strict C compiler (implicit-declaration as error) failed to build that configuration. Declare it alongside the sibling curve25519() prototype under the same guard. --- wolfssl/wolfcrypt/fe_operations.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wolfssl/wolfcrypt/fe_operations.h b/wolfssl/wolfcrypt/fe_operations.h index 65d0e1c904..97e1f1610d 100644 --- a/wolfssl/wolfcrypt/fe_operations.h +++ b/wolfssl/wolfcrypt/fe_operations.h @@ -112,6 +112,13 @@ WOLFSSL_LOCAL int curve25519_nb(byte * q, const byte * n, const byte * p, WOLFSSL_LOCAL void fe_init(void); WOLFSSL_LOCAL int curve25519(byte * q, const byte * n, const byte * p); +#if defined(CURVED25519_X64) || (defined(WOLFSSL_ARMASM) && defined(__aarch64__)) +/* Fixed-base scalar multiply provided by the x64/aarch64 assembly + * (fe_x25519_asm.S, armv8-curve25519); declared here as no other header + * prototypes it, which otherwise breaks a strict C (implicit-declaration) + * build of curve25519.c. */ +WOLFSSL_LOCAL int curve25519_base(byte * q, const byte * n); +#endif #ifdef WOLFSSL_CURVE25519_BLINDING WOLFSSL_LOCAL int curve25519_blind(byte* q, const byte* n, const byte* mask, const byte* p, const byte* rz); From 666de7d0bb999061a8c993de96689005becdd0f2 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Jul 2026 07:29:09 +0200 Subject: [PATCH 4/9] random: guard PollAndReSeed with !CUSTOM_RAND_GENERATE_BLOCK PollAndReSeed was compiled under #ifdef HAVE_HASHDRBG alone, but its only callers (in wc_RNG_GenerateBlock) sit in the #else of CUSTOM_RAND_GENERATE_ BLOCK, and it references wc_GenerateSeed which is not provided when a custom block generator replaces the seed layer. Defining HAVE_HASHDRBG together with CUSTOM_RAND_GENERATE_BLOCK therefore produced an undefined-symbol link error. Match _InitRng's guard so the function is compiled exactly when it can be called. --- wolfcrypt/src/random.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 5ca8ce20a1..1a7d233bad 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -2234,7 +2234,7 @@ int wc_InitRngNonce_ex(WC_RNG* rng, byte* nonce, word32 nonceSz, return _InitRng(rng, nonce, nonceSz, heap, devId); } -#ifdef HAVE_HASHDRBG +#if defined(HAVE_HASHDRBG) && !defined(CUSTOM_RAND_GENERATE_BLOCK) static int PollAndReSeed(WC_RNG* rng) { int ret = DRBG_NEED_RESEED; From 0a0e08470bef41df39ec6b73342f6c0b59db603a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Jul 2026 07:29:27 +0200 Subject: [PATCH 5/9] random: reject NULL output in Unix wc_GenerateSeed wc_GenerateSeed only checked os for NULL, so a NULL output passed straight into the entropy backend. glibc's vDSO getrandom() dereferences the buffer without validating it and segfaults instead of returning an error. Add the output NULL check (matching wc_RNG_GenerateBlock's convention) so the public seed API fails cleanly with BAD_FUNC_ARG. --- wolfcrypt/src/random.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wolfcrypt/src/random.c b/wolfcrypt/src/random.c index 1a7d233bad..daa7daef0c 100644 --- a/wolfcrypt/src/random.c +++ b/wolfcrypt/src/random.c @@ -5320,7 +5320,10 @@ int wc_GenerateSeed(OS_Seed* os, byte* output, word32 sz) { int ret = 0; - if (os == NULL) { + /* Validate output before any entropy backend dereferences it: some + * (e.g. glibc's vDSO getrandom()) fault on a NULL buffer rather than + * returning an error. Mirrors wc_RNG_GenerateBlock's NULL check. */ + if (os == NULL || output == NULL) { return BAD_FUNC_ARG; } From 9721c5221f8f8cb7b69747c83e391caedebc5f5c Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Jul 2026 07:52:15 +0200 Subject: [PATCH 6/9] fe_operations: error on CURVE25519_SMALL/ED25519_SMALL + x64 asm The small (reduced-C) curve25519/ed25519 code and the Intel x64 assembly both define the same fe_/sc_/curve25519 symbols. A user_settings.h that keeps CURVE25519_SMALL/ED25519_SMALL while USE_INTEL_SPEEDUP selects the x64 assembly (as happens with --enable-usersettings, which does not run configure's implementation selection) links with duplicate symbols that are hard to diagnose. Detect the incompatible combination at compile time via a clear #error keyed on CURVED25519_X64 (the single source of truth for 'x64 25519 asm active'), so it fails fast instead. NO_CURVED25519_X64 remains the escape hatch to keep the small implementation. --- wolfssl/wolfcrypt/fe_operations.h | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/wolfssl/wolfcrypt/fe_operations.h b/wolfssl/wolfcrypt/fe_operations.h index 97e1f1610d..88c785f2c3 100644 --- a/wolfssl/wolfcrypt/fe_operations.h +++ b/wolfssl/wolfcrypt/fe_operations.h @@ -40,6 +40,20 @@ #define CURVED25519_ASM_64BIT #define CURVED25519_ASM #endif + +/* The small (reduced-C) curve25519/ed25519 code and the Intel x64 assembly + * both provide the same fe_, sc_ and curve25519 symbols, so selecting both + * (for example a user_settings.h that keeps CURVE25519_SMALL/ED25519_SMALL + * while USE_INTEL_SPEEDUP enables the x64 assembly) produces duplicate-symbol + * link errors that are hard to diagnose. Detect the incompatible combination + * at compile time with a clear message instead. To keep the small + * implementation define NO_CURVED25519_X64; to use the assembly drop + * CURVE25519_SMALL / ED25519_SMALL. */ +#if defined(CURVED25519_X64) && \ + (defined(CURVE25519_SMALL) || defined(ED25519_SMALL)) + #error "CURVE25519_SMALL/ED25519_SMALL are incompatible with the Intel x64 curve25519/ed25519 assembly (CURVED25519_X64); define NO_CURVED25519_X64 to keep the small implementation, or remove the SMALL settings to use the assembly" +#endif + #if defined(WOLFSSL_ARMASM) #ifdef __aarch64__ #define CURVED25519_ASM_64BIT From 8dcef499701990e5bbf0befe270371a83eb77dd4 Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Jul 2026 08:56:49 +0200 Subject: [PATCH 7/9] dsa test: gate CheckDsaLN on err==MP_OKAY in _DsaImportParamsRaw Temporary fix before new tests land. --- tests/api/test_dsa.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/api/test_dsa.c b/tests/api/test_dsa.c index b645a57f45..cc56d78a03 100644 --- a/tests/api/test_dsa.c +++ b/tests/api/test_dsa.c @@ -426,9 +426,11 @@ int test_wc_DsaImportParamsRawCheck(void) /* null param pointers */ ExpectIntEQ(wc_DsaImportParamsRawCheck(&key, NULL, NULL, NULL, trusted, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); - /* illegal p length */ + /* illegal p length: the checked path now preserves the specific + * primality failure from p validation instead of overwriting it with + * BAD_FUNC_ARG during the later (L,N) size check. */ ExpectIntEQ(wc_DsaImportParamsRawCheck(&key, invalidP, q, g, trusted, NULL), - WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + WC_NO_ERR_TRACE(DH_CHECK_PUB_E)); /* illegal q length */ ExpectIntEQ(wc_DsaImportParamsRawCheck(&key, p, invalidQ, g, trusted, NULL), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); @@ -731,4 +733,3 @@ int test_wc_DsaCheckPubKey(void) #endif return EXPECT_RESULT(); } /* END test_wc_DsaCheckPubKey */ - From cf5c126a40246fd4c28f96b5ef22fe709806477a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Jul 2026 11:46:38 +0200 Subject: [PATCH 8/9] dh: deregister mem-zero entries in wc_FreeDhKey wc_DhImportKeyPair() registers key->priv for zero-on-free tracking via mp_memzero_add() under WOLFSSL_CHECK_MEM_ZERO, but wc_FreeDhKey() cleared priv with mp_forcezero(), which zeroes the data without removing the registration. Unlike wc_FreeRsaKey(), wc_FreeDhKey() had no wc_MemZero_Check() to remove the entry, so the registration leaked past the DhKey's lifetime and a later, unrelated wc_MemZero_Check() over reused stack could false-abort on it. Add wc_MemZero_Check(key, sizeof(*key)) at the end of wc_FreeDhKey(), mirroring wc_FreeRsaKey(). --- wolfcrypt/src/dh.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wolfcrypt/src/dh.c b/wolfcrypt/src/dh.c index 7da2173ac0..5a6daa9853 100644 --- a/wolfcrypt/src/dh.c +++ b/wolfcrypt/src/dh.c @@ -1013,6 +1013,13 @@ int wc_FreeDhKey(DhKey* key) #ifdef WOLFSSL_KCAPI_DH KcapiDh_Free(key); #endif + #ifdef WOLFSSL_CHECK_MEM_ZERO + /* Deregister any mem-zero entries covering this key (e.g. key->priv + * registered by wc_DhImportKeyPair) now that its fields are zeroed. + * Mirrors wc_FreeRsaKey(); mp_forcezero() alone does not remove the + * registration, so without this the entry leaks into later checks. */ + wc_MemZero_Check(key, sizeof(*key)); + #endif } return 0; } From 3fed5f90f234aedcd22a1605612eb040ebeb042a Mon Sep 17 00:00:00 2001 From: Daniele Lacamera Date: Fri, 10 Jul 2026 12:13:41 +0200 Subject: [PATCH 9/9] rsa: zero wc_MakeRsaKey stack temporaries for mem-zero check wc_MakeRsaKey()'s non-small-stack path declares p/q/tmp1..3 as stack mp_ints and only mp_init's them after the argument and size checks. An early 'goto out' from those checks reaches the WOLFSSL_CHECK_MEM_ZERO cleanup, which calls mp_memzero_check() on the still-uninitialized structs; the garbage size field makes the check scan an arbitrary stack range and can false-abort on unrelated registered memory. Zero the temporaries up front (under WOLFSSL_CHECK_MEM_ZERO) so the early-out cleanup is safe. --- wolfcrypt/src/rsa.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/wolfcrypt/src/rsa.c b/wolfcrypt/src/rsa.c index cd66eab2ef..80b33c2075 100644 --- a/wolfcrypt/src/rsa.c +++ b/wolfcrypt/src/rsa.c @@ -5402,6 +5402,23 @@ int wc_MakeRsaKey(RsaKey* key, int size, long e, WC_RNG* rng) #endif /* !WOLFSSL_CRYPTOCELL && !WOLFSSL_SE050 */ int err; +#if !defined(WOLFSSL_CRYPTOCELL) && \ + (!defined(WOLFSSL_SE050) || defined(WOLFSSL_SE050_NO_RSA)) && \ + !defined(WOLF_CRYPTO_CB_ONLY_RSA) && \ + !defined(WOLFSSL_MICROCHIP_TA100) && \ + !defined(WOLFSSL_SMALL_STACK) && defined(WOLFSSL_CHECK_MEM_ZERO) + /* Zero the stack temporaries so the mp_memzero_check() in the 'out' + * cleanup is safe even when an early argument/size check leaves via + * 'goto out' before these are mp_init'd - an uninitialized mp_int's size + * field would otherwise make the check scan an arbitrary stack range. + * Done here, after all declarations, to satisfy C89. */ + XMEMSET(&p_buf, 0, sizeof(p_buf)); + XMEMSET(&q_buf, 0, sizeof(q_buf)); + XMEMSET(&tmp1_buf, 0, sizeof(tmp1_buf)); + XMEMSET(&tmp2_buf, 0, sizeof(tmp2_buf)); + XMEMSET(&tmp3_buf, 0, sizeof(tmp3_buf)); +#endif + if (key == NULL || rng == NULL) { err = BAD_FUNC_ARG; goto out;