Skip to content

Commit 265b8f6

Browse files
committed
Fix TLS ext bounds checking
1 parent b573823 commit 265b8f6

3 files changed

Lines changed: 139 additions & 16 deletions

File tree

src/tls.c

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6493,7 +6493,7 @@ static int TLSX_SessionTicket_Parse(WOLFSSL* ssl, const byte* input,
64936493
return ret;
64946494
}
64956495

6496-
WOLFSSL_LOCAL SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
6496+
WOLFSSL_TEST_VIS SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
64976497
byte* data, word16 size, void* heap)
64986498
{
64996499
SessionTicket* ticket = (SessionTicket*)XMALLOC(sizeof(SessionTicket),
@@ -6514,7 +6514,7 @@ WOLFSSL_LOCAL SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
65146514

65156515
return ticket;
65166516
}
6517-
WOLFSSL_LOCAL void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap)
6517+
WOLFSSL_TEST_VIS void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap)
65186518
{
65196519
if (ticket) {
65206520
XFREE(ticket->data, heap, DYNAMIC_TYPE_TLSX);
@@ -14868,7 +14868,14 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1486814868
{
1486914869
int ret = 0;
1487014870
TLSX* extension;
14871-
word16 length = 0;
14871+
/* Use a word32 accumulator so that an extension whose contribution
14872+
* pushes the running total past 0xFFFF is detected rather than
14873+
* silently wrapped (the TLS extensions block length prefix on the
14874+
* wire is a 2-byte field). Callees that take a word16* accumulator
14875+
* are invoked via a per-iteration shim and their delta is added
14876+
* back into the word32 total. */
14877+
word32 length = 0;
14878+
word16 cbShim;
1487214879
byte isRequest = (msgType == client_hello ||
1487314880
msgType == certificate_request);
1487414881

@@ -14954,19 +14961,25 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1495414961
#endif
1495514962
#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY)
1495614963
case TLSX_ENCRYPT_THEN_MAC:
14957-
ret = ETM_GET_SIZE(msgType, &length);
14964+
cbShim = 0;
14965+
ret = ETM_GET_SIZE(msgType, &cbShim);
14966+
length += cbShim;
1495814967
break;
1495914968
#endif /* HAVE_ENCRYPT_THEN_MAC */
1496014969

1496114970
#if defined(WOLFSSL_TLS13) || !defined(WOLFSSL_NO_TLS12) || !defined(NO_OLD_TLS)
1496214971
#if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
1496314972
case TLSX_PRE_SHARED_KEY:
14973+
cbShim = 0;
1496414974
ret = PSK_GET_SIZE((PreSharedKey*)extension->data, msgType,
14965-
&length);
14975+
&cbShim);
14976+
length += cbShim;
1496614977
break;
1496714978
#ifdef WOLFSSL_TLS13
1496814979
case TLSX_PSK_KEY_EXCHANGE_MODES:
14969-
ret = PKM_GET_SIZE((byte)extension->val, msgType, &length);
14980+
cbShim = 0;
14981+
ret = PKM_GET_SIZE((byte)extension->val, msgType, &cbShim);
14982+
length += cbShim;
1497014983
break;
1497114984
#ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
1497214985
case TLSX_CERT_WITH_EXTERN_PSK:
@@ -14982,22 +14995,30 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1498214995

1498314996
#ifdef WOLFSSL_TLS13
1498414997
case TLSX_SUPPORTED_VERSIONS:
14985-
ret = SV_GET_SIZE(extension->data, msgType, &length);
14998+
cbShim = 0;
14999+
ret = SV_GET_SIZE(extension->data, msgType, &cbShim);
15000+
length += cbShim;
1498615001
break;
1498715002

1498815003
case TLSX_COOKIE:
14989-
ret = CKE_GET_SIZE((Cookie*)extension->data, msgType, &length);
15004+
cbShim = 0;
15005+
ret = CKE_GET_SIZE((Cookie*)extension->data, msgType, &cbShim);
15006+
length += cbShim;
1499015007
break;
1499115008

1499215009
#ifdef WOLFSSL_EARLY_DATA
1499315010
case TLSX_EARLY_DATA:
14994-
ret = EDI_GET_SIZE(msgType, &length);
15011+
cbShim = 0;
15012+
ret = EDI_GET_SIZE(msgType, &cbShim);
15013+
length += cbShim;
1499515014
break;
1499615015
#endif
1499715016

1499815017
#ifdef WOLFSSL_POST_HANDSHAKE_AUTH
1499915018
case TLSX_POST_HANDSHAKE_AUTH:
15000-
ret = PHA_GET_SIZE(msgType, &length);
15019+
cbShim = 0;
15020+
ret = PHA_GET_SIZE(msgType, &cbShim);
15021+
length += cbShim;
1500115022
break;
1500215023
#endif
1500315024

@@ -15053,9 +15074,21 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1505315074
/* marks the extension as processed so ctx level */
1505415075
/* extensions don't overlap with ssl level ones. */
1505515076
TURN_ON(semaphore, TLSX_ToSemaphore((word16)extension->type));
15077+
15078+
/* Early exit: stop accumulating as soon as the running total
15079+
* cannot possibly fit the 2-byte wire length. */
15080+
if (length > WOLFSSL_MAX_16BIT) {
15081+
WOLFSSL_MSG("TLSX_GetSize extension length exceeds word16");
15082+
return BUFFER_E;
15083+
}
1505615084
}
1505715085

15058-
*pLength += length;
15086+
if ((word32)*pLength + length > WOLFSSL_MAX_16BIT) {
15087+
WOLFSSL_MSG("TLSX_GetSize total extensions length exceeds word16");
15088+
return BUFFER_E;
15089+
}
15090+
15091+
*pLength += (word16)length;
1505915092

1506015093
return ret;
1506115094
}
@@ -15068,6 +15101,7 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1506815101
TLSX* extension;
1506915102
word16 offset = 0;
1507015103
word16 length_offset = 0;
15104+
word32 prevOffset;
1507115105
byte isRequest = (msgType == client_hello ||
1507215106
msgType == certificate_request);
1507315107

