@@ -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);
@@ -14885,9 +14885,27 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1488514885{
1488614886 int ret = 0;
1488714887 TLSX* extension;
14888- word16 length = 0;
14888+ /* Use a word32 accumulator so that an extension whose contribution
14889+ * pushes the running total past 0xFFFF is detected rather than
14890+ * silently wrapped (the TLS extensions block length prefix on the
14891+ * wire is a 2-byte field). Callees that take a word16* accumulator
14892+ * are invoked via a per-iteration shim (`cbShim`) and their delta
14893+ * is added back into the word32 total.
14894+ *
14895+ * MAINTAINER NOTE: do NOT pass &length to any *_GET_SIZE function
14896+ * that expects a `word16*` out-parameter -- that would be a type
14897+ * mismatch (UB) and would silently bypass the overflow detection
14898+ * below. When adding a new extension case, either:
14899+ * - use `length += FOO_GET_SIZE(...)` when the helper returns a
14900+ * word16 by value, or
14901+ * - use the cbShim pattern: `cbShim = 0; ret = FOO_GET_SIZE(...,
14902+ * &cbShim); length += cbShim;`
14903+ */
14904+ word32 length = 0;
14905+ word16 cbShim = 0;
1488914906 byte isRequest = (msgType == client_hello ||
1489014907 msgType == certificate_request);
14908+ (void)cbShim;
1489114909
1489214910 while ((extension = list)) {
1489314911 list = extension->next;
@@ -14971,23 +14989,31 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1497114989#endif
1497214990#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY)
1497314991 case TLSX_ENCRYPT_THEN_MAC:
14974- ret = ETM_GET_SIZE(msgType, &length);
14992+ cbShim = 0;
14993+ ret = ETM_GET_SIZE(msgType, &cbShim);
14994+ length += cbShim;
1497514995 break;
1497614996#endif /* HAVE_ENCRYPT_THEN_MAC */
1497714997
1497814998#if defined(WOLFSSL_TLS13) || !defined(WOLFSSL_NO_TLS12) || !defined(NO_OLD_TLS)
1497914999 #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
1498015000 case TLSX_PRE_SHARED_KEY:
15001+ cbShim = 0;
1498115002 ret = PSK_GET_SIZE((PreSharedKey*)extension->data, msgType,
14982- &length);
15003+ &cbShim);
15004+ length += cbShim;
1498315005 break;
1498415006 #ifdef WOLFSSL_TLS13
1498515007 case TLSX_PSK_KEY_EXCHANGE_MODES:
14986- ret = PKM_GET_SIZE((byte)extension->val, msgType, &length);
15008+ cbShim = 0;
15009+ ret = PKM_GET_SIZE((byte)extension->val, msgType, &cbShim);
15010+ length += cbShim;
1498715011 break;
1498815012 #ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
1498915013 case TLSX_CERT_WITH_EXTERN_PSK:
14990- ret = PSK_WITH_CERT_GET_SIZE(msgType, &length);
15014+ cbShim = 0;
15015+ ret = PSK_WITH_CERT_GET_SIZE(msgType, &cbShim);
15016+ length += cbShim;
1499115017 break;
1499215018 #endif
1499315019 #endif
@@ -14999,22 +15025,30 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1499915025
1500015026#ifdef WOLFSSL_TLS13
1500115027 case TLSX_SUPPORTED_VERSIONS:
15002- ret = SV_GET_SIZE(extension->data, msgType, &length);
15028+ cbShim = 0;
15029+ ret = SV_GET_SIZE(extension->data, msgType, &cbShim);
15030+ length += cbShim;
1500315031 break;
1500415032
1500515033 case TLSX_COOKIE:
15006- ret = CKE_GET_SIZE((Cookie*)extension->data, msgType, &length);
15034+ cbShim = 0;
15035+ ret = CKE_GET_SIZE((Cookie*)extension->data, msgType, &cbShim);
15036+ length += cbShim;
1500715037 break;
1500815038
1500915039 #ifdef WOLFSSL_EARLY_DATA
1501015040 case TLSX_EARLY_DATA:
15011- ret = EDI_GET_SIZE(msgType, &length);
15041+ cbShim = 0;
15042+ ret = EDI_GET_SIZE(msgType, &cbShim);
15043+ length += cbShim;
1501215044 break;
1501315045 #endif
1501415046
1501515047 #ifdef WOLFSSL_POST_HANDSHAKE_AUTH
1501615048 case TLSX_POST_HANDSHAKE_AUTH:
15017- ret = PHA_GET_SIZE(msgType, &length);
15049+ cbShim = 0;
15050+ ret = PHA_GET_SIZE(msgType, &cbShim);
15051+ length += cbShim;
1501815052 break;
1501915053 #endif
1502015054
@@ -15067,12 +15101,26 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1506715101 break;
1506815102 }
1506915103
15104+ /* Early exit: stop accumulating as soon as the running total
15105+ * cannot possibly fit the 2-byte wire length. Check *before*
15106+ * marking the extension as processed so the semaphore is not
15107+ * left in an inconsistent state on the error path. */
15108+ if (length > WOLFSSL_MAX_16BIT) {
15109+ WOLFSSL_MSG("TLSX_GetSize extension length exceeds word16");
15110+ return BUFFER_E;
15111+ }
15112+
1507015113 /* marks the extension as processed so ctx level */
1507115114 /* extensions don't overlap with ssl level ones. */
1507215115 TURN_ON(semaphore, TLSX_ToSemaphore((word16)extension->type));
1507315116 }
1507415117
15075- *pLength += length;
15118+ if ((word32)*pLength + length > WOLFSSL_MAX_16BIT) {
15119+ WOLFSSL_MSG("TLSX_GetSize total extensions length exceeds word16");
15120+ return BUFFER_E;
15121+ }
15122+
15123+ *pLength += (word16)length;
1507615124
1507715125 return ret;
1507815126}
@@ -15083,10 +15131,20 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1508315131{
1508415132 int ret = 0;
1508515133 TLSX* extension;
15086- word16 offset = 0;
15087- word16 length_offset = 0;
15134+ /* Use word32 to symmetrize with TLSX_GetSize -- a single extension can
15135+ * contribute up to 0x10003 bytes (4-byte type/length header + 0xFFFF
15136+ * payload), which would word16-overflow undetectably (e.g. wrap to a
15137+ * value still above prevOffset). Per-iteration and aggregate bounds are
15138+ * checked below before truncating back into the word16 wire fields.
15139+ * Callees that take a word16* offset use the cbShim pattern (init to 0,
15140+ * then add the returned delta to the word32 accumulator). */
15141+ word32 offset = 0;
15142+ word32 length_offset = 0;
15143+ word32 prevOffset;
15144+ word16 cbShim = 0;
1508815145 byte isRequest = (msgType == client_hello ||
1508915146 msgType == certificate_request);
15147+ (void)cbShim;
1509015148
1509115149 while ((extension = list)) {
1509215150 list = extension->next;
@@ -15099,6 +15157,10 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1509915157 if (!IS_OFF(semaphore, TLSX_ToSemaphore((word16)extension->type)))
1510015158 continue; /* skip! */
1510115159
15160+ /* Snapshot offset to detect word16 wrap within this iteration;
15161+ * see matching comment in TLSX_GetSize. */
15162+ prevOffset = offset;
15163+
1510215164 /* writes extension type. */
1510315165 c16toa((word16)extension->type, output + offset);
1510415166 offset += HELLO_EXT_TYPE_SZ + OPAQUE16_LEN;
@@ -15202,28 +15264,36 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1520215264#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY)
1520315265 case TLSX_ENCRYPT_THEN_MAC:
1520415266 WOLFSSL_MSG("Encrypt-Then-Mac extension to write");
15205- ret = ETM_WRITE(extension->data, output, msgType, &offset);
15267+ cbShim = 0;
15268+ ret = ETM_WRITE(extension->data, output, msgType, &cbShim);
15269+ offset += cbShim;
1520615270 break;
1520715271#endif /* HAVE_ENCRYPT_THEN_MAC */
1520815272
1520915273#if defined(WOLFSSL_TLS13) || !defined(WOLFSSL_NO_TLS12) || !defined(NO_OLD_TLS)
1521015274 #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
1521115275 case TLSX_PRE_SHARED_KEY:
1521215276 WOLFSSL_MSG("Pre-Shared Key extension to write");
15277+ cbShim = 0;
1521315278 ret = PSK_WRITE((PreSharedKey*)extension->data, output + offset,
15214- msgType, &offset);
15279+ msgType, &cbShim);
15280+ offset += cbShim;
1521515281 break;
1521615282
1521715283 #ifdef WOLFSSL_TLS13
1521815284 case TLSX_PSK_KEY_EXCHANGE_MODES:
1521915285 WOLFSSL_MSG("PSK Key Exchange Modes extension to write");
15286+ cbShim = 0;
1522015287 ret = PKM_WRITE((byte)extension->val, output + offset, msgType,
15221- &offset);
15288+ &cbShim);
15289+ offset += cbShim;
1522215290 break;
1522315291 #ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
1522415292 case TLSX_CERT_WITH_EXTERN_PSK:
1522515293 WOLFSSL_MSG("Cert with external PSK extension to write");
15226- ret = PSK_WITH_CERT_WRITE(output + offset, msgType, &offset);
15294+ cbShim = 0;
15295+ ret = PSK_WITH_CERT_WRITE(output + offset, msgType, &cbShim);
15296+ offset += cbShim;
1522715297 break;
1522815298 #endif
1522915299 #endif
@@ -15237,28 +15307,36 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1523715307#ifdef WOLFSSL_TLS13
1523815308 case TLSX_SUPPORTED_VERSIONS:
1523915309 WOLFSSL_MSG("Supported Versions extension to write");
15310+ cbShim = 0;
1524015311 ret = SV_WRITE(extension->data, output + offset, msgType,
15241- &offset);
15312+ &cbShim);
15313+ offset += cbShim;
1524215314 break;
1524315315
1524415316 case TLSX_COOKIE:
1524515317 WOLFSSL_MSG("Cookie extension to write");
15318+ cbShim = 0;
1524615319 ret = CKE_WRITE((Cookie*)extension->data, output + offset,
15247- msgType, &offset);
15320+ msgType, &cbShim);
15321+ offset += cbShim;
1524815322 break;
1524915323
1525015324 #ifdef WOLFSSL_EARLY_DATA
1525115325 case TLSX_EARLY_DATA:
1525215326 WOLFSSL_MSG("Early Data extension to write");
15327+ cbShim = 0;
1525315328 ret = EDI_WRITE(extension->val, output + offset, msgType,
15254- &offset);
15329+ &cbShim);
15330+ offset += cbShim;
1525515331 break;
1525615332 #endif
1525715333
1525815334 #ifdef WOLFSSL_POST_HANDSHAKE_AUTH
1525915335 case TLSX_POST_HANDSHAKE_AUTH:
1526015336 WOLFSSL_MSG("Post-Handshake Authentication extension to write");
15261- ret = PHA_WRITE(output + offset, msgType, &offset);
15337+ cbShim = 0;
15338+ ret = PHA_WRITE(output + offset, msgType, &cbShim);
15339+ offset += cbShim;
1526215340 break;
1526315341 #endif
1526415342
@@ -15314,16 +15392,26 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1531415392#if defined(WOLFSSL_TLS13) && defined(HAVE_ECH)
1531515393 case TLSX_ECH:
1531615394 WOLFSSL_MSG("ECH extension to write");
15395+ cbShim = 0;
1531715396 ret = ECH_WRITE((WOLFSSL_ECH*)extension->data, msgType,
15318- output + offset, &offset);
15397+ output + offset, &cbShim);
15398+ offset += cbShim;
1531915399 break;
1532015400#endif
1532115401 default:
1532215402 break;
1532315403 }
1532415404
15405+ /* Per-extension data length is a 2-byte wire field; reject any
15406+ * single extension whose payload exceeds that before truncating. */
15407+ if (offset - length_offset > WOLFSSL_MAX_16BIT) {
15408+ WOLFSSL_MSG("TLSX_Write single extension length exceeds word16");
15409+ return BUFFER_E;
15410+ }
15411+
1532515412 /* writes extension data length. */
15326- c16toa(offset - length_offset, output + length_offset - OPAQUE16_LEN);
15413+ c16toa((word16)(offset - length_offset),
15414+ output + length_offset - OPAQUE16_LEN);
1532715415
1532815416 /* marks the extension as processed so ctx level */
1532915417 /* extensions don't overlap with ssl level ones. */
@@ -15332,9 +15420,24 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1533215420 /* if we encountered an error propagate it */
1533315421 if (ret != 0)
1533415422 break;
15423+
15424+ if (offset <= prevOffset) {
15425+ WOLFSSL_MSG("TLSX_Write extension made no progress");
15426+ return BUFFER_E;
15427+ }
1533515428 }
1533615429
15337- *pOffset += offset;
15430+ /* Only validate and commit the aggregate offset when the loop
15431+ * completed without error; on the error path, leave *pOffset
15432+ * unchanged and return the original failure reason so callers
15433+ * see the real error instead of a masking BUFFER_E. */
15434+ if (ret == 0) {
15435+ if ((word32)*pOffset + offset > WOLFSSL_MAX_16BIT) {
15436+ WOLFSSL_MSG("TLSX_Write total extensions length exceeds word16");
15437+ return BUFFER_E;
15438+ }
15439+ *pOffset += (word16)offset;
15440+ }
1533815441
1533915442 return ret;
1534015443}
@@ -16438,6 +16541,13 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength)
1643816541 }
1643916542#endif
1644016543
16544+ /* The TLS extensions block length prefix is a 2-byte field, so any
16545+ * accumulated total above 0xFFFF must be rejected rather than silently
16546+ * truncating and producing a short, malformed handshake message. */
16547+ if (length > (word16)(WOLFSSL_MAX_16BIT - OPAQUE16_LEN)) {
16548+ WOLFSSL_MSG("TLSX_GetRequestSize extensions exceed word16");
16549+ return BUFFER_E;
16550+ }
1644116551 if (length)
1644216552 length += OPAQUE16_LEN; /* for total length storage. */
1644316553
@@ -16641,6 +16751,12 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset)
1664116751 #endif
1664216752#endif
1664316753
16754+ /* Wrap detection for the TLSX_Write calls above is handled inside
16755+ * TLSX_Write itself: any iteration that would push the local word16
16756+ * offset past 0xFFFF returns BUFFER_E so we never reach here with a
16757+ * truncated value. The TLS extensions block length prefix on the
16758+ * wire is a 2-byte field, matching this invariant. */
16759+
1664416760 if (offset > OPAQUE16_LEN || msgType != client_hello)
1664516761 c16toa(offset - OPAQUE16_LEN, output); /* extensions length */
1664616762
0 commit comments