Skip to content

Commit 4bb9883

Browse files
committed
Fix TLS ext bounds checking
1 parent 9ed79a2 commit 4bb9883

4 files changed

Lines changed: 163 additions & 22 deletions

File tree

src/tls.c

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6477,7 +6477,7 @@ static int TLSX_SessionTicket_Parse(WOLFSSL* ssl, const byte* input,
64776477
return ret;
64786478
}
64796479

6480-
WOLFSSL_LOCAL SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
6480+
WOLFSSL_TEST_VIS SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
64816481
byte* data, word16 size, void* heap)
64826482
{
64836483
SessionTicket* ticket = (SessionTicket*)XMALLOC(sizeof(SessionTicket),
@@ -6498,7 +6498,7 @@ WOLFSSL_LOCAL SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
64986498

64996499
return ticket;
65006500
}
6501-
WOLFSSL_LOCAL void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap)
6501+
WOLFSSL_TEST_VIS void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap)
65026502
{
65036503
if (ticket) {
65046504
XFREE(ticket->data, heap, DYNAMIC_TYPE_TLSX);
@@ -14159,10 +14159,16 @@ static int TLSX_ECH_ExpandOuterExtensions(WOLFSSL* ssl, WOLFSSL_ECH* ech,
1415914159
}
1416014160

1416114161
if (ret == 0) {
14162-
XFREE(ech->innerClientHello, heap, DYNAMIC_TYPE_TMP_BUFFER);
14163-
ech->innerClientHello = newInnerCh;
14164-
ech->innerClientHelloLen = (word16)newInnerChLen;
14165-
newInnerCh = NULL;
14162+
if (newInnerChLen > WOLFSSL_MAX_16BIT) {
14163+
WOLFSSL_MSG("ECH expanded inner ClientHello exceeds word16");
14164+
ret = BUFFER_E;
14165+
}
14166+
else {
14167+
XFREE(ech->innerClientHello, heap, DYNAMIC_TYPE_TMP_BUFFER);
14168+
ech->innerClientHello = newInnerCh;
14169+
ech->innerClientHelloLen = (word16)newInnerChLen;
14170+
newInnerCh = NULL;
14171+
}
1416614172
}
1416714173

1416814174
if (newInnerCh != NULL)
@@ -14760,7 +14766,14 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1476014766
{
1476114767
int ret = 0;
1476214768
TLSX* extension;
14763-
word16 length = 0;
14769+
/* Use a word32 accumulator so that an extension whose contribution
14770+
* pushes the running total past 0xFFFF is detected rather than
14771+
* silently wrapped (the TLS extensions block length prefix on the
14772+
* wire is a 2-byte field). Callees that take a word16* accumulator
14773+
* are invoked via a per-iteration shim and their delta is added
14774+
* back into the word32 total. */
14775+
word32 length = 0;
14776+
word16 cbShim;
1476414777
byte isRequest = (msgType == client_hello ||
1476514778
msgType == certificate_request);
1476614779

@@ -14846,19 +14859,25 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1484614859
#endif
1484714860
#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY)
1484814861
case TLSX_ENCRYPT_THEN_MAC:
14849-
ret = ETM_GET_SIZE(msgType, &length);
14862+
cbShim = 0;
14863+
ret = ETM_GET_SIZE(msgType, &cbShim);
14864+
length += cbShim;
1485014865
break;
1485114866
#endif /* HAVE_ENCRYPT_THEN_MAC */
1485214867

1485314868
#if defined(WOLFSSL_TLS13) || !defined(WOLFSSL_NO_TLS12) || !defined(NO_OLD_TLS)
1485414869
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
1485514870
case TLSX_PRE_SHARED_KEY:
14871+
cbShim = 0;
1485614872
ret = PSK_GET_SIZE((PreSharedKey*)extension->data, msgType,
14857-
&length);
14873+
&cbShim);
14874+
length += cbShim;
1485814875
break;
1485914876
#ifdef WOLFSSL_TLS13
1486014877
case TLSX_PSK_KEY_EXCHANGE_MODES:
14861-
ret = PKM_GET_SIZE((byte)extension->val, msgType, &length);
14878+
cbShim = 0;
14879+
ret = PKM_GET_SIZE((byte)extension->val, msgType, &cbShim);
14880+
length += cbShim;
1486214881
break;
1486314882
#endif
1486414883
#endif
@@ -14869,22 +14888,30 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1486914888

1487014889
#ifdef WOLFSSL_TLS13
1487114890
case TLSX_SUPPORTED_VERSIONS:
14872-
ret = SV_GET_SIZE(extension->data, msgType, &length);
14891+
cbShim = 0;
14892+
ret = SV_GET_SIZE(extension->data, msgType, &cbShim);
14893+
length += cbShim;
1487314894
break;
1487414895

1487514896
case TLSX_COOKIE:
14876-
ret = CKE_GET_SIZE((Cookie*)extension->data, msgType, &length);
14897+
cbShim = 0;
14898+
ret = CKE_GET_SIZE((Cookie*)extension->data, msgType, &cbShim);
14899+
length += cbShim;
1487714900
break;
1487814901

1487914902
#ifdef WOLFSSL_EARLY_DATA
1488014903
case TLSX_EARLY_DATA:
14881-
ret = EDI_GET_SIZE(msgType, &length);
14904+
cbShim = 0;
14905+
ret = EDI_GET_SIZE(msgType, &cbShim);
14906+
length += cbShim;
1488214907
break;
1488314908
#endif
1488414909

1488514910
#ifdef WOLFSSL_POST_HANDSHAKE_AUTH
1488614911
case TLSX_POST_HANDSHAKE_AUTH:
14887-
ret = PHA_GET_SIZE(msgType, &length);
14912+
cbShim = 0;
14913+
ret = PHA_GET_SIZE(msgType, &cbShim);
14914+
length += cbShim;
1488814915
break;
1488914916
#endif
1489014917

@@ -14940,9 +14967,21 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1494014967
/* marks the extension as processed so ctx level */
1494114968
/* extensions don't overlap with ssl level ones. */
1494214969
TURN_ON(semaphore, TLSX_ToSemaphore((word16)extension->type));
14970+
14971+
/* Early exit: stop accumulating as soon as the running total
14972+
* cannot possibly fit the 2-byte wire length. */
14973+
if (length > WOLFSSL_MAX_16BIT) {
14974+
WOLFSSL_MSG("TLSX_GetSize extension length exceeds word16");
14975+
return BUFFER_E;
14976+
}
1494314977
}
1494414978

14945-
*pLength += length;
14979+
if ((word32)*pLength + length > WOLFSSL_MAX_16BIT) {
14980+
WOLFSSL_MSG("TLSX_GetSize total extensions length exceeds word16");
14981+
return BUFFER_E;
14982+
}
14983+
14984+
*pLength += (word16)length;
1494614985

1494714986
return ret;
1494814987
}
@@ -14955,6 +14994,7 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1495514994
TLSX* extension;
1495614995
word16 offset = 0;
1495714996
word16 length_offset = 0;
14997+
word32 prevOffset;
1495814998
byte isRequest = (msgType == client_hello ||
1495914999
msgType == certificate_request);
1496015000

