@@ -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,9 +14868,27 @@ 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 (`cbShim`) and their delta
14876+ * is added back into the word32 total.
14877+ *
14878+ * MAINTAINER NOTE: do NOT pass &length to any *_GET_SIZE function
14879+ * that expects a `word16*` out-parameter -- that would be a type
14880+ * mismatch (UB) and would silently bypass the overflow detection
14881+ * below. When adding a new extension case, either:
14882+ * - use `length += FOO_GET_SIZE(...)` when the helper returns a
14883+ * word16 by value, or
14884+ * - use the cbShim pattern: `cbShim = 0; ret = FOO_GET_SIZE(...,
14885+ * &cbShim); length += cbShim;`
14886+ */
14887+ word32 length = 0;
14888+ word16 cbShim = 0;
1487214889 byte isRequest = (msgType == client_hello ||
1487314890 msgType == certificate_request);
14891+ (void)cbShim;
1487414892
1487514893 while ((extension = list)) {
1487614894 list = extension->next;
@@ -14954,23 +14972,31 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1495414972#endif
1495514973#if defined(HAVE_ENCRYPT_THEN_MAC) && !defined(WOLFSSL_AEAD_ONLY)
1495614974 case TLSX_ENCRYPT_THEN_MAC:
14957- ret = ETM_GET_SIZE(msgType, &length);
14975+ cbShim = 0;
14976+ ret = ETM_GET_SIZE(msgType, &cbShim);
14977+ length += cbShim;
1495814978 break;
1495914979#endif /* HAVE_ENCRYPT_THEN_MAC */
1496014980
1496114981#if defined(WOLFSSL_TLS13) || !defined(WOLFSSL_NO_TLS12) || !defined(NO_OLD_TLS)
1496214982 #if defined(HAVE_SESSION_TICKET) || !defined(NO_PSK)
1496314983 case TLSX_PRE_SHARED_KEY:
14984+ cbShim = 0;
1496414985 ret = PSK_GET_SIZE((PreSharedKey*)extension->data, msgType,
14965- &length);
14986+ &cbShim);
14987+ length += cbShim;
1496614988 break;
1496714989 #ifdef WOLFSSL_TLS13
1496814990 case TLSX_PSK_KEY_EXCHANGE_MODES:
14969- ret = PKM_GET_SIZE((byte)extension->val, msgType, &length);
14991+ cbShim = 0;
14992+ ret = PKM_GET_SIZE((byte)extension->val, msgType, &cbShim);
14993+ length += cbShim;
1497014994 break;
1497114995 #ifdef WOLFSSL_CERT_WITH_EXTERN_PSK
1497214996 case TLSX_CERT_WITH_EXTERN_PSK:
14973- ret = PSK_WITH_CERT_GET_SIZE(msgType, &length);
14997+ cbShim = 0;
14998+ ret = PSK_WITH_CERT_GET_SIZE(msgType, &cbShim);
14999+ length += cbShim;
1497415000 break;
1497515001 #endif
1497615002 #endif
@@ -14982,22 +15008,30 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1498215008
1498315009#ifdef WOLFSSL_TLS13
1498415010 case TLSX_SUPPORTED_VERSIONS:
14985- ret = SV_GET_SIZE(extension->data, msgType, &length);
15011+ cbShim = 0;
15012+ ret = SV_GET_SIZE(extension->data, msgType, &cbShim);
15013+ length += cbShim;
1498615014 break;
1498715015
1498815016 case TLSX_COOKIE:
14989- ret = CKE_GET_SIZE((Cookie*)extension->data, msgType, &length);
15017+ cbShim = 0;
15018+ ret = CKE_GET_SIZE((Cookie*)extension->data, msgType, &cbShim);
15019+ length += cbShim;
1499015020 break;
1499115021
1499215022 #ifdef WOLFSSL_EARLY_DATA
1499315023 case TLSX_EARLY_DATA:
14994- ret = EDI_GET_SIZE(msgType, &length);
15024+ cbShim = 0;
15025+ ret = EDI_GET_SIZE(msgType, &cbShim);
15026+ length += cbShim;
1499515027 break;
1499615028 #endif
1499715029
1499815030 #ifdef WOLFSSL_POST_HANDSHAKE_AUTH
1499915031 case TLSX_POST_HANDSHAKE_AUTH:
15000- ret = PHA_GET_SIZE(msgType, &length);
15032+ cbShim = 0;
15033+ ret = PHA_GET_SIZE(msgType, &cbShim);
15034+ length += cbShim;
1500115035 break;
1500215036 #endif
1500315037
@@ -15050,12 +15084,26 @@ static int TLSX_GetSize(TLSX* list, byte* semaphore, byte msgType,
1505015084 break;
1505115085 }
1505215086
15087+ /* Early exit: stop accumulating as soon as the running total
15088+ * cannot possibly fit the 2-byte wire length. Check *before*
15089+ * marking the extension as processed so the semaphore is not
15090+ * left in an inconsistent state on the error path. */
15091+ if (length > WOLFSSL_MAX_16BIT) {
15092+ WOLFSSL_MSG("TLSX_GetSize extension length exceeds word16");
15093+ return BUFFER_E;
15094+ }
15095+
1505315096 /* marks the extension as processed so ctx level */
1505415097 /* extensions don't overlap with ssl level ones. */
1505515098 TURN_ON(semaphore, TLSX_ToSemaphore((word16)extension->type));
1505615099 }
1505715100
15058- *pLength += length;
15101+ if ((word32)*pLength + length > WOLFSSL_MAX_16BIT) {
15102+ WOLFSSL_MSG("TLSX_GetSize total extensions length exceeds word16");
15103+ return BUFFER_E;
15104+ }
15105+
15106+ *pLength += (word16)length;
1505915107
1506015108 return ret;
1506115109}
@@ -15068,6 +15116,7 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1506815116 TLSX* extension;
1506915117 word16 offset = 0;
1507015118 word16 length_offset = 0;
15119+ word32 prevOffset;
1507115120 byte isRequest = (msgType == client_hello ||
1507215121 msgType == certificate_request);
1507315122
@@ -15082,6 +15131,10 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1508215131 if (!IS_OFF(semaphore, TLSX_ToSemaphore((word16)extension->type)))
1508315132 continue; /* skip! */
1508415133
15134+ /* Snapshot offset to detect word16 wrap within this iteration;
15135+ * see matching comment in TLSX_GetSize. */
15136+ prevOffset = offset;
15137+
1508515138 /* writes extension type. */
1508615139 c16toa((word16)extension->type, output + offset);
1508715140 offset += HELLO_EXT_TYPE_SZ + OPAQUE16_LEN;
@@ -15315,9 +15368,24 @@ static int TLSX_Write(TLSX* list, byte* output, byte* semaphore,
1531515368 /* if we encountered an error propagate it */
1531615369 if (ret != 0)
1531715370 break;
15371+
15372+ if (offset <= prevOffset) {
15373+ WOLFSSL_MSG("TLSX_Write extension length exceeds word16");
15374+ return BUFFER_E;
15375+ }
1531815376 }
1531915377
15320- *pOffset += offset;
15378+ /* Only validate and commit the aggregate offset when the loop
15379+ * completed without error; on the error path, leave *pOffset
15380+ * unchanged and return the original failure reason so callers
15381+ * see the real error instead of a masking BUFFER_E. */
15382+ if (ret == 0) {
15383+ if ((word32)*pOffset + (word32)offset > WOLFSSL_MAX_16BIT) {
15384+ WOLFSSL_MSG("TLSX_Write total extensions length exceeds word16");
15385+ return BUFFER_E;
15386+ }
15387+ *pOffset += offset;
15388+ }
1532115389
1532215390 return ret;
1532315391}
@@ -16421,6 +16489,13 @@ int TLSX_GetRequestSize(WOLFSSL* ssl, byte msgType, word32* pLength)
1642116489 }
1642216490#endif
1642316491
16492+ /* The TLS extensions block length prefix is a 2-byte field, so any
16493+ * accumulated total above 0xFFFF must be rejected rather than silently
16494+ * truncating and producing a short, malformed handshake message. */
16495+ if (length > (word16)(WOLFSSL_MAX_16BIT - OPAQUE16_LEN)) {
16496+ WOLFSSL_MSG("TLSX_GetRequestSize extensions exceed word16");
16497+ return BUFFER_E;
16498+ }
1642416499 if (length)
1642516500 length += OPAQUE16_LEN; /* for total length storage. */
1642616501
@@ -16624,6 +16699,12 @@ int TLSX_WriteRequest(WOLFSSL* ssl, byte* output, byte msgType, word32* pOffset)
1662416699 #endif
1662516700#endif
1662616701
16702+ /* Wrap detection for the TLSX_Write calls above is handled inside
16703+ * TLSX_Write itself: any iteration that would push the local word16
16704+ * offset past 0xFFFF returns BUFFER_E so we never reach here with a
16705+ * truncated value. The TLS extensions block length prefix on the
16706+ * wire is a 2-byte field, matching this invariant. */
16707+
1662716708 if (offset > OPAQUE16_LEN || msgType != client_hello)
1662816709 c16toa(offset - OPAQUE16_LEN, output); /* extensions length */
1662916710
0 commit comments