From 4bb98839ba6585f646b65fdb67c9d067bb4eca26 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Tue, 14 Apr 2026 11:48:56 -0500 Subject: [PATCH 1/2] Fix TLS ext bounds checking --- src/tls.c | 97 +++++++++++++++++++++++++++++++++++++++------- src/tls13.c | 16 +++++++- tests/api.c | 62 +++++++++++++++++++++++++++++ wolfssl/internal.h | 10 ++--- 4 files changed, 163 insertions(+), 22 deletions(-) diff --git a/src/tls.c b/src/tls.c index 02a0881352a..021fdf75657 100644 --- a/src/tls.c +++ b/src/tls.c @@ -6477,7 +6477,7 @@ static int TLSX_SessionTicket_Parse(WOLFSSL* ssl, const byte* input, return ret; } -WOLFSSL_LOCAL SessionTicket* TLSX_SessionTicket_Create(word32 lifetime, +WOLFSSL_TEST_VIS SessionTicket* TLSX_SessionTicket_Create(word32 lifetime, byte* data, word16 size, void* heap) { SessionTicket* ticket = (SessionTicket*)XMALLOC(sizeof(SessionTicket), @@ -6498,7 +6498,7 @@ WOLFSSL_LOCAL SessionTicket* TLSX_SessionTicket_Create(word32 lifetime, return ticket; } -WOLFSSL_LOCAL void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap) +WOLFSSL_TEST_VIS void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap) { if (ticket) { XFREE(ticket->data, heap, DYNAMIC_TYPE_TLSX); @@ -14159,10 +14159,16 @@ static int TLSX_ECH_ExpandOuterExtensions(WOLFSSL* ssl, WOLFSSL_ECH* ech, } if (ret == 0) { - XFREE(ech->innerClientHello, heap, DYNAMIC_TYPE_TMP_BUFFER); - ech->innerClientHello = newInnerCh; - ech->innerClientHelloLen = (word16)newInnerChLen; - newInnerCh = NULL; + if (newInnerChLen > WOLFSSL_MAX_16BIT) { + WOLFSSL_MSG("ECH expanded inner ClientHello exceeds word16"); + ret = BUFFER_E; + } + else { + XFREE(ech->innerClientHello, heap, DYNAMIC_TYPE_TMP_BUFFER); + ech->innerClientHello = newInnerCh; + ech->innerClientHelloLen = (word16)newInnerChLen; + newInnerCh = NULL; + } } if (newInnerCh != NULL) @@ -14760,7 +14766,14 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, { int ret = 0; TLSX* extension; - word16 length = 0; + /* Use a word32 accumulator so that an extension whose contribution + * pushes the running total past 0xFFFF is detected rather than + * silently wrapped (the TLS extensions block length prefix on the + * wire is a 2-byte field). Callees that take a word16* accumulator + * are invoked via a per-iteration shim and their delta is added + * back into the word32 total. */ + word32 length = 0; + word16 cbShim; byte isRequest = (msgType == client_hello || msgType == certificate_request); @@ -14846,19 +14859,25 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, #endif #if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY) case TLSX_ENCRYPT_THEN_MAC: - ret = ETM_GET_SIZE(msgType, &length); + cbShim = 0; + ret = ETM_GET_SIZE(msgType, &cbShim); + length += cbShim; break; #endif /* HAVE_ENCRYPT_THEN_MAC */ #if defined(WOLFSSL_TLS13) || !defined(WOLFSSL_NO_TLS12) || !defined(NO_OLD_TLS) #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK) case TLSX_PRE_SHARED_KEY: + cbShim = 0; ret = PSK_GET_SIZE((PreSharedKey*)extension->data, msgType, - &length); + &cbShim); + length += cbShim; break; #ifdef WOLFSSL_TLS13 case TLSX_PSK_KEY_EXCHANGE_MODES: - ret = PKM_GET_SIZE((byte)extension->val, msgType, &length); + cbShim = 0; + ret = PKM_GET_SIZE((byte)extension->val, msgType, &cbShim); + length += cbShim; break; #endif #endif @@ -14869,22 +14888,30 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, #ifdef WOLFSSL_TLS13 case TLSX_SUPPORTED_VERSIONS: - ret = SV_GET_SIZE(extension->data, msgType, &length); + cbShim = 0; + ret = SV_GET_SIZE(extension->data, msgType, &cbShim); + length += cbShim; break; case TLSX_COOKIE: - ret = CKE_GET_SIZE((Cookie*)extension->data, msgType, &length); + cbShim = 0; + ret = CKE_GET_SIZE((Cookie*)extension->data, msgType, &cbShim); + length += cbShim; break; #ifdef WOLFSSL_EARLY_DATA case TLSX_EARLY_DATA: - ret = EDI_GET_SIZE(msgType, &length); + cbShim = 0; + ret = EDI_GET_SIZE(msgType, &cbShim); + length += cbShim; break; #endif #ifdef WOLFSSL_POST_HANDSHAKE_AUTH case TLSX_POST_HANDSHAKE_AUTH: - ret = PHA_GET_SIZE(msgType, &length); + cbShim = 0; + ret = PHA_GET_SIZE(msgType, &cbShim); + length += cbShim; break; #endif @@ -14940,9 +14967,21 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, /* marks the extension as processed so ctx level */ /* extensions don't overlap with ssl level ones. */ TURN_ON(semaphore, TLSX_ToSemaphore((word16)extension->type)); + + /* Early exit: stop accumulating as soon as the running total + * cannot possibly fit the 2-byte wire length. */ + if (length > WOLFSSL_MAX_16BIT) { + WOLFSSL_MSG("TLSX_GetSize extension length exceeds word16"); + return BUFFER_E; + } } - *pLength += length; + if ((word32)*pLength + length > WOLFSSL_MAX_16BIT) { + WOLFSSL_MSG("TLSX_GetSize total extensions length exceeds word16"); + return BUFFER_E; + } + + *pLength += (word16)length; return ret; } @@ -14955,6 +14994,7 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, TLSX* extension; word16 offset = 0; word16 length_offset = 0; + word32 prevOffset; byte isRequest = (msgType == client_hello || msgType == certificate_request); @@ -14969,6 +15009,10 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, if (!IS_OFF(semaphore, TLSX_ToSemaphore((word16)extension->type))) continue; /* skip! */ + /* Snapshot offset to detect word16 wrap within this iteration; + * see matching comment in TLSX_GetSize. */ + prevOffset = offset; + /* writes extension type. */ c16toa((word16)extension->type, output + offset); offset += HELLO_EXT_TYPE_SZ + OPAQUE16_LEN; @@ -15196,6 +15240,16 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, /* if we encountered an error propagate it */ if (ret != 0) break; + + if (offset < prevOffset) { + WOLFSSL_MSG("TLSX_Write extension length exceeds word16"); + return BUFFER_E; + } + } + + if ((word32)*pOffset + (word32)offset > WOLFSSL_MAX_16BIT) { + WOLFSSL_MSG("TLSX_Write total extensions length exceeds word16"); + return BUFFER_E; } *pOffset += offset; @@ -16283,6 +16337,13 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength) } #endif + /* The TLS extensions block length prefix is a 2-byte field, so any + * accumulated total above 0xFFFF must be rejected rather than silently + * truncating and producing a short, malformed handshake message. */ + if (length > (word16)(WOLFSSL_MAX_16BIT - OPAQUE16_LEN)) { + WOLFSSL_MSG("TLSX_GetRequestSize extensions exceed word16"); + return BUFFER_E; + } if (length) length += OPAQUE16_LEN; /* for total length storage. */ @@ -16486,6 +16547,12 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset) #endif #endif + /* Wrap detection for the TLSX_Write calls above is handled inside + * TLSX_Write itself: any iteration that would push the local word16 + * offset past 0xFFFF returns BUFFER_E so we never reach here with a + * truncated value. The TLS extensions block length prefix on the + * wire is a 2-byte field, matching this invariant. */ + if (offset > OPAQUE16_LEN || msgType != client_hello) c16toa(offset - OPAQUE16_LEN, output); /* extensions length */ diff --git a/src/tls13.c b/src/tls13.c index b963ab0e3d7..74f19972379 100644 --- a/src/tls13.c +++ b/src/tls13.c @@ -4759,8 +4759,20 @@ int SendTls13ClientHello(WOLFSSL* ssl) args->ech->type = 0; /* set innerClientHelloLen to ClientHelloInner + padding + tag */ args->ech->paddingLen = 31 - ((args->length - 1) % 32); - args->ech->innerClientHelloLen = (word16)(args->length + - args->ech->paddingLen + args->ech->hpke->Nt); + { + word32 ichLen = args->length + args->ech->paddingLen + + args->ech->hpke->Nt; + /* Guard against word16 truncation: the wire format field + * for the ECH payload length is two bytes, so anything + * above 0xFFFF cannot be represented and the silent cast + * would cause an undersized allocation and heap overflow + * in the subsequent XMEMCPY. */ + if (ichLen > WOLFSSL_MAX_16BIT) { + WOLFSSL_MSG("ECH inner ClientHello exceeds word16"); + return BUFFER_E; + } + args->ech->innerClientHelloLen = (word16)ichLen; + } /* set the length back to before we computed ClientHelloInner size */ args->length = (word32)args->preXLength; } diff --git a/tests/api.c b/tests/api.c index 0e9cbe29824..b82db0f9efb 100644 --- a/tests/api.c +++ b/tests/api.c @@ -10471,6 +10471,67 @@ static int test_tls_ext_duplicate(void) return EXPECT_RESULT(); } +/* Regression test: TLSX extension size accumulation must not silently wrap + * the internal word16 accumulator. Prior to the fix, a single extension + * whose size (plus the 4-byte header) pushes the running total past 0xFFFF + * caused TLSX_GetSize / TLSX_Write to return a truncated length, which + * in turn led to undersized buffer writes. The on-wire extensions block + * length is a 2-byte field per RFC 8446 Section 4.2, so the correct + * behavior is to return BUFFER_E rather than wrap. */ +static int test_tls_ext_word16_overflow(void) +{ + EXPECT_DECLS; +#if defined(HAVE_SESSION_TICKET) && !defined(NO_WOLFSSL_CLIENT) && \ + !defined(NO_TLS) + WOLFSSL_CTX* ctx = NULL; + WOLFSSL* ssl = NULL; + SessionTicket* ticket = NULL; + byte* big = NULL; + /* Size chosen so that 4 (ext header) + size > 0xFFFF. */ + const word16 bigSz = 0xFFFE; + word32 length = 0; + + big = (byte*)XMALLOC(bigSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(big); + if (big != NULL) + XMEMSET(big, 0xA5, bigSz); + + ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl = wolfSSL_new(ctx)); + + /* Build an oversized SessionTicket extension directly on the ssl + * extension list. Going via the public API is not enough here because + * wolfSSL_set_SessionTicket clamps to word16 without creating the + * TLSX entry; the TLSX path is what exercises the accumulator. */ + if (EXPECT_SUCCESS()) { + ticket = TLSX_SessionTicket_Create(0, big, bigSz, ssl->heap); + ExpectNotNull(ticket); + } + if (EXPECT_SUCCESS()) { + ExpectIntEQ(TLSX_UseSessionTicket(&ssl->extensions, ticket, ssl->heap), + WOLFSSL_SUCCESS); + /* TLSX_UseSessionTicket takes ownership on success. */ + ticket = NULL; + } + + /* TLSX_GetRequestSize must refuse to encode: 4-byte ext header + + * 0xFFFE payload + 2-byte block length prefix = 0x10004, which does + * not fit in a word16 wire length. Expect BUFFER_E, not a silently + * wrapped small value. */ + if (EXPECT_SUCCESS()) { + int ret = TLSX_GetRequestSize(ssl, client_hello, &length); + ExpectIntEQ(ret, BUFFER_E); + } + + if (ticket != NULL) + TLSX_SessionTicket_Free(ticket, ssl->heap); + wolfSSL_free(ssl); + wolfSSL_CTX_free(ctx); + XFREE(big, NULL, DYNAMIC_TYPE_TMP_BUFFER); +#endif + return EXPECT_RESULT(); +} + /* Test TLS connection abort when legacy version field indicates TLS 1.3 or * higher. Based on test_tls_ext_duplicate() but with legacy version modified @@ -35773,6 +35834,7 @@ TEST_CASE testCases[] = { TEST_DECL(test_wolfSSL_SCR_Reconnect), TEST_DECL(test_wolfSSL_SCR_check_enabled), TEST_DECL(test_tls_ext_duplicate), + TEST_DECL(test_tls_ext_word16_overflow), TEST_DECL(test_tls_bad_legacy_version), #if defined(WOLFSSL_TLS13) && defined(HAVE_ECH) #if defined(HAVE_IO_TESTS_DEPENDENCIES) diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 822c0722f67..9249fe4e765 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3177,9 +3177,9 @@ WOLFSSL_LOCAL int TLSX_SupportExtensions(WOLFSSL* ssl); WOLFSSL_LOCAL int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isRequest); #if defined(WOLFSSL_TLS13) || !defined(NO_WOLFSSL_CLIENT) -WOLFSSL_LOCAL int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, +WOLFSSL_TEST_VIS int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength); -WOLFSSL_LOCAL int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, +WOLFSSL_TEST_VIS int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset); #endif @@ -3585,11 +3585,11 @@ typedef struct TicketEncCbCtx { #endif /* !WOLFSSL_NO_DEF_TICKET_ENC_CB && !NO_WOLFSSL_SERVER */ -WOLFSSL_LOCAL int TLSX_UseSessionTicket(TLSX** extensions, +WOLFSSL_TEST_VIS int TLSX_UseSessionTicket(TLSX** extensions, SessionTicket* ticket, void* heap); -WOLFSSL_LOCAL SessionTicket* TLSX_SessionTicket_Create(word32 lifetime, +WOLFSSL_TEST_VIS SessionTicket* TLSX_SessionTicket_Create(word32 lifetime, byte* data, word16 size, void* heap); -WOLFSSL_LOCAL void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap); +WOLFSSL_TEST_VIS void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap); #endif /* HAVE_SESSION_TICKET */ From 0d4418ab2077159afdaef018f234495caa8a6aa2 Mon Sep 17 00:00:00 2001 From: Eric Blankenhorn Date: Tue, 14 Apr 2026 12:38:47 -0500 Subject: [PATCH 2/2] Check tag size in wc_AesEaxDecryptFinal --- src/tls.c | 46 ++++++++++++------ tests/api.c | 77 ++++++++++++++++++++++++++++++ wolfcrypt/src/aes.c | 8 +++- wolfcrypt/test/test.c | 108 ++++++++++++++++++++++++++++++++++++++++++ wolfssl/internal.h | 9 ++++ 5 files changed, 233 insertions(+), 15 deletions(-) diff --git a/src/tls.c b/src/tls.c index 021fdf75657..23bc27ef6bb 100644 --- a/src/tls.c +++ b/src/tls.c @@ -14770,12 +14770,23 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, * pushes the running total past 0xFFFF is detected rather than * silently wrapped (the TLS extensions block length prefix on the * wire is a 2-byte field). Callees that take a word16* accumulator - * are invoked via a per-iteration shim and their delta is added - * back into the word32 total. */ + * are invoked via a per-iteration shim (`cbShim`) and their delta + * is added back into the word32 total. + * + * MAINTAINER NOTE: do NOT pass &length to any *_GET_SIZE function + * that expects a `word16*` out-parameter -- that would be a type + * mismatch (UB) and would silently bypass the overflow detection + * below. When adding a new extension case, either: + * - use `length += FOO_GET_SIZE(...)` when the helper returns a + * word16 by value, or + * - use the cbShim pattern: `cbShim = 0; ret = FOO_GET_SIZE(..., + * &cbShim); length += cbShim;` + */ word32 length = 0; - word16 cbShim; + word16 cbShim = 0; byte isRequest = (msgType == client_hello || msgType == certificate_request); + (void)cbShim; while ((extension = list)) { list = extension->next; @@ -14964,16 +14975,18 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType, break; } - /* marks the extension as processed so ctx level */ - /* extensions don't overlap with ssl level ones. */ - TURN_ON(semaphore, TLSX_ToSemaphore((word16)extension->type)); - /* Early exit: stop accumulating as soon as the running total - * cannot possibly fit the 2-byte wire length. */ + * cannot possibly fit the 2-byte wire length. Check *before* + * marking the extension as processed so the semaphore is not + * left in an inconsistent state on the error path. */ if (length > WOLFSSL_MAX_16BIT) { WOLFSSL_MSG("TLSX_GetSize extension length exceeds word16"); return BUFFER_E; } + + /* marks the extension as processed so ctx level */ + /* extensions don't overlap with ssl level ones. */ + TURN_ON(semaphore, TLSX_ToSemaphore((word16)extension->type)); } if ((word32)*pLength + length > WOLFSSL_MAX_16BIT) { @@ -15241,19 +15254,24 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore, if (ret != 0) break; - if (offset < prevOffset) { + if (offset <= prevOffset) { WOLFSSL_MSG("TLSX_Write extension length exceeds word16"); return BUFFER_E; } } - if ((word32)*pOffset + (word32)offset > WOLFSSL_MAX_16BIT) { - WOLFSSL_MSG("TLSX_Write total extensions length exceeds word16"); - return BUFFER_E; + /* Only validate and commit the aggregate offset when the loop + * completed without error; on the error path, leave *pOffset + * unchanged and return the original failure reason so callers + * see the real error instead of a masking BUFFER_E. */ + if (ret == 0) { + if ((word32)*pOffset + (word32)offset > WOLFSSL_MAX_16BIT) { + WOLFSSL_MSG("TLSX_Write total extensions length exceeds word16"); + return BUFFER_E; + } + *pOffset += offset; } - *pOffset += offset; - return ret; } diff --git a/tests/api.c b/tests/api.c index b82db0f9efb..46b55da57a8 100644 --- a/tests/api.c +++ b/tests/api.c @@ -10528,6 +10528,83 @@ static int test_tls_ext_word16_overflow(void) wolfSSL_free(ssl); wolfSSL_CTX_free(ctx); XFREE(big, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ssl = NULL; + ctx = NULL; + big = NULL; + + /* Boundary case: construct a SessionTicket extension sized so that the + * total extensions length in TLSX_GetRequestSize is exactly + * WOLFSSL_MAX_16BIT - OPAQUE16_LEN (0xFFFD) *before* the OPAQUE16_LEN + * block-prefix adjustment, which must succeed. This pins the `>` + * comparison in the overflow check -- mutating it to `>=` would + * incorrectly reject this valid case and fail this test. */ + { + WOLFSSL_CTX* ctx2 = NULL; + WOLFSSL* ssl2 = NULL; + SessionTicket* ticket2 = NULL; + byte* buf = NULL; + word32 baseLen = 0; + word32 baseInternal = 0; + word32 tickSz = 0; + /* TLSX_GetRequestSize rejects when internal sum > 0xFFFD. */ + const word32 target = (word32)WOLFSSL_MAX_16BIT - (word32)OPAQUE16_LEN; + /* Session ticket extension contributes: type (2) + len (2) + size. */ + const word32 extHdr = (word32)HELLO_EXT_TYPE_SZ + + (word32)OPAQUE16_LEN; + int ret; + + ExpectNotNull(ctx2 = wolfSSL_CTX_new(wolfSSLv23_client_method())); + ExpectNotNull(ssl2 = wolfSSL_new(ctx2)); + + /* Measure baseline length with no session ticket extension. The + * returned value already includes the 2-byte block-length prefix + * when nonzero; strip it to get the raw internal sum. */ + if (EXPECT_SUCCESS()) { + baseLen = 0; + ret = TLSX_GetRequestSize(ssl2, client_hello, &baseLen); + ExpectIntEQ(ret, 0); + baseInternal = (baseLen > 0) + ? baseLen - (word32)OPAQUE16_LEN : 0; + } + + /* Target: baseInternal + extHdr + tickSz == 0xFFFD. */ + if (EXPECT_SUCCESS() && baseInternal + extHdr < target) { + tickSz = target - baseInternal - extHdr; + } + + if (EXPECT_SUCCESS() && tickSz > 0 && tickSz <= WOLFSSL_MAX_16BIT) { + buf = (byte*)XMALLOC(tickSz, NULL, DYNAMIC_TYPE_TMP_BUFFER); + ExpectNotNull(buf); + if (buf != NULL) + XMEMSET(buf, 0x5A, tickSz); + } + + if (EXPECT_SUCCESS() && buf != NULL) { + ticket2 = TLSX_SessionTicket_Create(0, buf, (word16)tickSz, + ssl2->heap); + ExpectNotNull(ticket2); + } + if (EXPECT_SUCCESS() && ticket2 != NULL) { + ExpectIntEQ(TLSX_UseSessionTicket(&ssl2->extensions, ticket2, + ssl2->heap), WOLFSSL_SUCCESS); + ticket2 = NULL; + } + + /* Exact boundary: internal sum == 0xFFFD must succeed, and the + * final returned length is 0xFFFD + OPAQUE16_LEN == 0xFFFF. */ + if (EXPECT_SUCCESS()) { + word32 lenBoundary = 0; + ret = TLSX_GetRequestSize(ssl2, client_hello, &lenBoundary); + ExpectIntEQ(ret, 0); + ExpectIntEQ(lenBoundary, (word32)WOLFSSL_MAX_16BIT); + } + + if (ticket2 != NULL) + TLSX_SessionTicket_Free(ticket2, ssl2->heap); + wolfSSL_free(ssl2); + wolfSSL_CTX_free(ctx2); + XFREE(buf, NULL, DYNAMIC_TYPE_TMP_BUFFER); + } #endif return EXPECT_RESULT(); } diff --git a/wolfcrypt/src/aes.c b/wolfcrypt/src/aes.c index 05267c1e830..f702178493f 100644 --- a/wolfcrypt/src/aes.c +++ b/wolfcrypt/src/aes.c @@ -16823,6 +16823,11 @@ int wc_AesEaxDecryptAuth(const byte* key, word32 keySz, byte* out, return BAD_FUNC_ARG; } + if (authTagSz < WOLFSSL_MIN_AUTH_TAG_SZ + || authTagSz > WC_AES_BLOCK_SIZE) { + return BAD_FUNC_ARG; + } + #if defined(WOLFSSL_SMALL_STACK) if ((eax = (AesEax *)XMALLOC(sizeof(AesEax), NULL, @@ -17171,7 +17176,8 @@ int wc_AesEaxDecryptFinal(AesEax* eax, byte authTag[WC_AES_BLOCK_SIZE]; #endif - if (eax == NULL || authIn == NULL || authInSz > WC_AES_BLOCK_SIZE) { + if (eax == NULL || authIn == NULL || authInSz > WC_AES_BLOCK_SIZE + || authInSz < WOLFSSL_MIN_AUTH_TAG_SZ) { return BAD_FUNC_ARG; } diff --git a/wolfcrypt/test/test.c b/wolfcrypt/test/test.c index 5f846fcc7e8..76620da20bd 100644 --- a/wolfcrypt/test/test.c +++ b/wolfcrypt/test/test.c @@ -19486,6 +19486,114 @@ WOLFSSL_TEST_SUBROUTINE wc_test_ret_t aes_eax_test(void) } } + + /* Regression test: wc_AesEaxDecryptAuth must reject authTagSz below + * WOLFSSL_MIN_AUTH_TAG_SZ (including zero), otherwise an attacker could + * bypass tag verification by supplying an empty tag. */ +#if WOLFSSL_MIN_AUTH_TAG_SZ > 0 + { + byte zero_ct[16]; + byte zero_pt[16]; + byte zero_tag[16]; + XMEMSET(zero_ct, 0, sizeof(zero_ct)); + XMEMSET(zero_tag, 0, sizeof(zero_tag)); + + ret = wc_AesEaxDecryptAuth(vectors[0].key, + (word32)vectors[0].key_length, + zero_pt, + zero_ct, (word32)sizeof(zero_ct), + vectors[0].iv, + (word32)vectors[0].iv_length, + zero_tag, 0, + vectors[0].aad, + (word32)vectors[0].aad_length); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + return WC_TEST_RET_ENC_EC(ret); + } + +#if WOLFSSL_MIN_AUTH_TAG_SZ > 1 + ret = wc_AesEaxDecryptAuth(vectors[0].key, + (word32)vectors[0].key_length, + zero_pt, + zero_ct, (word32)sizeof(zero_ct), + vectors[0].iv, + (word32)vectors[0].iv_length, + zero_tag, WOLFSSL_MIN_AUTH_TAG_SZ - 1, + vectors[0].aad, + (word32)vectors[0].aad_length); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + return WC_TEST_RET_ENC_EC(ret); + } +#endif + + /* Upper bound: authTagSz > WC_AES_BLOCK_SIZE must be rejected. + * Pins the '>' operator in the validation against mutation to '>=' + * and prevents an over-read of the caller-supplied tag buffer. */ + ret = wc_AesEaxDecryptAuth(vectors[0].key, + (word32)vectors[0].key_length, + zero_pt, + zero_ct, (word32)sizeof(zero_ct), + vectors[0].iv, + (word32)vectors[0].iv_length, + zero_tag, WC_AES_BLOCK_SIZE + 1, + vectors[0].aad, + (word32)vectors[0].aad_length); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + return WC_TEST_RET_ENC_EC(ret); + } + + /* Direct incremental-API coverage: wc_AesEaxDecryptFinal must also + * reject authInSz of zero and below WOLFSSL_MIN_AUTH_TAG_SZ. The + * one-shot API above is a separate code path. Heap-allocate the + * AesEax context to keep stack usage within Linux kernel limits. */ + { + AesEax *eax = (AesEax *)XMALLOC(sizeof(*eax), HEAP_HINT, + DYNAMIC_TYPE_TMP_BUFFER); + if (eax == NULL) { + return WC_TEST_RET_ENC_NC; + } + XMEMSET(eax, 0, sizeof(*eax)); + ret = wc_AesEaxInit(eax, + vectors[0].key, (word32)vectors[0].key_length, + vectors[0].iv, (word32)vectors[0].iv_length, + vectors[0].aad, + (word32)vectors[0].aad_length); + if (ret != 0) { + XFREE(eax, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return WC_TEST_RET_ENC_EC(ret); + } + + ret = wc_AesEaxDecryptFinal(eax, zero_tag, 0); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + wc_AesEaxFree(eax); + XFREE(eax, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return WC_TEST_RET_ENC_EC(ret); + } + +#if WOLFSSL_MIN_AUTH_TAG_SZ > 1 + ret = wc_AesEaxDecryptFinal(eax, zero_tag, + WOLFSSL_MIN_AUTH_TAG_SZ - 1); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + wc_AesEaxFree(eax); + XFREE(eax, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return WC_TEST_RET_ENC_EC(ret); + } +#endif + + /* Upper bound: authInSz > WC_AES_BLOCK_SIZE must be rejected. */ + ret = wc_AesEaxDecryptFinal(eax, zero_tag, WC_AES_BLOCK_SIZE + 1); + if (ret != WC_NO_ERR_TRACE(BAD_FUNC_ARG)) { + wc_AesEaxFree(eax); + XFREE(eax, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + return WC_TEST_RET_ENC_EC(ret); + } + + wc_AesEaxFree(eax); + XFREE(eax, HEAP_HINT, DYNAMIC_TYPE_TMP_BUFFER); + } + } +#endif /* WOLFSSL_MIN_AUTH_TAG_SZ > 0 */ + return 0; } diff --git a/wolfssl/internal.h b/wolfssl/internal.h index 9249fe4e765..3e75b1185c9 100644 --- a/wolfssl/internal.h +++ b/wolfssl/internal.h @@ -3177,6 +3177,10 @@ WOLFSSL_LOCAL int TLSX_SupportExtensions(WOLFSSL* ssl); WOLFSSL_LOCAL int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isRequest); #if defined(WOLFSSL_TLS13) || !defined(NO_WOLFSSL_CLIENT) +#ifdef WOLFSSL_API_PREFIX_MAP + #define TLSX_GetRequestSize wolfSSL_TLSX_GetRequestSize + #define TLSX_WriteRequest wolfSSL_TLSX_WriteRequest +#endif WOLFSSL_TEST_VIS int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength); WOLFSSL_TEST_VIS int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, @@ -3585,6 +3589,11 @@ typedef struct TicketEncCbCtx { #endif /* !WOLFSSL_NO_DEF_TICKET_ENC_CB && !NO_WOLFSSL_SERVER */ +#ifdef WOLFSSL_API_PREFIX_MAP + #define TLSX_UseSessionTicket wolfSSL_TLSX_UseSessionTicket + #define TLSX_SessionTicket_Create wolfSSL_TLSX_SessionTicket_Create + #define TLSX_SessionTicket_Free wolfSSL_TLSX_SessionTicket_Free +#endif WOLFSSL_TEST_VIS int TLSX_UseSessionTicket(TLSX** extensions, SessionTicket* ticket, void* heap); WOLFSSL_TEST_VIS SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,