@@ -14969,6 +15009,10 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1496915009
if (!IS_OFF(semaphore, TLSX_ToSemaphore((word16)extension->type)))
1497015010
continue; /* skip! */
1497115011

15012+
/* Snapshot offset to detect word16 wrap within this iteration;
15013+
* see matching comment in TLSX_GetSize. */
15014+
prevOffset = offset;
15015+
1497215016
/* writes extension type. */
1497315017
c16toa((word16)extension->type, output + offset);
1497415018
offset += HELLO_EXT_TYPE_SZ + OPAQUE16_LEN;
@@ -15196,6 +15240,16 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1519615240
/* if we encountered an error propagate it */
1519715241
if (ret != 0)
1519815242
break;
15243+
15244+
if (offset < prevOffset) {
15245+
WOLFSSL_MSG("TLSX_Write extension length exceeds word16");
15246+
return BUFFER_E;
15247+
}
15248+
}
15249+
15250+
if ((word32)*pOffset + (word32)offset > WOLFSSL_MAX_16BIT) {
15251+
WOLFSSL_MSG("TLSX_Write total extensions length exceeds word16");
15252+
return BUFFER_E;
1519915253
}
1520015254

1520115255
*pOffset += offset;
@@ -16283,6 +16337,13 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength)
1628316337
}
1628416338
#endif
1628516339