@@ -15082,6 +15116,10 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1508215116
if (!IS_OFF(semaphore, TLSX_ToSemaphore((word16)extension->type)))
1508315117
continue; /* skip! */
1508415118

15119+
/* Snapshot offset to detect word16 wrap within this iteration;
15120+
* see matching comment in TLSX_GetSize. */
15121+
prevOffset = offset;
15122+
1508515123
/* writes extension type. */
1508615124
c16toa((word16)extension->type, output + offset);
1508715125
offset += HELLO_EXT_TYPE_SZ + OPAQUE16_LEN;
@@ -15315,6 +15353,16 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1531515353
/* if we encountered an error propagate it */
1531615354
if (ret != 0)
1531715355
break;
15356+
15357+
if (offset < prevOffset) {
15358+
WOLFSSL_MSG("TLSX_Write extension length exceeds word16");
15359+
return BUFFER_E;
15360+
}
15361+
}
15362+
15363+
if ((word32)*pOffset + (word32)offset > WOLFSSL_MAX_16BIT) {
15364+
WOLFSSL_MSG("TLSX_Write total extensions length exceeds word16");
15365+
return BUFFER_E;
1531815366
}
1531915367

1532015368
*pOffset += offset;
@@ -16421,6 +16469,13 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength)
1642116469
}
1642216470
#endif
1642316471

16472+
/* The TLS extensions block length prefix is a 2-byte field, so any
16473+
* accumulated total above 0xFFFF must be rejected rather than silently
16474+
* truncating and producing a short, malformed handshake message. */
16475+
if (length > (word16)(WOLFSSL_MAX_16BIT - OPAQUE16_LEN)) {
16476+
WOLFSSL_MSG("TLSX_GetRequestSize extensions exceed word16");
16477+
return BUFFER_E;
16478+
}
1642416479
if (length)
1642516480
length += OPAQUE16_LEN; /* for total length storage. */
1642616481

@@ -16624,6 +16679,12 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset)
1662416679
#endif
1662516680
#endif
1662616681

16682+
/* Wrap detection for the TLSX_Write calls above is handled inside
16683+
* TLSX_Write itself: any iteration that would push the local word16
16684+
* offset past 0xFFFF returns BUFFER_E so we never reach here with a
16685+
* truncated value. The TLS extensions block length prefix on the
16686+
* wire is a 2-byte field, matching this invariant. */
16687+
1662716688
if (offset > OPAQUE16_LEN || msgType != client_hello)
1662816689
c16toa(offset - OPAQUE16_LEN, output); /* extensions length */
1662916690

tests/api.c

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10545,6 +10545,67 @@ static int test_tls_ext_duplicate(void)
1054510545
return EXPECT_RESULT();
1054610546
}
1054710547

