@@ -545,3 +545,135 @@ int test_certificate_authorities_client_hello(void) {
545545#endif
546546 return EXPECT_RESULT ();
547547}
548+
549+ #if defined(HAVE_SNI ) && !defined(NO_WOLFSSL_CLIENT )
550+ /* Walk the TLSX list to find an extension by type. */
551+ static TLSX * test_TLSX_find_ext_sni (TLSX * list , TLSX_Type type )
552+ {
553+ while (list ) {
554+ if (list -> type == type )
555+ return list ;
556+ list = list -> next ;
557+ }
558+ return NULL ;
559+ }
560+
561+ /* Replicate the SNI size calculation to test overflow protection.
562+ * The fixed TLSX_SNI_GetSize uses word32 internally and returns 0 on
563+ * overflow. This helper mirrors that logic so we can verify the behavior. */
564+ static word16 test_SNI_GetSize (SNI * list )
565+ {
566+ SNI * sni ;
567+ word32 length = OPAQUE16_LEN ;
568+
569+ while ((sni = list )) {
570+ list = sni -> next ;
571+ length += ENUM_LEN + OPAQUE16_LEN ;
572+ switch (sni -> type ) {
573+ case WOLFSSL_SNI_HOST_NAME :
574+ length += (word16 )XSTRLEN ((char * )sni -> data .host_name );
575+ break ;
576+ }
577+ if (length > WOLFSSL_MAX_16BIT )
578+ return 0 ;
579+ }
580+ return (word16 )length ;
581+ }
582+
583+ /* Replicate the old (vulnerable) SNI size calculation using word16 to
584+ * demonstrate the overflow that the fix prevents. */
585+ static word16 test_SNI_GetSize_old (SNI * list )
586+ {
587+ SNI * sni ;
588+ word16 length = OPAQUE16_LEN ;
589+
590+ while ((sni = list )) {
591+ list = sni -> next ;
592+ length += ENUM_LEN + OPAQUE16_LEN ;
593+ switch (sni -> type ) {
594+ case WOLFSSL_SNI_HOST_NAME :
595+ length += (word16 )XSTRLEN ((char * )sni -> data .host_name );
596+ break ;
597+ }
598+ }
599+ return length ;
600+ }
601+
602+ /* Test that the SNI size calculation returns 0 on overflow instead of
603+ * wrapping around to a small value (integer overflow vulnerability). */
604+ /* closing #if for helper functions */
605+ #endif /* HAVE_SNI && !NO_WOLFSSL_CLIENT */
606+
607+ int test_TLSX_SNI_GetSize_overflow (void )
608+ {
609+ EXPECT_DECLS ;
610+ #if defined(HAVE_SNI ) && !defined(NO_WOLFSSL_CLIENT )
611+ WOLFSSL_CTX * ctx = NULL ;
612+ WOLFSSL * ssl = NULL ;
613+ TLSX * sni_ext = NULL ;
614+ SNI * head = NULL ;
615+ SNI * sni = NULL ;
616+ int i ;
617+ word16 fixed_size ;
618+ word16 old_size ;
619+ /* Each SNI adds ENUM_LEN(1) + OPAQUE16_LEN(2) + hostname_len to the size.
620+ * With a 1-byte hostname, each entry adds 4 bytes. Starting from
621+ * OPAQUE16_LEN(2) base, we need enough entries to exceed UINT16_MAX. */
622+ const int num_sni = (0xFFFF / 4 ) + 2 ;
623+
624+ ExpectNotNull (ctx = wolfSSL_CTX_new (wolfTLSv1_3_client_method ()));
625+ ExpectNotNull (ssl = wolfSSL_new (ctx ));
626+
627+ /* Add initial SNI via public API */
628+ ExpectIntEQ (WOLFSSL_SUCCESS ,
629+ wolfSSL_UseSNI (ssl , WOLFSSL_SNI_HOST_NAME , "a" , 1 ));
630+
631+ /* Find the SNI extension and manually build a long chain */
632+ if (EXPECT_SUCCESS ()) {
633+ sni_ext = test_TLSX_find_ext_sni (ssl -> extensions , TLSX_SERVER_NAME );
634+ ExpectNotNull (sni_ext );
635+ }
636+
637+ if (EXPECT_SUCCESS ()) {
638+ head = (SNI * )sni_ext -> data ;
639+ ExpectNotNull (head );
640+ }
641+
642+ /* Append many SNI nodes to force overflow in the size calculation */
643+ for (i = 1 ; EXPECT_SUCCESS () && i < num_sni ; i ++ ) {
644+ sni = (SNI * )XMALLOC (sizeof (SNI ), NULL , DYNAMIC_TYPE_TLSX );
645+ ExpectNotNull (sni );
646+ if (sni != NULL ) {
647+ XMEMSET (sni , 0 , sizeof (SNI ));
648+ sni -> type = WOLFSSL_SNI_HOST_NAME ;
649+ sni -> data .host_name = (char * )XMALLOC (2 , NULL , DYNAMIC_TYPE_TLSX );
650+ ExpectNotNull (sni -> data .host_name );
651+ if (sni -> data .host_name != NULL ) {
652+ sni -> data .host_name [0 ] = 'a' ;
653+ sni -> data .host_name [1 ] = '\0' ;
654+ }
655+ sni -> next = head -> next ;
656+ head -> next = sni ;
657+ }
658+ }
659+
660+ if (EXPECT_SUCCESS ()) {
661+ /* The fixed calculation should return 0 (overflow detected) */
662+ fixed_size = test_SNI_GetSize ((SNI * )sni_ext -> data );
663+ ExpectIntEQ (fixed_size , 0 );
664+
665+ /* The old (vulnerable) calculation would wrap around to a small value.
666+ * The unwrapped total is approximately OPAQUE16_LEN + num_sni * 4,
667+ * which is ~65,542. After wrapping past UINT16_MAX the result should
668+ * be drastically smaller than that expected total. */
669+ old_size = test_SNI_GetSize_old ((SNI * )sni_ext -> data );
670+ ExpectIntNE (old_size , 0 );
671+ ExpectIntLT (old_size ,
672+ OPAQUE16_LEN + num_sni * (ENUM_LEN + OPAQUE16_LEN + 1 ));
673+ }
674+
675+ wolfSSL_free (ssl );
676+ wolfSSL_CTX_free (ctx );
677+ #endif
678+ return EXPECT_RESULT ();
679+ }
0 commit comments