16340+
/* The TLS extensions block length prefix is a 2-byte field, so any
16341+
* accumulated total above 0xFFFF must be rejected rather than silently
16342+
* truncating and producing a short, malformed handshake message. */
16343+
if (length > (word16)(WOLFSSL_MAX_16BIT - OPAQUE16_LEN)) {
16344+
WOLFSSL_MSG("TLSX_GetRequestSize extensions exceed word16");
16345+
return BUFFER_E;
16346+
}
1628616347
if (length)
1628716348
length += OPAQUE16_LEN; /* for total length storage. */
1628816349

@@ -16486,6 +16547,12 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset)
1648616547
#endif
1648716548
#endif
1648816549

16550+
/* Wrap detection for the TLSX_Write calls above is handled inside
16551+
* TLSX_Write itself: any iteration that would push the local word16
16552+
* offset past 0xFFFF returns BUFFER_E so we never reach here with a
16553+
* truncated value. The TLS extensions block length prefix on the
16554+
* wire is a 2-byte field, matching this invariant. */
16555+
1648916556
if (offset > OPAQUE16_LEN || msgType != client_hello)
1649016557
c16toa(offset - OPAQUE16_LEN, output); /* extensions length */
1649116558

src/tls13.c

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4759,8 +4759,20 @@ int SendTls13ClientHello(WOLFSSL* ssl)
47594759
args->ech->type = 0;
47604760
/* set innerClientHelloLen to ClientHelloInner + padding + tag */
47614761
args->ech->paddingLen = 31 - ((args->length - 1) % 32);
4762-
args->ech->innerClientHelloLen = (word16)(args->length +
4763-
args->ech->paddingLen + args->ech->hpke->Nt);
4762+
{
4763+
word32 ichLen = args->length + args->ech->paddingLen +
4764+
args->ech->hpke->Nt;
4765+
/* Guard against word16 truncation: the wire format field
4766+
* for the ECH payload length is two bytes, so anything
4767+
* above 0xFFFF cannot be represented and the silent cast
4768+
* would cause an undersized allocation and heap overflow
4769+
* in the subsequent XMEMCPY. */
4770+
if (ichLen > WOLFSSL_MAX_16BIT) {
4771+
WOLFSSL_MSG("ECH inner ClientHello exceeds word16");
4772+
return BUFFER_E;
4773+
}
4774+
args->ech->innerClientHelloLen = (word16)ichLen;
4775+
}
47644776
/* set the length back to before we computed ClientHelloInner size */
47654777
args->length = (word32)args->preXLength;
47664778
}

tests/api.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10471,6 +10471,67 @@ static int test_tls_ext_duplicate(void)
1047110471
return EXPECT_RESULT();
1047210472
}
1047310473

