From 46edfa8997a15d617a03385485f593317a97707e Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:16:52 -0700 Subject: [PATCH 01/22] F-6279 - Pin membrowse-action to commit SHA and drop persisted creds in zephyr report workflow --- .github/workflows/membrowse-zephyr.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/membrowse-zephyr.yml b/.github/workflows/membrowse-zephyr.yml index 31e45d819dd..84e3bd4ff7a 100644 --- a/.github/workflows/membrowse-zephyr.yml +++ b/.github/workflows/membrowse-zephyr.yml @@ -45,6 +45,7 @@ jobs: with: ref: ${{ github.event.workflow_run.head_sha }} fetch-depth: 2 + persist-credentials: false - name: Download Zephyr build artifact id: download @@ -77,7 +78,10 @@ jobs: - name: Run Membrowse PR Action if: steps.verify.outputs.have_artifacts == 'true' - uses: membrowse/membrowse-action@v1 + # Pinned to a full commit SHA: this job downloads fork-controlled + # artifacts and holds MEMBROWSE_API_KEY, so a moved tag must not be + # able to swap the action out from under it. v1.2.9. + uses: membrowse/membrowse-action@3f23ea562abaa6614855d6be5ec83b3cd1d274dd with: target_name: ${{ matrix.target_name }} elf: zephyr-artifacts/${{ matrix.target_name }}/zephyr.elf @@ -106,7 +110,8 @@ jobs: - name: Post Membrowse PR comment if: ${{ env.MEMBROWSE_API_KEY != '' }} - uses: membrowse/membrowse-action/comment-action@v1 + # Pinned to a full commit SHA, same rationale as the analyze job. v1.2.9. + uses: membrowse/membrowse-action/comment-action@3f23ea562abaa6614855d6be5ec83b3cd1d274dd with: api_key: ${{ secrets.MEMBROWSE_API_KEY }} commit: ${{ github.event.workflow_run.head_sha }} From 7ca965ab9f972e79a939d7bfb8bc8b8fc30bb0ee Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:26:35 -0700 Subject: [PATCH 02/22] F-5246 - Check IPv6 extension header fits before dereference in sniffer --- src/sniffer.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/sniffer.c b/src/sniffer.c index 256e9b24fca..b2a2bd04ef8 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -2138,7 +2138,13 @@ static int CheckIp6Hdr(Ip6Hdr* iphdr, IpInfo* info, int length, char* error) if (iphdr->next_header != TCP_PROTOCOL) { Ip6ExtHdr* exthdr = (Ip6ExtHdr*)((byte*)iphdr + IP6_HDR_SZ); do { - int hdrsz = (exthdr->length + 1) * 8; + int hdrsz; + /* make sure the extension header is fully present before reading */ + if (length - exthdrsz < (int)sizeof(Ip6ExtHdr)) { + SetError(PACKET_HDR_SHORT_STR, error, NULL, 0); + return WOLFSSL_FATAL_ERROR; + } + hdrsz = (exthdr->length + 1) * 8; if (hdrsz > length - exthdrsz) { SetError(PACKET_HDR_SHORT_STR, error, NULL, 0); return WOLFSSL_FATAL_ERROR; From 5572db7f3bd1a84cffac54a002c72a1d520e6069 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:26:42 -0700 Subject: [PATCH 03/22] F-6302 - Bound ServerHello session ID copy by remaining sniffer input --- src/sniffer.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sniffer.c b/src/sniffer.c index b2a2bd04ef8..bb4895ead37 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -3739,6 +3739,10 @@ static int ProcessServerHello(int msgSz, const byte* input, int* sslBytes, return WOLFSSL_FATAL_ERROR; } if (b) { + if (ID_LEN > *sslBytes) { + SetError(SERVER_HELLO_INPUT_STR, error, session, FATAL_ERROR_STATE); + return WOLFSSL_FATAL_ERROR; + } #ifdef WOLFSSL_TLS13 XMEMCPY(session->sslServer->session->sessionID, input, ID_LEN); session->sslServer->session->sessionIDSz = ID_LEN; From 8e87bcbc3ed32544b97227d813ce13b7187694e8 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:26:49 -0700 Subject: [PATCH 04/22] F-5728 - Guard ServerHello extension header read and length underflow in sniffer --- src/sniffer.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/sniffer.c b/src/sniffer.c index bb4895ead37..c1c88d9c7aa 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -3814,6 +3814,13 @@ static int ProcessServerHello(int msgSz, const byte* input, int* sslBytes, word16 extType; word16 extLen; + /* make sure can read extension type and length */ + if (*sslBytes < EXT_TYPE_SZ + LENGTH_SZ) { + SetError(SERVER_HELLO_INPUT_STR, error, session, + FATAL_ERROR_STATE); + return WOLFSSL_FATAL_ERROR; + } + extType = (word16)((input[0] << 8) | input[1]); input += EXT_TYPE_SZ; *sslBytes -= EXT_TYPE_SZ; @@ -3905,6 +3912,13 @@ static int ProcessServerHello(int msgSz, const byte* input, int* sslBytes, break; } /* switch (extType) */ + /* make sure the extension fits in the remaining declared length */ + if ((word16)(extLen + EXT_TYPE_SZ + LENGTH_SZ) > len) { + SetError(SERVER_HELLO_INPUT_STR, error, session, + FATAL_ERROR_STATE); + return WOLFSSL_FATAL_ERROR; + } + input += extLen; *sslBytes -= extLen; len -= extLen + EXT_TYPE_SZ + LENGTH_SZ; From 0b0df686c3465a73ee644c6fbe316d7f9a090ade Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:26:54 -0700 Subject: [PATCH 05/22] F-4809 - Guard ClientHello extension header read and length underflow in sniffer --- src/sniffer.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sniffer.c b/src/sniffer.c index c1c88d9c7aa..26dc257977b 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -4199,6 +4199,12 @@ static int ProcessClientHello(const byte* input, int* sslBytes, word16 extType; word16 extLen; + /* make sure can read extension type and length */ + if (*sslBytes < EXT_TYPE_SZ + LENGTH_SZ) { + SetError(CLIENT_HELLO_INPUT_STR, error, session, FATAL_ERROR_STATE); + return WOLFSSL_FATAL_ERROR; + } + extType = (word16)((input[0] << 8) | input[1]); input += EXT_TYPE_SZ; *sslBytes -= EXT_TYPE_SZ; @@ -4386,6 +4392,12 @@ static int ProcessClientHello(const byte* input, int* sslBytes, break; } + /* make sure the extension fits in the remaining declared length */ + if ((word16)(extLen + EXT_TYPE_SZ + LENGTH_SZ) > len) { + SetError(CLIENT_HELLO_INPUT_STR, error, session, FATAL_ERROR_STATE); + return WOLFSSL_FATAL_ERROR; + } + input += extLen; *sslBytes -= extLen; len -= extLen + EXT_TYPE_SZ + LENGTH_SZ; From b3e90bef47b8bc21410af49a4ddbf6262b749447 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:27:05 -0700 Subject: [PATCH 06/22] F-5856 - Check QUIC early data length before narrowing to word32 --- src/quic.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/quic.c b/src/quic.c index d178f66bceb..5df8b29dc02 100644 --- a/src/quic.c +++ b/src/quic.c @@ -75,12 +75,12 @@ static QuicRecord *quic_record_make(WOLFSSL *ssl, XMEMSET(qr, 0, sizeof(*qr)); qr->level = level; if (level == wolfssl_encryption_early_data) { - qr->capacity = qr->len = (word32)len; - if (qr->capacity > WOLFSSL_QUIC_MAX_RECORD_CAPACITY) { + if (len > (size_t)WOLFSSL_QUIC_MAX_RECORD_CAPACITY) { WOLFSSL_MSG("QUIC early data length larger than expected"); quic_record_free(ssl, qr); return NULL; } + qr->capacity = qr->len = (word32)len; } else { qr->capacity = qr->len = (word32) qr_length(data, len); From 87863b1450c62f5cc717113abf86b8e64c5df579 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:27:05 -0700 Subject: [PATCH 07/22] F-5245 - Reset QUIC record length when XREALLOC fails to grow buffer --- src/quic.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/quic.c b/src/quic.c index 5df8b29dc02..fd20c165d1f 100644 --- a/src/quic.c +++ b/src/quic.c @@ -147,6 +147,9 @@ static int quic_record_append(WOLFSSL *ssl, QuicRecord *qr, const uint8_t *data, uint8_t *ndata = (uint8_t*)XREALLOC(qr->data, qr->len, ssl->heap, DYNAMIC_TYPE_TMP_BUFFER); if (!ndata) { + /* keep len consistent with the unchanged buffer so a later + * append does not copy into the smaller allocation */ + qr->len = 0; ret = WOLFSSL_FAILURE; goto cleanup; } From bfef0480fb6a6e4802c6507f4accd8df866a7aa6 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:29:34 -0700 Subject: [PATCH 08/22] F-5760 - Only free standalone BIOs in ssl_set_bio to avoid chained free UAF --- src/ssl.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ssl.c b/src/ssl.c index c2a5827c9dd..1365f6f78fe 100644 --- a/src/ssl.c +++ b/src/ssl.c @@ -6203,16 +6203,16 @@ int wolfSSL_set_compression(WOLFSSL* ssl) * a chain */ if ((flags & WOLFSSL_BIO_FLAG_READ) && (ssl->biord != NULL)) { if ((flags & WOLFSSL_BIO_FLAG_WRITE) && (ssl->biord != ssl->biowr)) { - if (ssl->biowr != NULL && ssl->biowr->prev != NULL) + if (ssl->biowr != NULL && ssl->biowr->prev == NULL) wolfSSL_BIO_free(ssl->biowr); ssl->biowr = NULL; } - if (ssl->biord->prev != NULL) + if (ssl->biord->prev == NULL) wolfSSL_BIO_free(ssl->biord); ssl->biord = NULL; } else if ((flags & WOLFSSL_BIO_FLAG_WRITE) && (ssl->biowr != NULL)) { - if (ssl->biowr->prev != NULL) + if (ssl->biowr->prev == NULL) wolfSSL_BIO_free(ssl->biowr); ssl->biowr = NULL; } From 9be0c4f01cc464a7a60b690c4f35ec7b86dd9456 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:29:34 -0700 Subject: [PATCH 09/22] F-5926 - Reject OCSP response missing nonce when forced nonce check enabled --- wolfcrypt/src/asn.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/wolfcrypt/src/asn.c b/wolfcrypt/src/asn.c index 7e855986f67..2f6db6d054a 100644 --- a/wolfcrypt/src/asn.c +++ b/wolfcrypt/src/asn.c @@ -35625,6 +35625,12 @@ int CompareOcspReqResp(OcspRequest* req, OcspResponse* resp) /* Nonces are not critical. The responder may not necessarily add * the nonce to the response. */ +#ifdef WOLFSSL_FORCE_OCSP_NONCE_CHECK + if (req->nonceSz && resp->nonce == NULL) { + WOLFSSL_MSG("\tnonce required but missing from response"); + return WOLFSSL_FATAL_ERROR; + } +#endif if (req->nonceSz && resp->nonce != NULL #ifndef WOLFSSL_FORCE_OCSP_NONCE_CHECK && resp->nonceSz != 0 From 600ccadf59244b6956c0ed2f97b019b4442f0d02 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:31:23 -0700 Subject: [PATCH 10/22] F-5247 - Guard pemApp_ReadFile reallocation size against word32 overflow --- examples/pem/pem.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/pem/pem.c b/examples/pem/pem.c index e23888558b1..853651ad459 100644 --- a/examples/pem/pem.c +++ b/examples/pem/pem.c @@ -112,13 +112,20 @@ static int pemApp_ReadFile(FILE* fp, unsigned char** pdata, word32* plen) /* Add read data amount to length. */ len += (word32)read_len; + /* Stop before the length or reallocation size can wrap word32. */ + if (len > (word32)(0xFFFFFFFFU - DATA_INC_LEN - BLOCK_SIZE_MAX)) { + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); + data = NULL; + break; + } + /* Stop if we are at end-of-file. */ if (feof(fp)) { break; } /* Make space for more data to be added to buffer. */ - p = (unsigned char*)XREALLOC(data, len + DATA_INC_LEN + + p = (unsigned char*)XREALLOC(data, (size_t)len + DATA_INC_LEN + BLOCK_SIZE_MAX, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (p == NULL) { /* Reallocation failed - free current buffer. */ From 4d629563ad7e6124c4b1e9721970374281ce574a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:31:23 -0700 Subject: [PATCH 11/22] F-5727 - Guard asn1App_ReadFile reallocation size against word32 overflow --- examples/asn1/asn1.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/asn1/asn1.c b/examples/asn1/asn1.c index 663d15a0b22..494e797671e 100644 --- a/examples/asn1/asn1.c +++ b/examples/asn1/asn1.c @@ -98,13 +98,20 @@ static int asn1App_ReadFile(FILE* fp, unsigned char** pdata, word32* plen) /* Add read data amount to length. */ len += (word32)read_len; + /* Stop before the length or reallocation size can wrap word32. */ + if (len > (word32)(0xFFFFFFFFU - DATA_INC_LEN)) { + XFREE(data, NULL, DYNAMIC_TYPE_TMP_BUFFER); + data = NULL; + break; + } + /* Stop if we are at end-of-file. */ if (feof(fp)) { break; } /* Make space for more data to be added to buffer. */ - p = (unsigned char*)XREALLOC(data, len + DATA_INC_LEN, NULL, + p = (unsigned char*)XREALLOC(data, (size_t)len + DATA_INC_LEN, NULL, DYNAMIC_TYPE_TMP_BUFFER); if (p == NULL) { /* Reallocation failed - free current buffer. */ From 8e7581ed1ac0b8535d66cd7582d958f43f24502f Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:36:22 -0700 Subject: [PATCH 12/22] F-5927 - Validate and clamp PKCS11 RSA public key attribute lengths --- wolfcrypt/src/wc_pkcs11.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/wolfcrypt/src/wc_pkcs11.c b/wolfcrypt/src/wc_pkcs11.c index 5a3b3cf5fee..c6c7a78fcd2 100644 --- a/wolfcrypt/src/wc_pkcs11.c +++ b/wolfcrypt/src/wc_pkcs11.c @@ -2513,6 +2513,13 @@ static int Pkcs11GetRsaPublicKey(RsaKey* key, Pkcs11Session* session, if (ret == 0) { modSz = (int)tmpl[0].ulValueLen; expSz = (int)tmpl[1].ulValueLen; + /* reject token lengths that do not fit in a positive int */ + if (modSz <= 0 || (CK_ULONG)modSz != tmpl[0].ulValueLen || + expSz <= 0 || (CK_ULONG)expSz != tmpl[1].ulValueLen) { + ret = WC_HW_E; + } + } + if (ret == 0) { mod = (unsigned char*)XMALLOC(modSz, key->heap, DYNAMIC_TYPE_TMP_BUFFER); if (mod == NULL) @@ -2526,7 +2533,9 @@ static int Pkcs11GetRsaPublicKey(RsaKey* key, Pkcs11Session* session, } if (ret == 0) { tmpl[0].pValue = mod; + tmpl[0].ulValueLen = (CK_ULONG)modSz; tmpl[1].pValue = exp; + tmpl[1].ulValueLen = (CK_ULONG)expSz; PKCS11_DUMP_TEMPLATE("Get RSA Public Key", tmpl, tmplCnt); rv = session->func->C_GetAttributeValue(session->handle, pubKey, From 41a14986384277dc294431a5db4b4740561bff41 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:36:22 -0700 Subject: [PATCH 13/22] F-5928 - Validate and clamp PKCS11 ECC public key attribute length --- wolfcrypt/src/wc_pkcs11.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wolfcrypt/src/wc_pkcs11.c b/wolfcrypt/src/wc_pkcs11.c index c6c7a78fcd2..695b94a9635 100644 --- a/wolfcrypt/src/wc_pkcs11.c +++ b/wolfcrypt/src/wc_pkcs11.c @@ -3281,12 +3281,19 @@ static int Pkcs11GetEccPublicKey(ecc_key* key, Pkcs11Session* session, if (ret == 0) { pointSz = (int)tmpl[0].ulValueLen; + /* reject a token length that does not fit in a positive int */ + if (pointSz <= 0 || (CK_ULONG)pointSz != tmpl[0].ulValueLen) { + ret = WC_HW_E; + } + } + if (ret == 0) { point = (unsigned char*)XMALLOC(pointSz, key->heap, DYNAMIC_TYPE_ECC); if (point == NULL) ret = MEMORY_E; } if (ret == 0) { tmpl[0].pValue = point; + tmpl[0].ulValueLen = (CK_ULONG)pointSz; PKCS11_DUMP_TEMPLATE("Get Ec Public Key", tmpl, tmplCnt); rv = session->func->C_GetAttributeValue(session->handle, pubKey, From 195b2a091a732cc666c08837029288f4651ea14a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:36:23 -0700 Subject: [PATCH 14/22] F-5929 - Validate and clamp PKCS11 certificate attribute length --- wolfcrypt/src/wc_pkcs11.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/wolfcrypt/src/wc_pkcs11.c b/wolfcrypt/src/wc_pkcs11.c index 695b94a9635..f146e2d8aa5 100644 --- a/wolfcrypt/src/wc_pkcs11.c +++ b/wolfcrypt/src/wc_pkcs11.c @@ -6239,6 +6239,7 @@ static int Pkcs11GetCert(Pkcs11Session* session, wc_CryptoInfo* info) { CK_ULONG count = 0; CK_OBJECT_HANDLE certHandle = CK_INVALID_HANDLE; byte *certData = NULL; + int certDataSz = 0; CK_ATTRIBUTE certTemplate[2] = { { CKA_CLASS, &certClass, sizeof(certClass) } }; @@ -6280,19 +6281,21 @@ static int Pkcs11GetCert(Pkcs11Session* session, wc_CryptoInfo* info) { goto exit; } - if (tmpl[0].ulValueLen <= 0) { + certDataSz = (int)tmpl[0].ulValueLen; + /* reject a token length that does not fit in a positive int */ + if (certDataSz <= 0 || (CK_ULONG)certDataSz != tmpl[0].ulValueLen) { ret = WC_HW_E; goto exit; } - certData = (byte *)XMALLOC( - (int)tmpl[0].ulValueLen, info->cert.heap, DYNAMIC_TYPE_CERT); + certData = (byte *)XMALLOC(certDataSz, info->cert.heap, DYNAMIC_TYPE_CERT); if (certData == NULL) { ret = MEMORY_E; goto exit; } tmpl[0].pValue = certData; + tmpl[0].ulValueLen = (CK_ULONG)certDataSz; rv = session->func->C_GetAttributeValue( session->handle, certHandle, tmpl, tmplCnt); PKCS11_RV("C_GetAttributeValue", rv); @@ -6302,7 +6305,7 @@ static int Pkcs11GetCert(Pkcs11Session* session, wc_CryptoInfo* info) { } *info->cert.certDataOut = certData; - *info->cert.certSz = (word32)tmpl[0].ulValueLen; + *info->cert.certSz = (word32)certDataSz; if (info->cert.certFormatOut != NULL) { *info->cert.certFormatOut = CTC_FILETYPE_ASN1; } From 6ed02f9a5004b41871082b0b881c5c98acd784b2 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:41:36 -0700 Subject: [PATCH 15/22] F-5858 - Grow decompress buffer geometrically to avoid quadratic copy DoS --- wolfcrypt/src/compress.c | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/wolfcrypt/src/compress.c b/wolfcrypt/src/compress.c index 63f58fbd4f7..cec5b7b8bba 100644 --- a/wolfcrypt/src/compress.c +++ b/wolfcrypt/src/compress.c @@ -213,6 +213,7 @@ int wc_DeCompressDynamic(byte** out, int maxSz, int memoryType, int result = 0; int i; word32 tmpSz = 0; + word32 maxBufSz; byte* tmp; (void)memoryType; @@ -221,14 +222,27 @@ int wc_DeCompressDynamic(byte** out, int maxSz, int memoryType, if (out == NULL || in == NULL) { return BAD_FUNC_ARG; } - /* Cap input so the initial doubling and additive growth in the loop - * cannot overflow word32 or the int return type. */ - if (inSz > (word32)(INT_MAX / 2)) { + /* Cap input so the initial doubling and the growth in the loop cannot + * overflow word32 or the int return type. Zero input has no buffer to + * size against. */ + if (inSz == 0 || inSz > (word32)(INT_MAX / 2)) { return BAD_FUNC_ARG; } i = (maxSz == 1)? 1 : 2; /* start with output buffer twice the size of input * unless max was set to 1 */ + /* Largest output buffer permitted: inSz * maxSz keeps the historic + * maxSz ratio, clamped to INT_MAX (and unbounded becomes INT_MAX). */ + if (maxSz > 0) { + if ((word32)maxSz > (word32)INT_MAX / inSz) + maxBufSz = (word32)INT_MAX; + else + maxBufSz = inSz * (word32)maxSz; + } + else { + maxBufSz = (word32)INT_MAX; + } + stream.next_in = (Bytef*)in; stream.avail_in = (uInt)inSz; /* Check for source > 64K on 16-bit machine: */ @@ -277,18 +291,17 @@ int wc_DeCompressDynamic(byte** out, int maxSz, int memoryType, word32 newSz; byte* newTmp; - if (maxSz > 0 && i >= maxSz) { + if (tmpSz >= maxBufSz) { WOLFSSL_MSG("Hit max decompress size!"); break; } - i++; - if (tmpSz > (word32)INT_MAX - inSz) { - WOLFSSL_MSG("Decompress buffer would exceed INT_MAX"); - result = DECOMPRESS_E; - break; - } - newSz = tmpSz + inSz; + /* Double the buffer so cumulative copy work stays linear in the + * output size, clamped to the permitted maximum. */ + if (tmpSz > maxBufSz / 2) + newSz = maxBufSz; + else + newSz = tmpSz * 2; newTmp = (byte*)XMALLOC(newSz, heap, memoryType); if (newTmp == NULL) { WOLFSSL_MSG("Memory error with increasing buffer size"); @@ -298,14 +311,14 @@ int wc_DeCompressDynamic(byte** out, int maxSz, int memoryType, XFREE(tmp, heap, memoryType); tmp = newTmp; stream.next_out = tmp + stream.total_out; - stream.avail_out = stream.avail_out + (uInt)inSz; + stream.avail_out = (uInt)(newSz - stream.total_out); tmpSz = newSz; result = inflate(&stream, Z_BLOCK); } } while (result == Z_OK); if (result == Z_STREAM_END) { - if (stream.total_out > (uLong)INT_MAX) { + if (stream.total_out >= (uLong)INT_MAX) { result = DECOMPRESS_E; } else { From 869e732b5f60bac6112861a725b9191f69f822eb Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:43:55 -0700 Subject: [PATCH 16/22] F-6300 - Abort TLS 1.2 client on unsolicited ServerHello extension --- src/tls.c | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/src/tls.c b/src/tls.c index ace0f7f979a..9766911b3fd 100644 --- a/src/tls.c +++ b/src/tls.c @@ -18578,29 +18578,31 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, #endif default: WOLFSSL_MSG("Unknown TLS extension type"); -#if defined(WOLFSSL_TLS13) - /* RFC 8446 Sec. 4.2: a TLS 1.3 client MUST abort with an - * unsupported_extension alert when it receives an extension - * "response" that was not advertised in the ClientHello. The - * rule applies only to messages whose extensions are responses - * to the ClientHello: ServerHello, HelloRetryRequest, - * EncryptedExtensions and Certificate. + /* RFC 5246 Sec. 7.4.1.4 (TLS 1.2) and RFC 8446 Sec. 4.2 + * (TLS 1.3): a client MUST abort with an unsupported_extension + * alert when it receives an extension "response" that was not + * advertised in the ClientHello. wolfSSL never offers an + * extension type that has no case here, so an unknown type in + * a ServerHello is always unsolicited. * - * Extensions in CertificateRequest and NewSessionTicket are - * independent server-initiated payloads, not responses, and - * per RFC 8701 (GREASE) the server MAY include unknown - * (GREASE) extension types there which the client MUST treat - * like any other unknown value (i.e. ignore them). */ - if (IsAtLeastTLSv1_3(ssl->version) && - (msgType == server_hello || - msgType == hello_retry_request || - msgType == encrypted_extensions || - msgType == certificate)) { + * For TLS 1.3 the rule also covers HelloRetryRequest, + * EncryptedExtensions and Certificate. CertificateRequest and + * NewSessionTicket are independent server-initiated payloads, + * not responses, and per RFC 8701 (GREASE) the server MAY + * include unknown extension types there which the client MUST + * ignore like any other unknown value. */ + if (msgType == server_hello +#if defined(WOLFSSL_TLS13) + || (IsAtLeastTLSv1_3(ssl->version) && + (msgType == hello_retry_request || + msgType == encrypted_extensions || + msgType == certificate)) +#endif + ) { SendAlert((WOLFSSL*)ssl, alert_fatal, unsupported_extension); WOLFSSL_ERROR_VERBOSE(UNSUPPORTED_EXTENSION); return UNSUPPORTED_EXTENSION; } -#endif } /* offset should be updated here! */ From bd18a9bfcccefac9112eb5918712efc9d9b8a16a Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:45:31 -0700 Subject: [PATCH 17/22] F-4229 - Reject negative Size in Ada AES_Set_Cbc_Encrypt wrapper --- wrapper/Ada/wolfssl.adb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wrapper/Ada/wolfssl.adb b/wrapper/Ada/wolfssl.adb index 4928a7d99df..e64ffb991e8 100644 --- a/wrapper/Ada/wolfssl.adb +++ b/wrapper/Ada/wolfssl.adb @@ -1493,6 +1493,10 @@ package body WolfSSL is Size : Integer; Result : out Integer) is begin + if Size < 0 then + Result := Exception_Error; + return; + end if; declare R : int; begin From c8ccffd84c7e5e9c079aca19750883ed0f2030f4 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:45:31 -0700 Subject: [PATCH 18/22] F-5757 - Reject negative Size in Ada AES_Set_Cbc_Decrypt wrapper --- wrapper/Ada/wolfssl.adb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/wrapper/Ada/wolfssl.adb b/wrapper/Ada/wolfssl.adb index e64ffb991e8..617dd245e79 100644 --- a/wrapper/Ada/wolfssl.adb +++ b/wrapper/Ada/wolfssl.adb @@ -1522,6 +1522,10 @@ package body WolfSSL is Size : Integer; Result : out Integer) is begin + if Size < 0 then + Result := Exception_Error; + return; + end if; declare R : int; begin From 86e4a598b45f1344bd8fbaccd7804744fb2e7934 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:46:06 -0700 Subject: [PATCH 19/22] F-5855 - Bounds-check cert length against bundle end in esp_crt_bundle init --- .../src/port/Espressif/esp_crt_bundle/esp_crt_bundle.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/wolfcrypt/src/port/Espressif/esp_crt_bundle/esp_crt_bundle.c b/wolfcrypt/src/port/Espressif/esp_crt_bundle/esp_crt_bundle.c index 76c8abdb1c4..c0baed81807 100644 --- a/wolfcrypt/src/port/Espressif/esp_crt_bundle/esp_crt_bundle.c +++ b/wolfcrypt/src/port/Espressif/esp_crt_bundle/esp_crt_bundle.c @@ -1382,6 +1382,13 @@ static esp_err_t wolfssl_esp_crt_bundle_init(const uint8_t *x509_bundle, ESP_LOGCBI(TAG, "- This certificate at 0x%x, length: %u", (intptr_t)cur_crt, cert_len); + if (cur_crt + CRT_HEADER_OFFSET + cert_len > bundle_end) { + ESP_LOGE(TAG, "Invalid certificate bundle cert length"); + _esp_crt_bundle_is_valid = ESP_FAIL; + ret = ESP_ERR_INVALID_ARG; + break; + } + /* TODO: optional gate out serial check for performance. */ /* Useful only for custom cert bundle, known to have no zeros. */ if (wolfssl_is_zero_serial_number(cur_crt + CRT_HEADER_OFFSET, From daf99ae645030153916ef8455ca0fdf6edb807fa Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:59:21 -0700 Subject: [PATCH 20/22] F-5728 - Widen ServerHello extension length check to avoid word16 truncation --- src/sniffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sniffer.c b/src/sniffer.c index 26dc257977b..149e5e2169b 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -3913,7 +3913,7 @@ static int ProcessServerHello(int msgSz, const byte* input, int* sslBytes, } /* switch (extType) */ /* make sure the extension fits in the remaining declared length */ - if ((word16)(extLen + EXT_TYPE_SZ + LENGTH_SZ) > len) { + if ((word32)extLen + EXT_TYPE_SZ + LENGTH_SZ > len) { SetError(SERVER_HELLO_INPUT_STR, error, session, FATAL_ERROR_STATE); return WOLFSSL_FATAL_ERROR; From 89bf094a5d6eec9c3e3a085524d83c896b3eab79 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 14:59:21 -0700 Subject: [PATCH 21/22] F-4809 - Widen ClientHello extension length check to avoid word16 truncation --- src/sniffer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sniffer.c b/src/sniffer.c index 149e5e2169b..7f0c4322552 100644 --- a/src/sniffer.c +++ b/src/sniffer.c @@ -4393,7 +4393,7 @@ static int ProcessClientHello(const byte* input, int* sslBytes, } /* make sure the extension fits in the remaining declared length */ - if ((word16)(extLen + EXT_TYPE_SZ + LENGTH_SZ) > len) { + if ((word32)extLen + EXT_TYPE_SZ + LENGTH_SZ > len) { SetError(CLIENT_HELLO_INPUT_STR, error, session, FATAL_ERROR_STATE); return WOLFSSL_FATAL_ERROR; } From d53e3615a37b85ed2593dff866fc376369c389f5 Mon Sep 17 00:00:00 2001 From: aidan garske Date: Thu, 25 Jun 2026 15:20:32 -0700 Subject: [PATCH 22/22] F-6300 - Revert TLS 1.2 unsolicited ServerHello extension abort; breaks heartbeat interop --- src/tls.c | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/tls.c b/src/tls.c index 9766911b3fd..ace0f7f979a 100644 --- a/src/tls.c +++ b/src/tls.c @@ -18578,31 +18578,29 @@ WOLFSSL_TEST_VIS int TLSX_Parse(WOLFSSL* ssl, const byte* input, word16 length, #endif default: WOLFSSL_MSG("Unknown TLS extension type"); - /* RFC 5246 Sec. 7.4.1.4 (TLS 1.2) and RFC 8446 Sec. 4.2 - * (TLS 1.3): a client MUST abort with an unsupported_extension - * alert when it receives an extension "response" that was not - * advertised in the ClientHello. wolfSSL never offers an - * extension type that has no case here, so an unknown type in - * a ServerHello is always unsolicited. - * - * For TLS 1.3 the rule also covers HelloRetryRequest, - * EncryptedExtensions and Certificate. CertificateRequest and - * NewSessionTicket are independent server-initiated payloads, - * not responses, and per RFC 8701 (GREASE) the server MAY - * include unknown extension types there which the client MUST - * ignore like any other unknown value. */ - if (msgType == server_hello #if defined(WOLFSSL_TLS13) - || (IsAtLeastTLSv1_3(ssl->version) && - (msgType == hello_retry_request || - msgType == encrypted_extensions || - msgType == certificate)) -#endif - ) { + /* RFC 8446 Sec. 4.2: a TLS 1.3 client MUST abort with an + * unsupported_extension alert when it receives an extension + * "response" that was not advertised in the ClientHello. The + * rule applies only to messages whose extensions are responses + * to the ClientHello: ServerHello, HelloRetryRequest, + * EncryptedExtensions and Certificate. + * + * Extensions in CertificateRequest and NewSessionTicket are + * independent server-initiated payloads, not responses, and + * per RFC 8701 (GREASE) the server MAY include unknown + * (GREASE) extension types there which the client MUST treat + * like any other unknown value (i.e. ignore them). */ + if (IsAtLeastTLSv1_3(ssl->version) && + (msgType == server_hello || + msgType == hello_retry_request || + msgType == encrypted_extensions || + msgType == certificate)) { SendAlert((WOLFSSL*)ssl, alert_fatal, unsupported_extension); WOLFSSL_ERROR_VERBOSE(UNSUPPORTED_EXTENSION); return UNSUPPORTED_EXTENSION; } +#endif } /* offset should be updated here! */