From e0421828ffaa790889f0f11962539777abbaf399 Mon Sep 17 00:00:00 2001 From: Anthony Hu Date: Wed, 25 Mar 2026 14:47:17 -0400 Subject: [PATCH 01/11] Fix TLS 1.3 PQC key share over heap read (ZD 21413) Validate that the received key share data length (keLen) is at least as large as the expected ciphertext size (ctSz) before passing it to wc_KyberKey_Decapsulate. A malicious TLS 1.3 server could send a short ML-KEM key share. --- src/tls.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tls.c b/src/tls.c index 09e6c92174..93984ac4ba 100644 --- a/src/tls.c +++ b/src/tls.c @@ -9950,6 +9950,10 @@ static int TLSX_KeyShare_ProcessPqcClient_ex(WOLFSSL* ssl, } #endif + if (ret == 0 && keyShareEntry->keLen < ctSz) { + WOLFSSL_MSG("PQC key share data too short for ciphertext."); + ret = BUFFER_E; + } if (ret == 0) { ret = wc_KyberKey_Decapsulate(kem, ssOutput, keyShareEntry->ke, ctSz); From b3278af8dc9d6ea2e5b59863b6c6420ef0acee3c Mon Sep 17 00:00:00 2001 From: Anthony Hu Date: Wed, 25 Mar 2026 14:48:07 -0400 Subject: [PATCH 02/11] Fix wc_*_delete() functions (ZD 21415) Save key->heap before calling wc_*_free(), which zeros the entire key structure via ForceZero. The saved heap pointer is then passed to XFREE instead of the now-zeroed key->heap. --- wolfcrypt/src/curve25519.c | 4 +++- wolfcrypt/src/dilithium.c | 4 +++- wolfcrypt/src/ed25519.c | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/wolfcrypt/src/curve25519.c b/wolfcrypt/src/curve25519.c index bc2961aca4..f8ca74255c 100644 --- a/wolfcrypt/src/curve25519.c +++ b/wolfcrypt/src/curve25519.c @@ -1113,10 +1113,12 @@ curve25519_key* wc_curve25519_new(void* heap, int devId, int *result_code) } int wc_curve25519_delete(curve25519_key* key, curve25519_key** key_p) { + void* heap; if (key == NULL) return BAD_FUNC_ARG; + heap = key->heap; wc_curve25519_free(key); - XFREE(key, key->heap, DYNAMIC_TYPE_CURVE25519); + XFREE(key, heap, DYNAMIC_TYPE_CURVE25519); if (key_p != NULL) *key_p = NULL; return 0; diff --git a/wolfcrypt/src/dilithium.c b/wolfcrypt/src/dilithium.c index 8e1ea8e638..df2acea9fa 100644 --- a/wolfcrypt/src/dilithium.c +++ b/wolfcrypt/src/dilithium.c @@ -10737,10 +10737,12 @@ dilithium_key* wc_dilithium_new(void* heap, int devId) int wc_dilithium_delete(dilithium_key* key, dilithium_key** key_p) { + void* heap; if (key == NULL) return BAD_FUNC_ARG; + heap = key->heap; wc_dilithium_free(key); - XFREE(key, key->heap, DYNAMIC_TYPE_DILITHIUM); + XFREE(key, heap, DYNAMIC_TYPE_DILITHIUM); if (key_p != NULL) *key_p = NULL; diff --git a/wolfcrypt/src/ed25519.c b/wolfcrypt/src/ed25519.c index bf5cf590d0..1bf12f7b24 100644 --- a/wolfcrypt/src/ed25519.c +++ b/wolfcrypt/src/ed25519.c @@ -1047,10 +1047,12 @@ ed25519_key* wc_ed25519_new(void* heap, int devId, int *result_code) } int wc_ed25519_delete(ed25519_key* key, ed25519_key** key_p) { + void* heap; if (key == NULL) return BAD_FUNC_ARG; + heap = key->heap; wc_ed25519_free(key); - XFREE(key, key->heap, DYNAMIC_TYPE_ED25519); + XFREE(key, heap, DYNAMIC_TYPE_ED25519); if (key_p != NULL) *key_p = NULL; return 0; From d14b506c513444750e3415b7fc976ef4345d11b9 Mon Sep 17 00:00:00 2001 From: Anthony Hu Date: Wed, 25 Mar 2026 14:48:43 -0400 Subject: [PATCH 03/11] Fix Dilithium with USE_INTEL_SPEEDUP (ZD 21417) Add check before word32 addition in dilithium_hash256() that could wrap to zero, bypassing the size check. Also reject absurdly large msgLen (> UINT32_MAX/2) in wc_dilithium_verify_ctx_msg. --- wolfcrypt/src/dilithium.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wolfcrypt/src/dilithium.c b/wolfcrypt/src/dilithium.c index df2acea9fa..228c5b22ff 100644 --- a/wolfcrypt/src/dilithium.c +++ b/wolfcrypt/src/dilithium.c @@ -503,6 +503,9 @@ static int dilithium_hash256(wc_Shake* shake256, const byte* data1, word64* state = shake256->s; word8 *state8 = (word8*)state; + if (data2Len > (UINT32_MAX - data1Len)) { + return BAD_FUNC_ARG; + } if (data1Len + data2Len >= WC_SHA3_256_COUNT * 8) { XMEMCPY(state8, data1, data1Len); XMEMCPY(state8 + data1Len, data2, WC_SHA3_256_COUNT * 8 - data1Len); @@ -10554,6 +10557,10 @@ int wc_dilithium_verify_ctx_msg(const byte* sig, word32 sigLen, const byte* ctx, if ((ret == 0) && (ctx == NULL) && (ctxLen > 0)) { ret = BAD_FUNC_ARG; } + /* Reject msgLen that would cause integer overflow in hash computations */ + if ((ret == 0) && (msgLen > UINT32_MAX / 2)) { + ret = BAD_FUNC_ARG; + } #ifdef WOLF_CRYPTO_CB if (ret == 0) { From c563f3932a3aa01ce8d779d038e9cbf90ffc0e41 Mon Sep 17 00:00:00 2001 From: Anthony Hu Date: Fri, 27 Mar 2026 09:15:14 -0400 Subject: [PATCH 04/11] Fix PKCS7 CBC padding oracle in EnvelopedData and EncryptedData (ZD 21422) Replace single last-byte padding check with full PKCS#5/PKCS#7 validation: verify padLen is non-zero and within block size. Both wc_PKCS7_DecodeEnvelopedData and wc_PKCS7_DecodeEncryptedData paths are fixed. --- wolfcrypt/src/pkcs7.c | 34 ++++++++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index c46885b168..1c38516631 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -13262,10 +13262,24 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in, padLen = encryptedContent[encryptedContentSz-1]; /* copy plaintext to output */ - if (padLen > encryptedContentSz) { + if (padLen == 0 || padLen > expBlockSz || + padLen > encryptedContentSz) { ret = BUFFER_E; break; } + /* Constant-time check all padding bytes */ + { + byte padCheck = 0; + int pi; + for (pi = encryptedContentSz - padLen; + pi < encryptedContentSz; pi++) { + padCheck |= encryptedContent[pi] ^ padLen; + } + if (padCheck != 0) { + ret = BUFFER_E; + break; + } + } #ifdef ASN_BER_TO_DER if (pkcs7->streamOutCb) { @@ -15315,12 +15329,28 @@ int wc_PKCS7_DecodeEncryptedData(wc_PKCS7* pkcs7, byte* in, word32 inSz, if (ret == 0) { padLen = encryptedContent[encryptedContentSz-1]; - if (padLen > encryptedContentSz) { + if (padLen == 0 || padLen > expBlockSz || + padLen > encryptedContentSz) { WOLFSSL_MSG("Bad padding size found"); ret = BUFFER_E; XFREE(encryptedContent, pkcs7->heap, DYNAMIC_TYPE_PKCS7); break; } + /* Constant-time check all padding bytes */ + { + byte padCheck = 0; + int pi; + for (pi = encryptedContentSz - padLen; + pi < encryptedContentSz; pi++) { + padCheck |= encryptedContent[pi] ^ padLen; + } + if (padCheck != 0) { + WOLFSSL_MSG("Bad padding bytes found"); + ret = BUFFER_E; + XFREE(encryptedContent, pkcs7->heap, DYNAMIC_TYPE_PKCS7); + break; + } + } /* copy plaintext to output */ if ((word32)(encryptedContentSz - padLen) > outputSz) { From 985cceaa97a2e7c8d493bde11452f54895208bfe Mon Sep 17 00:00:00 2001 From: Anthony Hu Date: Fri, 27 Mar 2026 09:15:22 -0400 Subject: [PATCH 05/11] Fix session cache restore dangling pointer (ZD 21423) Reinitialize pointer fields in WOLFSSL_SESSION after raw XMEMCPY or XFREAD in wolfSSL_memrestore_session_cache and wolfSSL_restore_session_cache. After restore, ticket is reset to staticTicket, ticketLenAlloc to 0, and peer to NULL. --- src/ssl_sess.c | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 5501c5cc2d..7ccc526f6a 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -522,6 +522,22 @@ int wolfSSL_memrestore_session_cache(const void* mem, int sz) #endif XMEMCPY(&SessionCache[i], row++, SIZEOF_SESSION_ROW); + #ifndef SESSION_CACHE_DYNAMIC_MEM + /* Reset pointers to safe values after raw copy */ + { + int j; + for (j = 0; j < SESSIONS_PER_ROW; j++) { + WOLFSSL_SESSION* s = &SessionCache[i].Sessions[j]; + #ifdef HAVE_SESSION_TICKET + s->ticket = s->staticTicket; + s->ticketLenAlloc = 0; + #endif + #if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) + s->peer = NULL; + #endif + } + } + #endif #ifdef ENABLE_SESSION_CACHE_ROW_LOCK SESSION_ROW_UNLOCK(&SessionCache[i]); #endif @@ -681,6 +697,22 @@ int wolfSSL_restore_session_cache(const char *fname) #endif ret = (int)XFREAD(&SessionCache[i], SIZEOF_SESSION_ROW, 1, file); + #ifndef SESSION_CACHE_DYNAMIC_MEM + /* Reset pointers to safe values after raw copy */ + { + int j; + for (j = 0; j < SESSIONS_PER_ROW; j++) { + WOLFSSL_SESSION* s = &SessionCache[i].Sessions[j]; + #ifdef HAVE_SESSION_TICKET + s->ticket = s->staticTicket; + s->ticketLenAlloc = 0; + #endif + #if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) + s->peer = NULL; + #endif + } + } + #endif #ifdef ENABLE_SESSION_CACHE_ROW_LOCK SESSION_ROW_UNLOCK(&SessionCache[i]); #endif From 5bd5f36dffc04033ea265412f31a53eab243fcfc Mon Sep 17 00:00:00 2001 From: Anthony Hu Date: Fri, 27 Mar 2026 09:15:27 -0400 Subject: [PATCH 06/11] Fix RSA exponent printing (ZD 21426) Increase buff size from 8 to 24 bytes in PrintPubKeyRSA and related EVP PKEY print functions. --- wolfcrypt/src/evp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index e7a87d249b..e74b9202fb 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -11877,7 +11877,7 @@ static int PrintHexWithColon(WOLFSSL_BIO* out, const byte* input, static int PrintPubKeyRSA(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, int indent, int bitlen, WOLFSSL_ASN1_PCTX* pctx) { - byte buff[8] = { 0 }; + byte buff[24] = { 0 }; int res = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); word32 inOutIdx = 0; word32 nSz; /* size of modulus */ @@ -12021,7 +12021,7 @@ static int PrintPubKeyEC(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, { byte* pub = NULL; word32 pubSz = 0; - byte buff[8] = { 0 }; + byte buff[24] = { 0 }; int res = WOLFSSL_SUCCESS; word32 inOutIdx = 0; int curveId = 0; @@ -12210,7 +12210,7 @@ static int PrintPubKeyDSA(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, int indent, int bitlen, WOLFSSL_ASN1_PCTX* pctx) { - byte buff[8] = { 0 }; + byte buff[24] = { 0 }; int length; int res = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); word32 inOutIdx = 0; @@ -12417,7 +12417,7 @@ static int PrintPubKeyDH(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, int indent, int bitlen, WOLFSSL_ASN1_PCTX* pctx) { - byte buff[8] = { 0 }; + byte buff[24] = { 0 }; int res = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); word32 length; word32 inOutIdx; From 2e32094545148ab1b48746e04937a4044527fca6 Mon Sep 17 00:00:00 2001 From: Anthony Hu Date: Fri, 27 Mar 2026 09:15:34 -0400 Subject: [PATCH 07/11] Add regression tests for fixes --- tests/api.c | 184 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/tests/api.c b/tests/api.c index eba7a6c301..fabc2fab9f 100644 --- a/tests/api.c +++ b/tests/api.c @@ -165,6 +165,19 @@ #include #endif +#ifdef HAVE_DILITHIUM + #include +#endif +#if defined(WOLFSSL_HAVE_MLKEM) + #include +#endif +#if defined(HAVE_PKCS7) + #include +#endif +#if !defined(NO_BIG_INT) + #include +#endif + /* include misc.c here regardless of NO_INLINE, because misc.c implementations * have default (hidden) visibility, and in the absence of visibility, it's * benign to mask out the library implementation. @@ -34758,6 +34771,47 @@ static int test_DhAgree_rejects_p_minus_1(void) return EXPECT_RESULT(); } + +/* ML-DSA HashML-DSA verify must reject hashLen > WC_MAX_DIGEST_SIZE */ +static int test_mldsa_verify_hash(void) +{ + EXPECT_DECLS; +#if defined(HAVE_DILITHIUM) && defined(WOLFSSL_WC_DILITHIUM) && \ + !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_DILITHIUM_NO_VERIFY) + dilithium_key key; + WC_RNG rng; + int res = 0; + byte sig[4000]; + byte hash[4096]; /* larger than WC_MAX_DIGEST_SIZE */ + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(sig, 0x41, sizeof(sig)); + XMEMSET(hash, 'A', sizeof(hash)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_dilithium_init(&key), 0); +#ifndef WOLFSSL_NO_ML_DSA_65 + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_65), 0); +#elif !defined(WOLFSSL_NO_ML_DSA_44) + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_44), 0); +#else + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_87), 0); +#endif + ExpectIntEQ(wc_dilithium_make_key(&key, &rng), 0); + + /* hashLen=4096 must be rejected with BUFFER_E, not overflow the stack */ + ExpectIntEQ(wc_dilithium_verify_ctx_hash(sig, sizeof(sig), NULL, 0, + WC_HASH_TYPE_SHA256, hash, sizeof(hash), &res, &key), + WC_NO_ERR_TRACE(BUFFER_E)); + + wc_dilithium_free(&key); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} + /* Test: Ed448 must reject identity public key (0,1) */ static int test_ed448_rejects_identity_key(void) { @@ -34936,6 +34990,133 @@ static int test_pkcs7_ori_oversized_oid(void) return EXPECT_RESULT(); } +/* Dilithium verify_ctx_msg must reject absurdly large msgLen */ +static int test_dilithium_hash(void) +{ + EXPECT_DECLS; +#if defined(HAVE_DILITHIUM) && defined(WOLFSSL_WC_DILITHIUM) && \ + !defined(WOLFSSL_DILITHIUM_NO_MAKE_KEY) && \ + !defined(WOLFSSL_DILITHIUM_NO_VERIFY) + dilithium_key key; + WC_RNG rng; + int res = 0; + byte sig[4000]; + byte msg[64]; + + XMEMSET(&key, 0, sizeof(key)); + XMEMSET(&rng, 0, sizeof(rng)); + XMEMSET(sig, 0, sizeof(sig)); + XMEMSET(msg, 'A', sizeof(msg)); + + ExpectIntEQ(wc_InitRng(&rng), 0); + ExpectIntEQ(wc_dilithium_init(&key), 0); +#ifndef WOLFSSL_NO_ML_DSA_65 + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_65), 0); +#elif !defined(WOLFSSL_NO_ML_DSA_44) + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_44), 0); +#else + ExpectIntEQ(wc_dilithium_set_level(&key, WC_ML_DSA_87), 0); +#endif + ExpectIntEQ(wc_dilithium_make_key(&key, &rng), 0); + + ExpectIntEQ(wc_dilithium_verify_ctx_msg(sig, sizeof(sig), NULL, 0, + msg, 0xFFFFFFC0u, &res, &key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + + wc_dilithium_free(&key); + DoExpectIntEQ(wc_FreeRng(&rng), 0); +#endif + return EXPECT_RESULT(); +} + +/* PKCS7 CBC must validate all padding bytes */ +static int test_pkcs7_padding(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS7) && !defined(NO_AES) && defined(HAVE_AES_CBC) && \ + defined(WOLFSSL_AES_256) && !defined(NO_PKCS7_ENCRYPTED_DATA) + PKCS7 pkcs7; + byte key[32]; + byte plaintext[27]; /* 27 bytes → padded to 32 → padding = 05 05 05 05 05 */ + byte encoded[4096]; + byte output[256]; + byte modified[4096]; + int encodedSz; + int outSz; + int ctOff = -1; + int ctLen = 0; + int i; + + XMEMSET(key, 0xAA, sizeof(key)); + XMEMSET(plaintext, 'X', sizeof(plaintext)); + + /* Encode EncryptedData */ + XMEMSET(&pkcs7, 0, sizeof(pkcs7)); + ExpectIntEQ(wc_PKCS7_Init(&pkcs7, NULL, 0), 0); + pkcs7.content = plaintext; + pkcs7.contentSz = sizeof(plaintext); + pkcs7.contentOID = DATA; + pkcs7.encryptOID = AES256CBCb; + pkcs7.encryptionKey = key; + pkcs7.encryptionKeySz = sizeof(key); + + ExpectIntGT(encodedSz = wc_PKCS7_EncodeEncryptedData(&pkcs7, encoded, + sizeof(encoded)), 0); + + /* Verify normal decrypt works */ + ExpectIntEQ(outSz = wc_PKCS7_DecodeEncryptedData(&pkcs7, encoded, + (word32)encodedSz, output, sizeof(output)), (int)sizeof(plaintext)); + wc_PKCS7_Free(&pkcs7); + + /* Find ciphertext block in encoded DER */ + if (EXPECT_SUCCESS()) { + for (i = encodedSz - 10; i > 10; i--) { + if (encoded[i] == 0x04 || encoded[i] == 0x80) { + int len, lbytes; + + if (encoded[i+1] < 0x80) { + len = encoded[i+1]; lbytes = 1; + } + else if (encoded[i+1] == 0x81) { + len = encoded[i+2]; lbytes = 2; + } + else { + continue; + } + if (len > 0 && len % 16 == 0 && + i + 1 + lbytes + len <= encodedSz) { + ctOff = i + 1 + lbytes; + ctLen = len; + break; + } + } + } + } + ExpectIntGT(ctOff, 0); + ExpectIntGE(ctLen, 32); + + /* Corrupt an interior padding byte via CBC bit-flip */ + if (EXPECT_SUCCESS()) { + XMEMCPY(modified, encoded, (size_t)encodedSz); + /* Flip byte in penultimate block to corrupt interior padding */ + modified[ctOff + ctLen - 32 + 11] ^= 0x42; + + /* Decrypt modified ciphertext — must fail, not succeed */ + XMEMSET(&pkcs7, 0, sizeof(pkcs7)); + ExpectIntEQ(wc_PKCS7_Init(&pkcs7, NULL, 0), 0); + pkcs7.encryptionKey = key; + pkcs7.encryptionKeySz = sizeof(key); + + outSz = wc_PKCS7_DecodeEncryptedData(&pkcs7, modified, + (word32)encodedSz, output, sizeof(output)); + /* Must return an error — if it returns plaintext size, padding + * oracle vulnerability exists */ + ExpectIntLT(outSz, 0); + wc_PKCS7_Free(&pkcs7); + } +#endif + return EXPECT_RESULT(); +} + TEST_CASE testCases[] = { TEST_DECL(test_fileAccess), @@ -35754,11 +35935,14 @@ TEST_CASE testCases[] = { TEST_DECL(test_ed448_rejects_identity_key), TEST_DECL(test_pkcs7_decode_encrypted_outputsz), TEST_DECL(test_pkcs7_ori_oversized_oid), + TEST_DECL(test_pkcs7_padding), #if defined(WOLFSSL_SNIFFER) && defined(WOLFSSL_SNIFFER_CHAIN_INPUT) TEST_DECL(test_sniffer_chain_input_overflow), #endif + TEST_DECL(test_mldsa_verify_hash), + TEST_DECL(test_dilithium_hash), /* This test needs to stay at the end to clean up any caches allocated. */ TEST_DECL(test_wolfSSL_Cleanup) }; From c335f7dd6f71594adf3915387f61d5a45a75e6b7 Mon Sep 17 00:00:00 2001 From: Paul Adelsbach Date: Mon, 30 Mar 2026 16:20:50 -0700 Subject: [PATCH 08/11] Remove UTF-8 chars Get rid of weird character Fix warning found by CI Style changes Addressed 1 and 2. --- src/ssl_sess.c | 31 ++++++++++++++++------ tests/api.c | 10 ++++---- wolfcrypt/src/evp.c | 8 +++--- wolfcrypt/src/pkcs7.c | 54 +++++++++++++++++++-------------------- wolfssl/wolfcrypt/types.h | 1 + 5 files changed, 60 insertions(+), 44 deletions(-) diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 7ccc526f6a..1f9e75132d 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -487,6 +487,12 @@ int wolfSSL_memsave_session_cache(void* mem, int sz) int wolfSSL_memrestore_session_cache(const void* mem, int sz) { int i; + #ifndef SESSION_CACHE_DYNAMIC_MEM + #if defined(HAVE_SESSION_TICKET) || \ + (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA)) + int j; + #endif + #endif cache_header_t cache_header; SessionRow* row = (SessionRow*)((byte*)mem + sizeof(cache_header)); @@ -523,21 +529,24 @@ int wolfSSL_memrestore_session_cache(const void* mem, int sz) XMEMCPY(&SessionCache[i], row++, SIZEOF_SESSION_ROW); #ifndef SESSION_CACHE_DYNAMIC_MEM + #if defined(HAVE_SESSION_TICKET) || \ + (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA)) /* Reset pointers to safe values after raw copy */ - { - int j; - for (j = 0; j < SESSIONS_PER_ROW; j++) { - WOLFSSL_SESSION* s = &SessionCache[i].Sessions[j]; + for (j = 0; j < SESSIONS_PER_ROW; j++) { + WOLFSSL_SESSION* s = &SessionCache[i].Sessions[j]; #ifdef HAVE_SESSION_TICKET - s->ticket = s->staticTicket; - s->ticketLenAlloc = 0; + s->ticket = s->staticTicket; + s->ticketLenAlloc = 0; + if (s->ticketLen > SESSION_TICKET_LEN) { + s->ticketLen = SESSION_TICKET_LEN; + } #endif #if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) - s->peer = NULL; + s->peer = NULL; #endif - } } #endif + #endif #ifdef ENABLE_SESSION_CACHE_ROW_LOCK SESSION_ROW_UNLOCK(&SessionCache[i]); #endif @@ -698,6 +707,8 @@ int wolfSSL_restore_session_cache(const char *fname) ret = (int)XFREAD(&SessionCache[i], SIZEOF_SESSION_ROW, 1, file); #ifndef SESSION_CACHE_DYNAMIC_MEM + #if defined(HAVE_SESSION_TICKET) || \ + (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA)) /* Reset pointers to safe values after raw copy */ { int j; @@ -706,6 +717,9 @@ int wolfSSL_restore_session_cache(const char *fname) #ifdef HAVE_SESSION_TICKET s->ticket = s->staticTicket; s->ticketLenAlloc = 0; + if (s->ticketLen > SESSION_TICKET_LEN) { + s->ticketLen = SESSION_TICKET_LEN; + } #endif #if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) s->peer = NULL; @@ -713,6 +727,7 @@ int wolfSSL_restore_session_cache(const char *fname) } } #endif + #endif #ifdef ENABLE_SESSION_CACHE_ROW_LOCK SESSION_ROW_UNLOCK(&SessionCache[i]); #endif diff --git a/tests/api.c b/tests/api.c index fabc2fab9f..0774ae99f1 100644 --- a/tests/api.c +++ b/tests/api.c @@ -35020,7 +35020,7 @@ static int test_dilithium_hash(void) ExpectIntEQ(wc_dilithium_make_key(&key, &rng), 0); ExpectIntEQ(wc_dilithium_verify_ctx_msg(sig, sizeof(sig), NULL, 0, - msg, 0xFFFFFFC0u, &res, &key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); + msg, 0xFFFFFFC0, &res, &key), WC_NO_ERR_TRACE(BAD_FUNC_ARG)); wc_dilithium_free(&key); DoExpectIntEQ(wc_FreeRng(&rng), 0); @@ -35036,11 +35036,11 @@ static int test_pkcs7_padding(void) defined(WOLFSSL_AES_256) && !defined(NO_PKCS7_ENCRYPTED_DATA) PKCS7 pkcs7; byte key[32]; - byte plaintext[27]; /* 27 bytes → padded to 32 → padding = 05 05 05 05 05 */ + byte plaintext[27]; /* 27 bytes -> padded to 32 -> padding = 05 05 05 05 05 */ byte encoded[4096]; byte output[256]; byte modified[4096]; - int encodedSz; + int encodedSz = 0; int outSz; int ctOff = -1; int ctLen = 0; @@ -35100,7 +35100,7 @@ static int test_pkcs7_padding(void) /* Flip byte in penultimate block to corrupt interior padding */ modified[ctOff + ctLen - 32 + 11] ^= 0x42; - /* Decrypt modified ciphertext — must fail, not succeed */ + /* Decrypt modified ciphertext - must fail, not succeed */ XMEMSET(&pkcs7, 0, sizeof(pkcs7)); ExpectIntEQ(wc_PKCS7_Init(&pkcs7, NULL, 0), 0); pkcs7.encryptionKey = key; @@ -35108,7 +35108,7 @@ static int test_pkcs7_padding(void) outSz = wc_PKCS7_DecodeEncryptedData(&pkcs7, modified, (word32)encodedSz, output, sizeof(output)); - /* Must return an error — if it returns plaintext size, padding + /* Must return an error - if it returns plaintext size, padding * oracle vulnerability exists */ ExpectIntLT(outSz, 0); wc_PKCS7_Free(&pkcs7); diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index e74b9202fb..c71393fe3c 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -11877,7 +11877,7 @@ static int PrintHexWithColon(WOLFSSL_BIO* out, const byte* input, static int PrintPubKeyRSA(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, int indent, int bitlen, WOLFSSL_ASN1_PCTX* pctx) { - byte buff[24] = { 0 }; + byte buff[EVP_EXPONENT_PRINT_MAX] = { 0 }; int res = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); word32 inOutIdx = 0; word32 nSz; /* size of modulus */ @@ -12021,7 +12021,7 @@ static int PrintPubKeyEC(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, { byte* pub = NULL; word32 pubSz = 0; - byte buff[24] = { 0 }; + byte buff[EVP_EXPONENT_PRINT_MAX] = { 0 }; int res = WOLFSSL_SUCCESS; word32 inOutIdx = 0; int curveId = 0; @@ -12210,7 +12210,7 @@ static int PrintPubKeyDSA(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, int indent, int bitlen, WOLFSSL_ASN1_PCTX* pctx) { - byte buff[24] = { 0 }; + byte buff[EVP_EXPONENT_PRINT_MAX] = { 0 }; int length; int res = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); word32 inOutIdx = 0; @@ -12417,7 +12417,7 @@ static int PrintPubKeyDH(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, int indent, int bitlen, WOLFSSL_ASN1_PCTX* pctx) { - byte buff[24] = { 0 }; + byte buff[EVP_EXPONENT_PRINT_MAX] = { 0 }; int res = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); word32 length; word32 inOutIdx; diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 1c38516631..459f8fe55f 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -12759,7 +12759,9 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in, byte* encryptedContent = NULL; int explicitOctet = 0; word32 localIdx = 0; - byte tag = 0; + byte tag = 0; + byte padCheck = 0; + int padIndex; if (pkcs7 == NULL) return BAD_FUNC_ARG; @@ -13267,18 +13269,16 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in, ret = BUFFER_E; break; } - /* Constant-time check all padding bytes */ - { - byte padCheck = 0; - int pi; - for (pi = encryptedContentSz - padLen; - pi < encryptedContentSz; pi++) { - padCheck |= encryptedContent[pi] ^ padLen; - } - if (padCheck != 0) { - ret = BUFFER_E; - break; - } + + /* Check all padding bytes. Better implementation would be to run + * through the entire block. */ + for (padIndex = encryptedContentSz - padLen; + padIndex < encryptedContentSz; padIndex++) { + padCheck |= encryptedContent[padIndex] ^ padLen; + } + if (padCheck != 0) { + ret = BUFFER_E; + break; } #ifdef ASN_BER_TO_DER @@ -15052,6 +15052,8 @@ int wc_PKCS7_DecodeEncryptedData(wc_PKCS7* pkcs7, byte* in, word32 inSz, byte* pkiMsg = in; word32 pkiMsgSz = inSz; byte tag = 0; + byte padCheck = 0; + int padIndex; if (pkcs7 == NULL || ((pkcs7->encryptionKey == NULL || pkcs7->encryptionKeySz == 0) && @@ -15336,20 +15338,18 @@ int wc_PKCS7_DecodeEncryptedData(wc_PKCS7* pkcs7, byte* in, word32 inSz, XFREE(encryptedContent, pkcs7->heap, DYNAMIC_TYPE_PKCS7); break; } - /* Constant-time check all padding bytes */ - { - byte padCheck = 0; - int pi; - for (pi = encryptedContentSz - padLen; - pi < encryptedContentSz; pi++) { - padCheck |= encryptedContent[pi] ^ padLen; - } - if (padCheck != 0) { - WOLFSSL_MSG("Bad padding bytes found"); - ret = BUFFER_E; - XFREE(encryptedContent, pkcs7->heap, DYNAMIC_TYPE_PKCS7); - break; - } + + /* Check all padding bytes. Better implementation would be to + * run through the entire block. */ + for (padIndex = encryptedContentSz - padLen; + padIndex < encryptedContentSz; padIndex++) { + padCheck |= encryptedContent[padIndex] ^ padLen; + } + if (padCheck != 0) { + WOLFSSL_MSG("Bad padding bytes found"); + ret = BUFFER_E; + XFREE(encryptedContent, pkcs7->heap, DYNAMIC_TYPE_PKCS7); + break; } /* copy plaintext to output */ diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 6fd7f8198d..735d55e6b3 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -2423,6 +2423,7 @@ enum Max_ASN { #endif /* WOLFSSL_CERT_GEN */ +#define EVP_EXPONENT_PRINT_MAX 24 #ifdef __cplusplus } /* extern "C" */ From ad1cc4e87f1a5be5625014d162b64e1e99443a63 Mon Sep 17 00:00:00 2001 From: JacobBarthelmeh Date: Tue, 7 Apr 2026 10:26:16 -0600 Subject: [PATCH 09/11] adjust test case return value check after rebase --- tests/api.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/api.c b/tests/api.c index 0774ae99f1..5eca4071a8 100644 --- a/tests/api.c +++ b/tests/api.c @@ -34801,10 +34801,10 @@ static int test_mldsa_verify_hash(void) #endif ExpectIntEQ(wc_dilithium_make_key(&key, &rng), 0); - /* hashLen=4096 must be rejected with BUFFER_E, not overflow the stack */ + /* hashLen=4096 must be rejected, not overflow the stack */ ExpectIntEQ(wc_dilithium_verify_ctx_hash(sig, sizeof(sig), NULL, 0, WC_HASH_TYPE_SHA256, hash, sizeof(hash), &res, &key), - WC_NO_ERR_TRACE(BUFFER_E)); + WC_NO_ERR_TRACE(BAD_LENGTH_E)); wc_dilithium_free(&key); DoExpectIntEQ(wc_FreeRng(&rng), 0); From ecfd1174bbe9d65005da74d2fdcbac15d94d4ca6 Mon Sep 17 00:00:00 2001 From: JacobBarthelmeh Date: Tue, 7 Apr 2026 14:10:25 -0600 Subject: [PATCH 10/11] refactor sanity pointer set of session and clean up macro guards --- src/ssl_sess.c | 85 ++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/src/ssl_sess.c b/src/ssl_sess.c index 1f9e75132d..d28d28976c 100644 --- a/src/ssl_sess.c +++ b/src/ssl_sess.c @@ -483,16 +483,42 @@ int wolfSSL_memsave_session_cache(void* mem, int sz) } +#if !defined(SESSION_CACHE_DYNAMIC_MEM) && \ + (defined(HAVE_SESSION_TICKET) || \ + (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA))) +static void SessionSanityPointerSet(SessionRow* row) +{ + int j; + + /* Reset pointers to safe values after raw copy */ + for (j = 0; j < SESSIONS_PER_ROW; j++) { + WOLFSSL_SESSION* s = &row->Sessions[j]; +#ifdef HAVE_SESSION_TICKET + s->ticket = s->staticTicket; + s->ticketLenAlloc = 0; + if (s->ticketLen > SESSION_TICKET_LEN) { + s->ticketLen = SESSION_TICKET_LEN; + } +#endif +#if defined(WOLFSSL_TLS13) && defined(HAVE_SESSION_TICKET) && \ + defined(WOLFSSL_TICKET_NONCE_MALLOC) && \ + (!defined(HAVE_FIPS) || (defined(FIPS_VERSION_GE) && FIPS_VERSION_GE(5,3))) + s->ticketNonce.data = s->ticketNonce.dataStatic; + if (s->ticketNonce.len > MAX_TICKET_NONCE_STATIC_SZ) { + s->ticketNonce.len = MAX_TICKET_NONCE_STATIC_SZ; + } +#endif +#if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) + s->peer = NULL; +#endif + } +} +#endif + /* Restore the persistent session cache from memory */ int wolfSSL_memrestore_session_cache(const void* mem, int sz) { int i; - #ifndef SESSION_CACHE_DYNAMIC_MEM - #if defined(HAVE_SESSION_TICKET) || \ - (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA)) - int j; - #endif - #endif cache_header_t cache_header; SessionRow* row = (SessionRow*)((byte*)mem + sizeof(cache_header)); @@ -528,24 +554,10 @@ int wolfSSL_memrestore_session_cache(const void* mem, int sz) #endif XMEMCPY(&SessionCache[i], row++, SIZEOF_SESSION_ROW); - #ifndef SESSION_CACHE_DYNAMIC_MEM - #if defined(HAVE_SESSION_TICKET) || \ - (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA)) - /* Reset pointers to safe values after raw copy */ - for (j = 0; j < SESSIONS_PER_ROW; j++) { - WOLFSSL_SESSION* s = &SessionCache[i].Sessions[j]; - #ifdef HAVE_SESSION_TICKET - s->ticket = s->staticTicket; - s->ticketLenAlloc = 0; - if (s->ticketLen > SESSION_TICKET_LEN) { - s->ticketLen = SESSION_TICKET_LEN; - } - #endif - #if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) - s->peer = NULL; - #endif - } - #endif + #if !defined(SESSION_CACHE_DYNAMIC_MEM) && \ + (defined(HAVE_SESSION_TICKET) || \ + (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA))) + SessionSanityPointerSet(&SessionCache[i]); #endif #ifdef ENABLE_SESSION_CACHE_ROW_LOCK SESSION_ROW_UNLOCK(&SessionCache[i]); @@ -706,27 +718,10 @@ int wolfSSL_restore_session_cache(const char *fname) #endif ret = (int)XFREAD(&SessionCache[i], SIZEOF_SESSION_ROW, 1, file); - #ifndef SESSION_CACHE_DYNAMIC_MEM - #if defined(HAVE_SESSION_TICKET) || \ - (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA)) - /* Reset pointers to safe values after raw copy */ - { - int j; - for (j = 0; j < SESSIONS_PER_ROW; j++) { - WOLFSSL_SESSION* s = &SessionCache[i].Sessions[j]; - #ifdef HAVE_SESSION_TICKET - s->ticket = s->staticTicket; - s->ticketLenAlloc = 0; - if (s->ticketLen > SESSION_TICKET_LEN) { - s->ticketLen = SESSION_TICKET_LEN; - } - #endif - #if defined(SESSION_CERTS) && defined(OPENSSL_EXTRA) - s->peer = NULL; - #endif - } - } - #endif + #if !defined(SESSION_CACHE_DYNAMIC_MEM) && \ + (defined(HAVE_SESSION_TICKET) || \ + (defined(SESSION_CERTS) && defined(OPENSSL_EXTRA))) + SessionSanityPointerSet(&SessionCache[i]); #endif #ifdef ENABLE_SESSION_CACHE_ROW_LOCK SESSION_ROW_UNLOCK(&SessionCache[i]); From d1c6423b82522c9bf5b9aa10005efd98c978190c Mon Sep 17 00:00:00 2001 From: JacobBarthelmeh Date: Tue, 7 Apr 2026 16:09:52 -0600 Subject: [PATCH 11/11] make the padding check constant time and move evp exponent print size macro to local file --- wolfcrypt/src/evp.c | 9 +++++---- wolfcrypt/src/pkcs7.c | 41 +++++++++++++++++---------------------- wolfssl/wolfcrypt/types.h | 2 -- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/wolfcrypt/src/evp.c b/wolfcrypt/src/evp.c index c71393fe3c..76e1496c25 100644 --- a/wolfcrypt/src/evp.c +++ b/wolfcrypt/src/evp.c @@ -318,6 +318,7 @@ int wolfSSL_EVP_PKEY_is_a(const WOLFSSL_EVP_PKEY *pkey, const char *name) { #define WOLFSSL_EVP_PKEY_PRINT_LINE_WIDTH_MAX 80 #define WOLFSSL_EVP_PKEY_PRINT_DIGITS_PER_LINE 15 +#define WOLFSSL_EVP_EXPONENT_PRINT_MAX 24 static unsigned int cipherType(const WOLFSSL_EVP_CIPHER *cipher); @@ -11877,7 +11878,7 @@ static int PrintHexWithColon(WOLFSSL_BIO* out, const byte* input, static int PrintPubKeyRSA(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, int indent, int bitlen, WOLFSSL_ASN1_PCTX* pctx) { - byte buff[EVP_EXPONENT_PRINT_MAX] = { 0 }; + byte buff[WOLFSSL_EVP_EXPONENT_PRINT_MAX] = { 0 }; int res = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); word32 inOutIdx = 0; word32 nSz; /* size of modulus */ @@ -12021,7 +12022,7 @@ static int PrintPubKeyEC(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, { byte* pub = NULL; word32 pubSz = 0; - byte buff[EVP_EXPONENT_PRINT_MAX] = { 0 }; + byte buff[WOLFSSL_EVP_EXPONENT_PRINT_MAX] = { 0 }; int res = WOLFSSL_SUCCESS; word32 inOutIdx = 0; int curveId = 0; @@ -12210,7 +12211,7 @@ static int PrintPubKeyDSA(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, int indent, int bitlen, WOLFSSL_ASN1_PCTX* pctx) { - byte buff[EVP_EXPONENT_PRINT_MAX] = { 0 }; + byte buff[WOLFSSL_EVP_EXPONENT_PRINT_MAX] = { 0 }; int length; int res = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); word32 inOutIdx = 0; @@ -12417,7 +12418,7 @@ static int PrintPubKeyDH(WOLFSSL_BIO* out, const byte* pkey, int pkeySz, int indent, int bitlen, WOLFSSL_ASN1_PCTX* pctx) { - byte buff[EVP_EXPONENT_PRINT_MAX] = { 0 }; + byte buff[WOLFSSL_EVP_EXPONENT_PRINT_MAX] = { 0 }; int res = WC_NO_ERR_TRACE(WOLFSSL_FAILURE); word32 length; word32 inOutIdx; diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 459f8fe55f..dda1032877 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -13263,18 +13263,16 @@ int wc_PKCS7_DecodeEnvelopedData(wc_PKCS7* pkcs7, byte* in, padLen = encryptedContent[encryptedContentSz-1]; - /* copy plaintext to output */ - if (padLen == 0 || padLen > expBlockSz || - padLen > encryptedContentSz) { - ret = BUFFER_E; - break; - } - - /* Check all padding bytes. Better implementation would be to run - * through the entire block. */ - for (padIndex = encryptedContentSz - padLen; + /* Constant-time padding check */ + padCheck |= ctMaskEq(padLen, 0); + padCheck |= ctMaskGT(padLen, expBlockSz); + padCheck |= ctMaskGT(padLen, encryptedContentSz); + padCheck |= ctMaskGT(expBlockSz, encryptedContentSz); + for (padIndex = encryptedContentSz - expBlockSz; padIndex < encryptedContentSz; padIndex++) { - padCheck |= encryptedContent[padIndex] ^ padLen; + byte inPad = ctMaskGTE(padIndex, + encryptedContentSz - (int)padLen); + padCheck |= inPad & (encryptedContent[padIndex] ^ padLen); } if (padCheck != 0) { ret = BUFFER_E; @@ -15331,19 +15329,16 @@ int wc_PKCS7_DecodeEncryptedData(wc_PKCS7* pkcs7, byte* in, word32 inSz, if (ret == 0) { padLen = encryptedContent[encryptedContentSz-1]; - if (padLen == 0 || padLen > expBlockSz || - padLen > encryptedContentSz) { - WOLFSSL_MSG("Bad padding size found"); - ret = BUFFER_E; - XFREE(encryptedContent, pkcs7->heap, DYNAMIC_TYPE_PKCS7); - break; - } - - /* Check all padding bytes. Better implementation would be to - * run through the entire block. */ - for (padIndex = encryptedContentSz - padLen; + /* Constant-time padding check */ + padCheck |= ctMaskEq(padLen, 0); + padCheck |= ctMaskGT(padLen, expBlockSz); + padCheck |= ctMaskGT(padLen, encryptedContentSz); + padCheck |= ctMaskGT(expBlockSz, encryptedContentSz); + for (padIndex = encryptedContentSz - expBlockSz; padIndex < encryptedContentSz; padIndex++) { - padCheck |= encryptedContent[padIndex] ^ padLen; + byte inPad = ctMaskGTE(padIndex, + encryptedContentSz - (int)padLen); + padCheck |= inPad & (encryptedContent[padIndex] ^ padLen); } if (padCheck != 0) { WOLFSSL_MSG("Bad padding bytes found"); diff --git a/wolfssl/wolfcrypt/types.h b/wolfssl/wolfcrypt/types.h index 735d55e6b3..ec1e87a00b 100644 --- a/wolfssl/wolfcrypt/types.h +++ b/wolfssl/wolfcrypt/types.h @@ -2423,8 +2423,6 @@ enum Max_ASN { #endif /* WOLFSSL_CERT_GEN */ -#define EVP_EXPONENT_PRINT_MAX 24 - #ifdef __cplusplus } /* extern "C" */ #endif