10474+
/* Regression test: TLSX extension size accumulation must not silently wrap
10475+
* the internal word16 accumulator. Prior to the fix, a single extension
10476+
* whose size (plus the 4-byte header) pushes the running total past 0xFFFF
10477+
* caused TLSX_GetSize / TLSX_Write to return a truncated length, which
10478+
* in turn led to undersized buffer writes. The on-wire extensions block
10479+
* length is a 2-byte field per RFC 8446 Section 4.2, so the correct
10480+
* behavior is to return BUFFER_E rather than wrap. */
10481+
static int test_tls_ext_word16_overflow(void)
10482+
{
10483+
EXPECT_DECLS;
10484+
#if defined(HAVE_SESSION_TICKET) && !defined(NO_WOLFSSL_CLIENT) && \
10485+
!defined(NO_TLS)
10486+
WOLFSSL_CTX* ctx = NULL;
10487+
WOLFSSL* ssl = NULL;
10488+
SessionTicket* ticket = NULL;
10489+
byte* big = NULL;
10490+
/* Size chosen so that 4 (ext header) + size > 0xFFFF. */
10491+
const word16 bigSz = 0xFFFE;
10492+
word32 length = 0;
10493+
10494+
big = (byte*)XMALLOC(bigSz, NULL, DYNAMIC_TYPE_TMP_BUFFER);
10495+
ExpectNotNull(big);
10496+
if (big != NULL)
10497+
XMEMSET(big, 0xA5, bigSz);
10498+
10499+
ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method()));
10500+
ExpectNotNull(ssl = wolfSSL_new(ctx));
10501+
10502+
/* Build an oversized SessionTicket extension directly on the ssl
10503+
* extension list. Going via the public API is not enough here because
10504+
* wolfSSL_set_SessionTicket clamps to word16 without creating the
10505+
* TLSX entry; the TLSX path is what exercises the accumulator. */
10506+
if (EXPECT_SUCCESS()) {
10507+
ticket = TLSX_SessionTicket_Create(0, big, bigSz, ssl->heap);
10508+
ExpectNotNull(ticket);
10509+
}
10510+
if (EXPECT_SUCCESS()) {
10511+
ExpectIntEQ(TLSX_UseSessionTicket(&ssl->extensions, ticket, ssl->heap),
10512+
WOLFSSL_SUCCESS);
10513+
/* TLSX_UseSessionTicket takes ownership on success. */
10514+
ticket = NULL;
10515+
}
10516+
10517+
/* TLSX_GetRequestSize must refuse to encode: 4-byte ext header +
10518+
* 0xFFFE payload + 2-byte block length prefix = 0x10004, which does
10519+
* not fit in a word16 wire length. Expect BUFFER_E, not a silently
10520+
* wrapped small value. */
10521+
if (EXPECT_SUCCESS()) {
10522+
int ret = TLSX_GetRequestSize(ssl, client_hello, &length);
10523+
ExpectIntEQ(ret, BUFFER_E);
10524+
}
10525+
10526+
if (ticket != NULL)
10527+
TLSX_SessionTicket_Free(ticket, ssl->heap);
10528+
wolfSSL_free(ssl);
10529+
wolfSSL_CTX_free(ctx);
10530+
XFREE(big, NULL, DYNAMIC_TYPE_TMP_BUFFER);
10531+
#endif
10532+
return EXPECT_RESULT();
10533+
}
10534+
1047410535

1047510536
/* Test TLS connection abort when legacy version field indicates TLS 1.3 or
1047610537
* higher. Based on test_tls_ext_duplicate() but with legacy version modified
@@ -35773,6 +35834,7 @@ TEST_CASE testCases[] = {
3577335834
TEST_DECL(test_wolfSSL_SCR_Reconnect),
3577435835
TEST_DECL(test_wolfSSL_SCR_check_enabled),
3577535836
TEST_DECL(test_tls_ext_duplicate),
35837+
TEST_DECL(test_tls_ext_word16_overflow),
3577635838
TEST_DECL(test_tls_bad_legacy_version),
3577735839
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
3577835840
#if defined(HAVE_IO_TESTS_DEPENDENCIES)

wolfssl/internal.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3177,9 +3177,9 @@ WOLFSSL_LOCAL int TLSX_SupportExtensions(WOLFSSL* ssl);
31773177
WOLFSSL_LOCAL int TLSX_PopulateExtensions(WOLFSSL* ssl, byte isRequest);
31783178

31793179
#if defined(WOLFSSL_TLS13) || !defined(NO_WOLFSSL_CLIENT)
3180-
WOLFSSL_LOCAL int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType,
3180+
WOLFSSL_TEST_VIS int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType,
31813181
word32* pLength);
3182-
WOLFSSL_LOCAL int TLSX_WriteRequest(WOLFSSL* ssl, byte* output,
3182+
WOLFSSL_TEST_VIS int TLSX_WriteRequest(WOLFSSL* ssl, byte* output,
31833183
byte msgType, word32* pOffset);
31843184
#endif
31853185

@@ -3585,11 +3585,11 @@ typedef struct TicketEncCbCtx {
35853585

35863586
#endif /* !WOLFSSL_NO_DEF_TICKET_ENC_CB && !NO_WOLFSSL_SERVER */
35873587

3588-
WOLFSSL_LOCAL int TLSX_UseSessionTicket(TLSX** extensions,
3588+
WOLFSSL_TEST_VIS int TLSX_UseSessionTicket(TLSX** extensions,
35893589
SessionTicket* ticket, void* heap);
3590-
WOLFSSL_LOCAL SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
3590+
WOLFSSL_TEST_VIS SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
35913591
byte* data, word16 size, void* heap);
3592-
WOLFSSL_LOCAL void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap);
3592+
WOLFSSL_TEST_VIS void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap);
35933593

35943594
#endif /* HAVE_SESSION_TICKET */
35953595

0 commit comments

Comments
 (0)