10548+
/* Regression test: TLSX extension size accumulation must not silently wrap
10549+
* the internal word16 accumulator. Prior to the fix, a single extension
10550+
* whose size (plus the 4-byte header) pushes the running total past 0xFFFF
10551+
* caused TLSX_GetSize / TLSX_Write to return a truncated length, which
10552+
* in turn led to undersized buffer writes. The on-wire extensions block
10553+
* length is a 2-byte field per RFC 8446 Section 4.2, so the correct
10554+
* behavior is to return BUFFER_E rather than wrap. */
10555+
static int test_tls_ext_word16_overflow(void)
10556+
{
10557+
EXPECT_DECLS;
10558+
#if defined(HAVE_SESSION_TICKET) && !defined(NO_WOLFSSL_CLIENT) && \
10559+
!defined(NO_TLS)
10560+
WOLFSSL_CTX* ctx = NULL;
10561+
WOLFSSL* ssl = NULL;
10562+
SessionTicket* ticket = NULL;
10563+
byte* big = NULL;
10564+
/* Size chosen so that 4 (ext header) + size > 0xFFFF. */
10565+
const word16 bigSz = 0xFFFE;
10566+
word32 length = 0;
10567+
10568+
big = (byte*)XMALLOC(bigSz, NULL, DYNAMIC_TYPE_TMP_BUFFER);
10569+
ExpectNotNull(big);
10570+
if (big != NULL)
10571+
XMEMSET(big, 0xA5, bigSz);
10572+
10573+
ExpectNotNull(ctx = wolfSSL_CTX_new(wolfSSLv23_client_method()));
10574+
ExpectNotNull(ssl = wolfSSL_new(ctx));
10575+
10576+
/* Build an oversized SessionTicket extension directly on the ssl
10577+
* extension list. Going via the public API is not enough here because
10578+
* wolfSSL_set_SessionTicket clamps to word16 without creating the
10579+
* TLSX entry; the TLSX path is what exercises the accumulator. */
10580+
if (EXPECT_SUCCESS()) {
10581+
ticket = TLSX_SessionTicket_Create(0, big, bigSz, ssl->heap);
10582+
ExpectNotNull(ticket);
10583+
}
10584+
if (EXPECT_SUCCESS()) {
10585+
ExpectIntEQ(TLSX_UseSessionTicket(&ssl->extensions, ticket, ssl->heap),
10586+
WOLFSSL_SUCCESS);
10587+
/* TLSX_UseSessionTicket takes ownership on success. */
10588+
ticket = NULL;
10589+
}
10590+
10591+
/* TLSX_GetRequestSize must refuse to encode: 4-byte ext header +
10592+
* 0xFFFE payload + 2-byte block length prefix = 0x10004, which does
10593+
* not fit in a word16 wire length. Expect BUFFER_E, not a silently
10594+
* wrapped small value. */
10595+
if (EXPECT_SUCCESS()) {
10596+
int ret = TLSX_GetRequestSize(ssl, client_hello, &length);
10597+
ExpectIntEQ(ret, BUFFER_E);
10598+
}
10599+
10600+
if (ticket != NULL)
10601+
TLSX_SessionTicket_Free(ticket, ssl->heap);
10602+
wolfSSL_free(ssl);
10603+
wolfSSL_CTX_free(ctx);
10604+
XFREE(big, NULL, DYNAMIC_TYPE_TMP_BUFFER);
10605+
#endif
10606+
return EXPECT_RESULT();
10607+
}
10608+
1054810609

1054910610
/* Test TLS connection abort when legacy version field indicates TLS 1.3 or
1055010611
* higher. Based on test_tls_ext_duplicate() but with legacy version modified
@@ -36630,6 +36691,7 @@ TEST_CASE testCases[] = {
3663036691
TEST_DECL(test_wolfSSL_SCR_Reconnect),
3663136692
TEST_DECL(test_wolfSSL_SCR_check_enabled),
3663236693
TEST_DECL(test_tls_ext_duplicate),
36694+
TEST_DECL(test_tls_ext_word16_overflow),
3663336695
TEST_DECL(test_tls_bad_legacy_version),
3663436696
#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
3663536697
#if defined(HAVE_IO_TESTS_DEPENDENCIES)

wolfssl/internal.h

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

31913191
#if defined(WOLFSSL_TLS13) || !defined(NO_WOLFSSL_CLIENT)
3192-
WOLFSSL_LOCAL int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType,
3192+
WOLFSSL_TEST_VIS int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType,
31933193
word32* pLength);
3194-
WOLFSSL_LOCAL int TLSX_WriteRequest(WOLFSSL* ssl, byte* output,
3194+
WOLFSSL_TEST_VIS int TLSX_WriteRequest(WOLFSSL* ssl, byte* output,
31953195
byte msgType, word32* pOffset);
31963196
#endif
31973197

@@ -3597,11 +3597,11 @@ typedef struct TicketEncCbCtx {
35973597

35983598
#endif /* !WOLFSSL_NO_DEF_TICKET_ENC_CB && !NO_WOLFSSL_SERVER */
35993599

3600-
WOLFSSL_LOCAL int TLSX_UseSessionTicket(TLSX** extensions,
3600+
WOLFSSL_TEST_VIS int TLSX_UseSessionTicket(TLSX** extensions,
36013601
SessionTicket* ticket, void* heap);
3602-
WOLFSSL_LOCAL SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
3602+
WOLFSSL_TEST_VIS SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
36033603
byte* data, word16 size, void* heap);
3604-
WOLFSSL_LOCAL void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap);
3604+
WOLFSSL_TEST_VIS void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap);
36053605

36063606
#endif /* HAVE_SESSION_TICKET */
36073607

0 commit comments

Comments
 (0)