From 94b36c9092a30a81320dcdf021fd54e4773125da Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Mon, 13 Apr 2026 13:14:26 -0700 Subject: [PATCH 1/6] Harden chain depth bounds and parser input validation Enforce MAX_CHAIN_DEPTH limits in OCSP chain processing (SendCertificateStatus, ProcessChainOCSPRequest), certificate loading (ProcessUserChain), and TLS 1.3 certificate sending (SendTls13Certificate). Add idx bounds checks to chain accessors in ssl.c. Harden SNI extension parser (TLSX_SNI_GetFromBuffer) with length checks preventing buffer overreads on malformed ClientHello. Fix off-by-one in TLSX_CSR_Free where <= should be < since csr->requests is a count, not a max index. Add remaining-buffer bounds checks to PKCS7 decoders: DecodeEnvelopedData, DecodeAuthEnvelopedData (encryptedContentSz and authTagSz), DecodeEncryptedData, SignedData null signature tag, and PwriKek_KeyUnWrap cekLen validation. --- src/internal.c | 5 +++-- src/ssl.c | 6 +++--- src/ssl_load.c | 7 +++++++ src/tls.c | 8 +++++++- src/tls13.c | 4 ++++ wolfcrypt/src/pkcs7.c | 12 ++++++++++++ 6 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/internal.c b/src/internal.c index 75c007c6572..5f091580397 100644 --- a/src/internal.c +++ b/src/internal.c @@ -26208,7 +26208,8 @@ int SendCertificateStatus(WOLFSSL* ssl) } if (chain && chain->buffer) { - while (ret == 0 && idx + OPAQUE24_LEN < chain->length) { + while (ret == 0 && i < MAX_CHAIN_DEPTH && + idx + OPAQUE24_LEN < chain->length) { c24to32(chain->buffer + idx, &der.length); idx += OPAQUE24_LEN; @@ -26249,7 +26250,7 @@ int SendCertificateStatus(WOLFSSL* ssl) WC_FREE_VAR_EX(cert, ssl->heap, DYNAMIC_TYPE_DCERT); } else { - while (ret == 0 && + while (ret == 0 && i < MAX_CHAIN_DEPTH && NULL != (request = ssl->ctx->chainOcspRequest[i])) { if ((i + 1) >= MAX_CERT_EXTENSIONS) { ret = MAX_CERT_EXTENSIONS_ERR; diff --git a/src/ssl.c b/src/ssl.c index efbbf3074e9..2669f37aa18 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -14121,7 +14121,7 @@ int wolfSSL_get_chain_count(WOLFSSL_X509_CHAIN* chain) int wolfSSL_get_chain_length(WOLFSSL_X509_CHAIN* chain, int idx) { WOLFSSL_ENTER("wolfSSL_get_chain_length"); - if (chain) + if (chain && idx >= 0 && idx < chain->count) return chain->certs[idx].length; return 0; @@ -14132,7 +14132,7 @@ int wolfSSL_get_chain_length(WOLFSSL_X509_CHAIN* chain, int idx) byte* wolfSSL_get_chain_cert(WOLFSSL_X509_CHAIN* chain, int idx) { WOLFSSL_ENTER("wolfSSL_get_chain_cert"); - if (chain) + if (chain && idx >= 0 && idx < chain->count) return chain->certs[idx].buffer; return 0; @@ -14147,7 +14147,7 @@ WOLFSSL_X509* wolfSSL_get_chain_X509(WOLFSSL_X509_CHAIN* chain, int idx) WC_DECLARE_VAR(cert, DecodedCert, 1, 0); WOLFSSL_ENTER("wolfSSL_get_chain_X509"); - if (chain != NULL && idx < MAX_CHAIN_DEPTH) { + if (chain != NULL && idx >= 0 && idx < chain->count) { #ifdef WOLFSSL_SMALL_STACK cert = (DecodedCert*)XMALLOC(sizeof(DecodedCert), NULL, DYNAMIC_TYPE_DCERT); diff --git a/src/ssl_load.c b/src/ssl_load.c index 888c5783349..494f290e3e9 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -325,6 +325,13 @@ static int ProcessUserChain(WOLFSSL_CTX* ctx, WOLFSSL* ssl, while ((ret == 0) && (consumed < sz)) { DerBuffer* part = NULL; + /* Enforce maximum chain depth. */ + if (cnt >= MAX_CHAIN_DEPTH) { + WOLFSSL_MSG("Chain depth limit reached"); + ret = MAX_CHAIN_ERROR; + break; + } + /* Get a certificate as DER. */ ret = DataToDerBuffer(buff + consumed, (word32)(sz - consumed), format, type, info, heap, &part, NULL); diff --git a/src/tls.c b/src/tls.c index 62118d0678b..53d2da73144 100644 --- a/src/tls.c +++ b/src/tls.c @@ -2826,6 +2826,9 @@ int TLSX_SNI_GetFromBuffer(const byte* clientHello, word32 helloSz, ato16(clientHello + offset, &listLen); offset += OPAQUE16_LEN; + if (listLen != extLen - OPAQUE16_LEN) + return BUFFER_ERROR; + if (helloSz < offset + listLen) return BUFFER_ERROR; @@ -2836,6 +2839,9 @@ int TLSX_SNI_GetFromBuffer(const byte* clientHello, word32 helloSz, ato16(clientHello + offset, &sniLen); offset += OPAQUE16_LEN; + if (sniLen > listLen - (ENUM_LEN + OPAQUE16_LEN)) + return BUFFER_ERROR; + if (helloSz < offset + sniLen) return BUFFER_ERROR; @@ -3393,7 +3399,7 @@ static void TLSX_CSR_Free(CertificateStatusRequest* csr, void* heap) switch (csr->status_type) { case WOLFSSL_CSR_OCSP: - for (i = 0; i <= csr->requests; i++) { + for (i = 0; i < csr->requests; i++) { FreeOcspRequest(&csr->request.ocsp[i]); } break; diff --git a/src/tls13.c b/src/tls13.c index 8d7efb9df40..3925e67b8f0 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -9520,6 +9520,10 @@ static int SendTls13Certificate(WOLFSSL* ssl) } /* Certificate Data */ certSz = ssl->buffers.certificate->length; + if (ssl->buffers.certChainCnt > MAX_CHAIN_DEPTH) { + WOLFSSL_MSG("Certificate chain count exceeds maximum depth"); + return MAX_CHAIN_ERROR; + } /* Cert Req Ctx Len | Cert Req Ctx | Cert List Len | Cert Data Len */ headerSz = OPAQUE8_LEN + certReqCtxLen + CERT_HEADER_SZ + CERT_HEADER_SZ; diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index a4091890fd8..bb42fca82d7 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -14968,6 +14968,12 @@ int wc_PKCS7_DecodeAuthEnvelopedData(wc_PKCS7* pkcs7, byte* in, } } + if (ret == 0 && encryptedContentSz > (int)(pkiMsgSz - idx)) { + #ifdef NO_PKCS7_STREAM + ret = BUFFER_E; + #endif + } + if (ret < 0) break; @@ -15230,6 +15236,12 @@ int wc_PKCS7_DecodeAuthEnvelopedData(wc_PKCS7* pkcs7, byte* in, #endif idx = localIdx; + #ifdef NO_PKCS7_STREAM + if (ret == 0 && authTagSz > (word32)(pkiMsgSz - idx)) { + ret = BUFFER_E; + } + #endif + if (ret == 0 && authTagSz > (word32)sizeof(authTag)) { WOLFSSL_MSG("AuthEnvelopedData authTag too large for buffer"); ret = ASN_PARSE_E; From eb180dbee2b3cdcb4c33917dbd2b5b2babed9393 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Tue, 12 May 2026 11:43:12 -0700 Subject: [PATCH 2/6] Add test cases for max chain depth fixes --- tests/api.c | 85 ++++++++++++++++++++++++++++++++++++++++++ tests/api/test_pkcs7.c | 59 +++++++++++++++++++++++++++++ tests/api/test_pkcs7.h | 4 +- 3 files changed, 147 insertions(+), 1 deletion(-) diff --git a/tests/api.c b/tests/api.c index fe92bdee85e..58a97812390 100644 --- a/tests/api.c +++ b/tests/api.c @@ -3828,6 +3828,89 @@ static int test_wolfSSL_CTX_use_certificate_chain_buffer_format(void) return EXPECT_RESULT(); } +/* wolfSSL_get_chain_{length,cert,X509} must reject out-of-range idx. */ +static int test_wolfSSL_get_chain_idx_bounds(void) +{ + EXPECT_DECLS; +#if defined(SESSION_CERTS) && \ + defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) + struct test_memio_ctx test_ctx; + WOLFSSL_CTX* ctx_c = NULL; + WOLFSSL_CTX* ctx_s = NULL; + WOLFSSL* ssl_c = NULL; + WOLFSSL* ssl_s = NULL; + WOLFSSL_X509_CHAIN* chain = NULL; + int count; + + XMEMSET(&test_ctx, 0, sizeof(test_ctx)); + ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, + wolfTLS_client_method, wolfTLS_server_method), 0); + ExpectIntEQ(test_memio_do_handshake(ssl_c, ssl_s, 10, NULL), 0); + + ExpectNotNull(chain = wolfSSL_get_peer_chain(ssl_c)); + ExpectIntGT(count = wolfSSL_get_chain_count(chain), 0); + + ExpectIntEQ(wolfSSL_get_chain_length(chain, -1), 0); + ExpectIntEQ(wolfSSL_get_chain_length(chain, count), 0); + ExpectIntEQ(wolfSSL_get_chain_length(chain, MAX_CHAIN_DEPTH), 0); + ExpectNull(wolfSSL_get_chain_cert(chain, -1)); + ExpectNull(wolfSSL_get_chain_cert(chain, count)); + ExpectNull(wolfSSL_get_chain_cert(chain, MAX_CHAIN_DEPTH)); +#ifdef OPENSSL_EXTRA + { + WOLFSSL_X509* x = NULL; + ExpectNull(x = wolfSSL_get_chain_X509(chain, -1)); + if (x != NULL) { wolfSSL_X509_free(x); x = NULL; } + ExpectNull(x = wolfSSL_get_chain_X509(chain, count)); + if (x != NULL) { wolfSSL_X509_free(x); x = NULL; } + ExpectNull(x = wolfSSL_get_chain_X509(chain, MAX_CHAIN_DEPTH)); + if (x != NULL) { wolfSSL_X509_free(x); x = NULL; } + } +#endif + + wolfSSL_free(ssl_c); + wolfSSL_free(ssl_s); + wolfSSL_CTX_free(ctx_c); + wolfSSL_CTX_free(ctx_s); +#endif + return EXPECT_RESULT(); +} + +/* Reject chain buffers containing more than MAX_CHAIN_DEPTH certificates. */ +static int test_wolfSSL_CTX_use_certificate_chain_buffer_max_depth(void) +{ + EXPECT_DECLS; +#if !defined(NO_FILESYSTEM) && !defined(NO_CERTS) && !defined(NO_TLS) && \ + !defined(NO_WOLFSSL_CLIENT) && !defined(NO_RSA) && \ + defined(WOLFSSL_PEM_TO_DER) + WOLFSSL_CTX* ctx = NULL; + unsigned char* one = NULL; + unsigned char* big = NULL; + size_t oneLen = 0; + size_t bigLen; + int i; + const int nCerts = MAX_CHAIN_DEPTH + 1; + + ExpectIntEQ(load_file(svrCertFile, &one, &oneLen), 0); + bigLen = oneLen * (size_t)nCerts; + ExpectNotNull(big = (unsigned char*)XMALLOC(bigLen, NULL, + DYNAMIC_TYPE_TMP_BUFFER)); + for (i = 0; EXPECT_SUCCESS() && i < nCerts; i++) + XMEMCPY(big + (size_t)i * oneLen, one, oneLen); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_buffer(ctx, big, + (long)bigLen), WC_NO_ERR_TRACE(MAX_CHAIN_ERROR)); + + wolfSSL_CTX_free(ctx); + if (big != NULL) + XFREE(big, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (one != NULL) + XFREE(one, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + static int test_wolfSSL_CTX_use_certificate_chain_file_format(void) { EXPECT_DECLS; @@ -40648,6 +40731,8 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_CTX_add1_chain_cert), TEST_DECL(test_wolfSSL_add_to_chain_overflow), TEST_DECL(test_wolfSSL_CTX_use_certificate_chain_buffer_format), + TEST_DECL(test_wolfSSL_CTX_use_certificate_chain_buffer_max_depth), + TEST_DECL(test_wolfSSL_get_chain_idx_bounds), TEST_DECL(test_wolfSSL_CTX_use_certificate_chain_file_format), TEST_DECL(test_wolfSSL_use_certificate_chain_file), TEST_DECL(test_wolfSSL_CTX_trust_peer_cert), diff --git a/tests/api/test_pkcs7.c b/tests/api/test_pkcs7.c index 4aae3382ce9..b86262f1121 100644 --- a/tests/api/test_pkcs7.c +++ b/tests/api/test_pkcs7.c @@ -2873,6 +2873,65 @@ int test_wc_PKCS7_DecodeEnvelopedData_forgedRecipientSetLen(void) } /* END test_wc_PKCS7_DecodeEnvelopedData_forgedRecipientSetLen() */ +/* Decoding an AuthEnvelopedData blob whose encryptedContent or authTag + * is truncated must return BUFFER_E rather than reading past pkiMsg. */ +int test_wc_PKCS7_DecodeAuthEnvelopedData_truncated(void) +{ + EXPECT_DECLS; +#if defined(HAVE_PKCS7) && defined(HAVE_AESGCM) && !defined(NO_RSA) && \ + !defined(NO_AES) && defined(WOLFSSL_AES_128) && defined(NO_PKCS7_STREAM) + PKCS7* pkcs7 = NULL; + byte enveloped[2048]; + byte decoded[256]; + byte data[] = "truncated authEnvelopedData test"; + int encSz = 0; + + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, (byte*)client_cert_der_2048, + sizeof_client_cert_der_2048), 0); + if (pkcs7 != NULL) { + pkcs7->content = data; + pkcs7->contentSz = (word32)sizeof(data); + pkcs7->contentOID = DATA; + pkcs7->encryptOID = AES128GCMb; + } + ExpectIntGT(encSz = wc_PKCS7_EncodeAuthEnvelopedData(pkcs7, enveloped, + sizeof(enveloped)), 0); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* Truncate inside encryptedContent (encryptedContentSz check). */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, (byte*)client_cert_der_2048, + sizeof_client_cert_der_2048), 0); + if (pkcs7 != NULL) { + pkcs7->privateKey = (byte*)client_key_der_2048; + pkcs7->privateKeySz = sizeof_client_key_der_2048; + } + ExpectIntEQ(wc_PKCS7_DecodeAuthEnvelopedData(pkcs7, enveloped, + (word32)encSz - 32, decoded, sizeof(decoded)), + WC_NO_ERR_TRACE(BUFFER_E)); + wc_PKCS7_Free(pkcs7); + pkcs7 = NULL; + + /* Truncate one byte off the auth tag (authTagSz check). */ + ExpectNotNull(pkcs7 = wc_PKCS7_New(HEAP_HINT, testDevId)); + ExpectIntEQ(wc_PKCS7_InitWithCert(pkcs7, (byte*)client_cert_der_2048, + sizeof_client_cert_der_2048), 0); + if (pkcs7 != NULL) { + pkcs7->privateKey = (byte*)client_key_der_2048; + pkcs7->privateKeySz = sizeof_client_key_der_2048; + } + ExpectIntEQ(wc_PKCS7_DecodeAuthEnvelopedData(pkcs7, enveloped, + (word32)encSz - 1, decoded, sizeof(decoded)), + WC_NO_ERR_TRACE(BUFFER_E)); + + wc_PKCS7_Free(pkcs7); +#endif + return EXPECT_RESULT(); +} /* END test_wc_PKCS7_DecodeAuthEnvelopedData_truncated() */ + + /* * Testing wc_PKCS7_DecodeEnvelopedData with streaming */ diff --git a/tests/api/test_pkcs7.h b/tests/api/test_pkcs7.h index 5e3f3939fb8..1dfd7f56263 100644 --- a/tests/api/test_pkcs7.h +++ b/tests/api/test_pkcs7.h @@ -68,6 +68,7 @@ int test_wc_PKCS7_SetOriDecryptCtx(void); int test_wc_PKCS7_DecodeCompressedData(void); int test_wc_PKCS7_DecodeEnvelopedData_multiple_recipients(void); int test_wc_PKCS7_DecodeEnvelopedData_forgedRecipientSetLen(void); +int test_wc_PKCS7_DecodeAuthEnvelopedData_truncated(void); int test_wc_PKCS7_VerifySignedData_PKCS7ContentSeq(void); int test_wc_PKCS7_VerifySignedData_IndefLenOOB(void); int test_wc_PKCS7_VerifySignedData_TruncEContentTag(void); @@ -139,7 +140,8 @@ int test_wc_PKCS7_VerifySignedData_TruncCertSetTag(void); TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_SetOriEncryptCtx), \ TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_SetOriDecryptCtx), \ TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_multiple_recipients), \ - TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_forgedRecipientSetLen) + TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeEnvelopedData_forgedRecipientSetLen), \ + TEST_DECL_GROUP("pkcs7_ed", test_wc_PKCS7_DecodeAuthEnvelopedData_truncated) #define TEST_PKCS7_SIGNED_ENCRYPTED_DATA_DECLS \ TEST_DECL_GROUP("pkcs7_sed", test_wc_PKCS7_signed_enveloped) From 359fb7ecbdfdd0a79c381ee459080738ea92f389 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Wed, 27 May 2026 14:11:46 -0700 Subject: [PATCH 3/6] Remove DTLS oversized cert chain test superseded by load-time depth cap test_dtls13_oversized_cert_chain (added upstream in 1653ecd07) loaded a >9-cert chain to exercise SendTls13Certificate's word16 overflow guard. This PR now rejects over-MAX_CHAIN_DEPTH chains at load in ProcessUserChain, so the chain never reaches the send path; the load-time rejection is covered by test_wolfSSL_CTX_use_certificate_chain_buffer_max_depth. The word16 send guard remains as defense-in-depth (now only reachable via large PQC chains). --- tests/api/test_dtls.c | 95 ------------------------------------------- tests/api/test_dtls.h | 2 - 2 files changed, 97 deletions(-) diff --git a/tests/api/test_dtls.c b/tests/api/test_dtls.c index 336f89257f9..d72a9395c59 100644 --- a/tests/api/test_dtls.c +++ b/tests/api/test_dtls.c @@ -3158,101 +3158,6 @@ int test_dtls13_5_9_0_compat(void) return EXPECT_RESULT(); } -/* Test that a DTLS 1.3 handshake with an oversized certificate chain does - * not crash or cause out-of-bounds access in SendTls13Certificate. */ -int test_dtls13_oversized_cert_chain(void) -{ - EXPECT_DECLS; -#if defined(HAVE_MANUAL_MEMIO_TESTS_DEPENDENCIES) && defined(WOLFSSL_DTLS13) \ - && !defined(NO_FILESYSTEM) && !defined(NO_RSA) - WOLFSSL_CTX *ctx_c = NULL, *ctx_s = NULL; - WOLFSSL *ssl_c = NULL, *ssl_s = NULL; - struct test_memio_ctx test_ctx; - XFILE f = XBADFILE; - long sz = 0; - byte *cert = NULL; - byte *chain = NULL; - int copies, off, i; - - XMEMSET(&test_ctx, 0, sizeof(test_ctx)); - - /* Read server cert */ - f = XFOPEN(svrCertFile, "rb"); - ExpectTrue(f != XBADFILE); - if (EXPECT_SUCCESS()) { - (void)XFSEEK(f, 0, XSEEK_END); - sz = XFTELL(f); - (void)XFSEEK(f, 0, XSEEK_SET); - } - ExpectTrue(sz > 0); - cert = (byte*)XMALLOC((size_t)(sz + 1), NULL, DYNAMIC_TYPE_TMP_BUFFER); - ExpectNotNull(cert); - if (EXPECT_SUCCESS()) - ExpectIntEQ((int)XFREAD(cert, 1, (size_t)sz, f), (int)sz); - if (f != XBADFILE) - XFCLOSE(f); - - /* Build an oversized chain by duplicating the cert */ - copies = EXPECT_SUCCESS() ? (int)(70000 / sz) + 2 : 0; - chain = (byte*)XMALLOC((size_t)(sz * copies + 1), NULL, - DYNAMIC_TYPE_TMP_BUFFER); - ExpectNotNull(chain); - off = 0; - if (EXPECT_SUCCESS()) { - for (i = 0; i < copies; i++) { - XMEMCPY(chain + off, cert, (size_t)sz); - off += (int)sz; - } - } - - /* Server context: load the oversized chain */ - ExpectNotNull(ctx_s = wolfSSL_CTX_new(wolfDTLSv1_3_server_method())); - ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_buffer(ctx_s, - chain, (long)off), WOLFSSL_SUCCESS); - ExpectIntEQ(wolfSSL_CTX_use_PrivateKey_file(ctx_s, svrKeyFile, - WOLFSSL_FILETYPE_PEM), WOLFSSL_SUCCESS); - if (EXPECT_SUCCESS()) { - wolfSSL_SetIORecv(ctx_s, test_memio_read_cb); - wolfSSL_SetIOSend(ctx_s, test_memio_write_cb); - } - - /* Client context: no verification (chain certs are duplicates) */ - ExpectNotNull(ctx_c = wolfSSL_CTX_new(wolfDTLSv1_3_client_method())); - if (EXPECT_SUCCESS()) { - wolfSSL_CTX_set_verify(ctx_c, WOLFSSL_VERIFY_NONE, NULL); - wolfSSL_SetIORecv(ctx_c, test_memio_read_cb); - wolfSSL_SetIOSend(ctx_c, test_memio_write_cb); - } - - ExpectNotNull(ssl_s = wolfSSL_new(ctx_s)); - if (EXPECT_SUCCESS()) { - wolfSSL_SetIOWriteCtx(ssl_s, &test_ctx); - wolfSSL_SetIOReadCtx(ssl_s, &test_ctx); - } - - ExpectNotNull(ssl_c = wolfSSL_new(ctx_c)); - if (EXPECT_SUCCESS()) { - wolfSSL_SetIOWriteCtx(ssl_c, &test_ctx); - wolfSSL_SetIOReadCtx(ssl_c, &test_ctx); - } - - /* Handshake must not crash. If SendTls13Certificate mishandles the - * oversized chain this will trigger a wild pointer dereference or stack - * overflow resulting with the test failing. - * The correct behaviour either returns BUFFER_E or succeeds - * if the build config truncated the chain during loading. */ - (void)test_memio_do_handshake(ssl_c, ssl_s, 10, NULL); - - wolfSSL_free(ssl_c); - wolfSSL_free(ssl_s); - wolfSSL_CTX_free(ctx_c); - wolfSSL_CTX_free(ctx_s); - XFREE(cert, NULL, DYNAMIC_TYPE_TMP_BUFFER); - XFREE(chain, NULL, DYNAMIC_TYPE_TMP_BUFFER); -#endif - return EXPECT_RESULT(); -} - /* DTLS counterpart to test_tls_set_session_min_downgrade. Exercises the * inverted DTLS minor-version comparison (DTLS 1.2 minor 0xFD is "below" * floor 0xFC = DTLS 1.3). */ diff --git a/tests/api/test_dtls.h b/tests/api/test_dtls.h index 96e524b5372..823b9b9f457 100644 --- a/tests/api/test_dtls.h +++ b/tests/api/test_dtls.h @@ -56,7 +56,6 @@ int test_dtls_mtu_split_messages(void); int test_dtls13_min_rtx_interval(void); int test_dtls13_no_session_id_echo(void); int test_dtls13_5_9_0_compat(void); -int test_dtls13_oversized_cert_chain(void); int test_dtls_set_session_min_downgrade(void); #define TEST_DTLS_DECLS \ @@ -93,7 +92,6 @@ int test_dtls_set_session_min_downgrade(void); TEST_DECL_GROUP("dtls", test_dtls_memio_wolfio_stateless), \ TEST_DECL_GROUP("dtls", test_dtls13_min_rtx_interval), \ TEST_DECL_GROUP("dtls", test_dtls13_no_session_id_echo), \ - TEST_DECL_GROUP("dtls", test_dtls13_oversized_cert_chain), \ TEST_DECL_GROUP("dtls", test_dtls_set_session_min_downgrade), \ TEST_DECL_GROUP("dtls", test_dtls13_5_9_0_compat) #endif /* TESTS_API_DTLS_H */ From 0ca238c50e35fc3fc2de6b697b1c83c7470c1d50 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Tue, 2 Jun 2026 16:39:26 -0700 Subject: [PATCH 4/6] Scope PKCS7 truncation check to NO_PKCS7_STREAM and guard test underflow - pkcs7.c: wrap the AuthEnvelopedData encryptedContentSz bounds check entirely in #ifdef NO_PKCS7_STREAM so the default streaming build carries no empty/no-op branch (behavior unchanged). - test_pkcs7.c: assert the encoded size is >32 so the encSz-32 and encSz-1 truncations can't underflow. --- tests/api/test_pkcs7.c | 3 ++- wolfcrypt/src/pkcs7.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/api/test_pkcs7.c b/tests/api/test_pkcs7.c index b86262f1121..810573a6d1b 100644 --- a/tests/api/test_pkcs7.c +++ b/tests/api/test_pkcs7.c @@ -2895,8 +2895,9 @@ int test_wc_PKCS7_DecodeAuthEnvelopedData_truncated(void) pkcs7->contentOID = DATA; pkcs7->encryptOID = AES128GCMb; } + /* >32 so the encSz-32 / encSz-1 truncations below can't underflow */ ExpectIntGT(encSz = wc_PKCS7_EncodeAuthEnvelopedData(pkcs7, enveloped, - sizeof(enveloped)), 0); + sizeof(enveloped)), 32); wc_PKCS7_Free(pkcs7); pkcs7 = NULL; diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index bb42fca82d7..5ab3c54e346 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -14968,11 +14968,11 @@ int wc_PKCS7_DecodeAuthEnvelopedData(wc_PKCS7* pkcs7, byte* in, } } + #ifdef NO_PKCS7_STREAM if (ret == 0 && encryptedContentSz > (int)(pkiMsgSz - idx)) { - #ifdef NO_PKCS7_STREAM ret = BUFFER_E; - #endif } + #endif if (ret < 0) break; From ed11ac717c651b467a7cdada6b1b189ac503f0f5 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Mon, 8 Jun 2026 10:48:53 -0700 Subject: [PATCH 5/6] Fix maybe-uninitialized count in test_wolfSSL_get_chain_idx_bounds --- tests/api.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/api.c b/tests/api.c index beb1d1b0f51..1723e605fc8 100644 --- a/tests/api.c +++ b/tests/api.c @@ -3843,7 +3843,7 @@ static int test_wolfSSL_get_chain_idx_bounds(void) WOLFSSL* ssl_c = NULL; WOLFSSL* ssl_s = NULL; WOLFSSL_X509_CHAIN* chain = NULL; - int count; + int count = 0; XMEMSET(&test_ctx, 0, sizeof(test_ctx)); ExpectIntEQ(test_memio_setup(&test_ctx, &ctx_c, &ctx_s, &ssl_c, &ssl_s, From 1cdf37f321df98746839d660f82c6ad5866cdf68 Mon Sep 17 00:00:00 2001 From: Colton Willey Date: Mon, 8 Jun 2026 17:27:26 -0700 Subject: [PATCH 6/6] Address review feedback on chain-depth and parser hardening - ProcessUserChain: enforce MAX_CHAIN_DEPTH after a cert is parsed, so a full-depth chain followed by trailing data still loads instead of failing with MAX_CHAIN_ERROR. - SendCertificateStatus: error on an over-long OCSP chain instead of silently truncating the stapled response; keep the chainOcspRequest[] index bound. - wc_PKCS7_DecodeAuthEnvelopedData: bounds-check the authTag tag read under NO_PKCS7_STREAM. - Tests: chain-depth test now pins the boundary (exactly MAX_CHAIN_DEPTH loads, one more rejected). --- src/internal.c | 2 +- src/ssl_load.c | 14 ++++++------- tests/api.c | 47 ++++++++++++++++++++++++++++++------------- wolfcrypt/src/pkcs7.c | 6 ++++++ 4 files changed, 47 insertions(+), 22 deletions(-) diff --git a/src/internal.c b/src/internal.c index 4ca81cfcb2c..31fd94354d7 100644 --- a/src/internal.c +++ b/src/internal.c @@ -26238,7 +26238,7 @@ int SendCertificateStatus(WOLFSSL* ssl) } if (chain && chain->buffer) { - while (ret == 0 && i < MAX_CHAIN_DEPTH && + while (ret == 0 && idx + OPAQUE24_LEN < chain->length) { c24to32(chain->buffer + idx, &der.length); idx += OPAQUE24_LEN; diff --git a/src/ssl_load.c b/src/ssl_load.c index 494f290e3e9..b52eadf62ce 100644 --- a/src/ssl_load.c +++ b/src/ssl_load.c @@ -325,17 +325,17 @@ static int ProcessUserChain(WOLFSSL_CTX* ctx, WOLFSSL* ssl, while ((ret == 0) && (consumed < sz)) { DerBuffer* part = NULL; - /* Enforce maximum chain depth. */ - if (cnt >= MAX_CHAIN_DEPTH) { - WOLFSSL_MSG("Chain depth limit reached"); - ret = MAX_CHAIN_ERROR; - break; - } - /* Get a certificate as DER. */ ret = DataToDerBuffer(buff + consumed, (word32)(sz - consumed), format, type, info, heap, &part, NULL); if (ret == 0) { + /* Enforce maximum chain depth once a real cert is parsed. */ + if (cnt >= MAX_CHAIN_DEPTH) { + WOLFSSL_MSG("Chain depth limit reached"); + FreeDer(&part); + ret = MAX_CHAIN_ERROR; + break; + } /* Process the user certificate. */ ret = ProcessUserCert(ctx->cm, &part, type, verify, chain.buffer, &idx, (word32)maxSz); diff --git a/tests/api.c b/tests/api.c index 1723e605fc8..7757f53fc2a 100644 --- a/tests/api.c +++ b/tests/api.c @@ -3879,7 +3879,9 @@ static int test_wolfSSL_get_chain_idx_bounds(void) return EXPECT_RESULT(); } -/* Reject chain buffers containing more than MAX_CHAIN_DEPTH certificates. */ +/* Chain-depth boundary: exactly MAX_CHAIN_DEPTH chain certs (plus trailing data + * that forces one more parse pass) must load; one more cert must be rejected. + * cliCertFile is single-cert, so copy 0 is the leaf and N copies => N-1 chain. */ static int test_wolfSSL_CTX_use_certificate_chain_buffer_max_depth(void) { EXPECT_DECLS; @@ -3888,26 +3890,43 @@ static int test_wolfSSL_CTX_use_certificate_chain_buffer_max_depth(void) defined(WOLFSSL_PEM_TO_DER) WOLFSSL_CTX* ctx = NULL; unsigned char* one = NULL; - unsigned char* big = NULL; + unsigned char* buf = NULL; size_t oneLen = 0; - size_t bigLen; + const char* tail = "# trailing comment\n"; + size_t tailLen = XSTRLEN(tail); + /* +1 copy for the leaf yields exactly MAX_CHAIN_DEPTH chain certs. */ + const int atMax = MAX_CHAIN_DEPTH + 1; int i; - const int nCerts = MAX_CHAIN_DEPTH + 1; - ExpectIntEQ(load_file(svrCertFile, &one, &oneLen), 0); - bigLen = oneLen * (size_t)nCerts; - ExpectNotNull(big = (unsigned char*)XMALLOC(bigLen, NULL, - DYNAMIC_TYPE_TMP_BUFFER)); - for (i = 0; EXPECT_SUCCESS() && i < nCerts; i++) - XMEMCPY(big + (size_t)i * oneLen, one, oneLen); + ExpectIntEQ(load_file(cliCertFile, &one, &oneLen), 0); + /* Exactly MAX_CHAIN_DEPTH chain certs + trailing data: loads. */ + ExpectNotNull(buf = (unsigned char*)XMALLOC( + oneLen * (size_t)atMax + tailLen, NULL, DYNAMIC_TYPE_TMP_BUFFER)); + for (i = 0; EXPECT_SUCCESS() && i < atMax; i++) + XMEMCPY(buf + (size_t)i * oneLen, one, oneLen); + if (buf != NULL) + XMEMCPY(buf + (size_t)atMax * oneLen, tail, tailLen); ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); - ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_buffer(ctx, big, - (long)bigLen), WC_NO_ERR_TRACE(MAX_CHAIN_ERROR)); + ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_buffer(ctx, buf, + (long)(oneLen * (size_t)atMax + tailLen)), WOLFSSL_SUCCESS); + wolfSSL_CTX_free(ctx); + ctx = NULL; + if (buf != NULL) + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + buf = NULL; + /* One more chain cert: rejected. */ + ExpectNotNull(buf = (unsigned char*)XMALLOC(oneLen * (size_t)(atMax + 1), + NULL, DYNAMIC_TYPE_TMP_BUFFER)); + for (i = 0; EXPECT_SUCCESS() && i < atMax + 1; i++) + XMEMCPY(buf + (size_t)i * oneLen, one, oneLen); + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectIntEQ(wolfSSL_CTX_use_certificate_chain_buffer(ctx, buf, + (long)(oneLen * (size_t)(atMax + 1))), WC_NO_ERR_TRACE(MAX_CHAIN_ERROR)); wolfSSL_CTX_free(ctx); - if (big != NULL) - XFREE(big, NULL, DYNAMIC_TYPE_TMP_BUFFER); + if (buf != NULL) + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (one != NULL) XFREE(one, NULL, DYNAMIC_TYPE_TMP_BUFFER); #endif diff --git a/wolfcrypt/src/pkcs7.c b/wolfcrypt/src/pkcs7.c index 5ab3c54e346..df6ede4296e 100644 --- a/wolfcrypt/src/pkcs7.c +++ b/wolfcrypt/src/pkcs7.c @@ -15197,6 +15197,12 @@ int wc_PKCS7_DecodeAuthEnvelopedData(wc_PKCS7* pkcs7, byte* in, localIdx = idx; + #ifdef NO_PKCS7_STREAM + if (ret == 0 && localIdx >= pkiMsgSz) { + ret = BUFFER_E; + } + #endif + /* Get authTag OCTET STRING */ if (ret == 0 && pkiMsg[localIdx] != ASN_OCTET_STRING) { ret = ASN_PARSE_E;