@@ -6504,7 +6504,7 @@ static int TLSX_SessionTicket_Parse(WOLFSSL* ssl, const byte* input,
65046504 return ret;
65056505}
65066506
6507- WOLFSSL_LOCAL SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
6507+ WOLFSSL_TEST_VIS SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
65086508 byte* data, word16 size, void* heap)
65096509{
65106510 SessionTicket* ticket = (SessionTicket*)XMALLOC(sizeof(SessionTicket),
@@ -6525,7 +6525,7 @@ WOLFSSL_LOCAL SessionTicket* TLSX_SessionTicket_Create(word32 lifetime,
65256525
65266526 return ticket;
65276527}
6528- WOLFSSL_LOCAL void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap)
6528+ WOLFSSL_TEST_VIS void TLSX_SessionTicket_Free(SessionTicket* ticket, void* heap)
65296529{
65306530 if (ticket) {
65316531 XFREE(ticket->data, heap, DYNAMIC_TYPE_TLSX);
@@ -14879,9 +14879,27 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1487914879{
1488014880 int ret = 0;
1488114881 TLSX* extension;
14882- word16 length = 0;
14882+ /* Use a word32 accumulator so that an extension whose contribution
14883+ * pushes the running total past 0xFFFF is detected rather than
14884+ * silently wrapped (the TLS extensions block length prefix on the
14885+ * wire is a 2-byte field). Callees that take a word16* accumulator
14886+ * are invoked via a per-iteration shim (`cbShim`) and their delta
14887+ * is added back into the word32 total.
14888+ *
14889+ * MAINTAINER NOTE: do NOT pass &length to any *_GET_SIZE function
14890+ * that expects a `word16*` out-parameter -- that would be a type
14891+ * mismatch (UB) and would silently bypass the overflow detection
14892+ * below. When adding a new extension case, either:
14893+ * - use `length += FOO_GET_SIZE(...)` when the helper returns a
14894+ * word16 by value, or
14895+ * - use the cbShim pattern: `cbShim = 0; ret = FOO_GET_SIZE(...,
14896+ * &cbShim); length += cbShim;`
14897+ */
14898+ word32 length = 0;
14899+ word16 cbShim = 0;
1488314900 byte isRequest = (msgType == client_hello ||
1488414901 msgType == certificate_request);
14902+ (void)cbShim;
1488514903
1488614904 while ((extension = list)) {
1488714905 list = extension->next;
@@ -14965,23 +14983,31 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1496514983#endif
1496614984#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY)
1496714985 case TLSX_ENCRYPT_THEN_MAC:
14968- ret = ETM_GET_SIZE(msgType, &length);
14986+ cbShim = 0;
14987+ ret = ETM_GET_SIZE(msgType, &cbShim);
14988+ length += cbShim;
1496914989 break;
1497014990#endif /* HAVE_ENCRYPT_THEN_MAC */
1497114991
1497214992#if defined(WOLFSSL_TLS13) || !defined(WOLFSSL_NO_TLS12) || !defined(NO_OLD_TLS)
1497314993 #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
1497414994 case TLSX_PRE_SHARED_KEY:
14995+ cbShim = 0;
1497514996 ret = PSK_GET_SIZE((PreSharedKey*)extension->data, msgType,
14976- &length);
14997+ &cbShim);
14998+ length += cbShim;
1497714999 break;
1497815000 #ifdef WOLFSSL_TLS13
1497915001 case TLSX_PSK_KEY_EXCHANGE_MODES:
14980- ret = PKM_GET_SIZE((byte)extension->val, msgType, &length);
15002+ cbShim = 0;
15003+ ret = PKM_GET_SIZE((byte)extension->val, msgType, &cbShim);
15004+ length += cbShim;
1498115005 break;
1498215006 #ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
1498315007 case TLSX_CERT_WITH_EXTERN_PSK:
14984- ret = PSK_WITH_CERT_GET_SIZE(msgType, &length);
15008+ cbShim = 0;
15009+ ret = PSK_WITH_CERT_GET_SIZE(msgType, &cbShim);
15010+ length += cbShim;
1498515011 break;
1498615012 #endif
1498715013 #endif
@@ -14993,22 +15019,30 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1499315019
1499415020#ifdef WOLFSSL_TLS13
1499515021 case TLSX_SUPPORTED_VERSIONS:
14996- ret = SV_GET_SIZE(extension->data, msgType, &length);
15022+ cbShim = 0;
15023+ ret = SV_GET_SIZE(extension->data, msgType, &cbShim);
15024+ length += cbShim;
1499715025 break;
1499815026
1499915027 case TLSX_COOKIE:
15000- ret = CKE_GET_SIZE((Cookie*)extension->data, msgType, &length);
15028+ cbShim = 0;
15029+ ret = CKE_GET_SIZE((Cookie*)extension->data, msgType, &cbShim);
15030+ length += cbShim;
1500115031 break;
1500215032
1500315033 #ifdef WOLFSSL_EARLY_DATA
1500415034 case TLSX_EARLY_DATA:
15005- ret = EDI_GET_SIZE(msgType, &length);
15035+ cbShim = 0;
15036+ ret = EDI_GET_SIZE(msgType, &cbShim);
15037+ length += cbShim;
1500615038 break;
1500715039 #endif
1500815040
1500915041 #ifdef WOLFSSL_POST_HANDSHAKE_AUTH
1501015042 case TLSX_POST_HANDSHAKE_AUTH:
15011- ret = PHA_GET_SIZE(msgType, &length);
15043+ cbShim = 0;
15044+ ret = PHA_GET_SIZE(msgType, &cbShim);
15045+ length += cbShim;
1501215046 break;
1501315047 #endif
1501415048
@@ -15061,12 +15095,26 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1506115095 break;
1506215096 }
1506315097
15098+ /* Early exit: stop accumulating as soon as the running total
15099+ * cannot possibly fit the 2-byte wire length. Check *before*
15100+ * marking the extension as processed so the semaphore is not
15101+ * left in an inconsistent state on the error path. */
15102+ if (length > WOLFSSL_MAX_16BIT) {
15103+ WOLFSSL_MSG("TLSX_GetSize extension length exceeds word16");
15104+ return BUFFER_E;
15105+ }
15106+
1506415107 /* marks the extension as processed so ctx level */
1506515108 /* extensions don't overlap with ssl level ones. */
1506615109 TURN_ON(semaphore, TLSX_ToSemaphore((word16)extension->type));
1506715110 }
1506815111
15069- *pLength += length;
15112+ if ((word32)*pLength + length > WOLFSSL_MAX_16BIT) {
15113+ WOLFSSL_MSG("TLSX_GetSize total extensions length exceeds word16");
15114+ return BUFFER_E;
15115+ }
15116+
15117+ *pLength += (word16)length;
1507015118
1507115119 return ret;
1507215120}
@@ -15079,6 +15127,7 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1507915127 TLSX* extension;
1508015128 word16 offset = 0;
1508115129 word16 length_offset = 0;
15130+ word32 prevOffset;
1508215131 byte isRequest = (msgType == client_hello ||
1508315132 msgType == certificate_request);
1508415133
@@ -15093,6 +15142,10 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1509315142 if (!IS_OFF(semaphore, TLSX_ToSemaphore((word16)extension->type)))
1509415143 continue; /* skip! */
1509515144
15145+ /* Snapshot offset to detect word16 wrap within this iteration;
15146+ * see matching comment in TLSX_GetSize. */
15147+ prevOffset = offset;
15148+
1509615149 /* writes extension type. */
1509715150 c16toa((word16)extension->type, output + offset);
1509815151 offset += HELLO_EXT_TYPE_SZ + OPAQUE16_LEN;
@@ -15326,9 +15379,24 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1532615379 /* if we encountered an error propagate it */
1532715380 if (ret != 0)
1532815381 break;
15382+
15383+ if (offset <= prevOffset) {
15384+ WOLFSSL_MSG("TLSX_Write extension length exceeds word16");
15385+ return BUFFER_E;
15386+ }
1532915387 }
1533015388
15331- *pOffset += offset;
15389+ /* Only validate and commit the aggregate offset when the loop
15390+ * completed without error; on the error path, leave *pOffset
15391+ * unchanged and return the original failure reason so callers
15392+ * see the real error instead of a masking BUFFER_E. */
15393+ if (ret == 0) {
15394+ if ((word32)*pOffset + (word32)offset > WOLFSSL_MAX_16BIT) {
15395+ WOLFSSL_MSG("TLSX_Write total extensions length exceeds word16");
15396+ return BUFFER_E;
15397+ }
15398+ *pOffset += offset;
15399+ }
1533215400
1533315401 return ret;
1533415402}
@@ -16432,6 +16500,13 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength)
1643216500 }
1643316501#endif
1643416502
16503+ /* The TLS extensions block length prefix is a 2-byte field, so any
16504+ * accumulated total above 0xFFFF must be rejected rather than silently
16505+ * truncating and producing a short, malformed handshake message. */
16506+ if (length > (word16)(WOLFSSL_MAX_16BIT - OPAQUE16_LEN)) {
16507+ WOLFSSL_MSG("TLSX_GetRequestSize extensions exceed word16");
16508+ return BUFFER_E;
16509+ }
1643516510 if (length)
1643616511 length += OPAQUE16_LEN; /* for total length storage. */
1643716512
@@ -16635,6 +16710,12 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset)
1663516710 #endif
1663616711#endif
1663716712
16713+ /* Wrap detection for the TLSX_Write calls above is handled inside
16714+ * TLSX_Write itself: any iteration that would push the local word16
16715+ * offset past 0xFFFF returns BUFFER_E so we never reach here with a
16716+ * truncated value. The TLS extensions block length prefix on the
16717+ * wire is a 2-byte field, matching this invariant. */
16718+
1663816719 if (offset > OPAQUE16_LEN || msgType != client_hello)
1663916720 c16toa(offset - OPAQUE16_LEN, output); /* extensions length */
1664016721
0 commit comments