Commit 4f17865ba4 for openssl.org

commit 4f17865ba44b904c99d8e10ac1e460b811b7842e
Author: Daniel Kubec <kubec@openssl.foundation>
Date:   Tue Jul 7 09:16:19 2026 +0000

    NULL-pointer subtraction UB in tls_collect_extensions()

    Fixed invalid-pointer-pair in the existing branch by ensuring thisex != NULL
    before subtraction.

    Fixes #31689

    Reviewed-by: Tom Cosgrove <tom.cosgrove@arm.com>
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    MergeDate: Fri Jul 10 15:40:21 2026
    (Merged from https://github.com/openssl/openssl/pull/31875)

diff --git a/ssl/statem/extensions.c b/ssl/statem/extensions.c
index 017d1a5fb5..c3ef683d75 100644
--- a/ssl/statem/extensions.c
+++ b/ssl/statem/extensions.c
@@ -907,6 +907,11 @@ int tls_collect_extensions(SSL_CONNECTION *s, PACKET *packet,
             SSLfatal(s, SSL_AD_ILLEGAL_PARAMETER, SSL_R_BAD_EXTENSION);
             goto err;
         }
+
+        /* The server must tolerate the unknown extension and complete. */
+        if (thisex == NULL)
+            continue;
+
         idx = (unsigned int)(thisex - raw_extensions);
         /*-
          * Check that we requested this extension (if appropriate). Requests can
@@ -937,17 +942,15 @@ int tls_collect_extensions(SSL_CONNECTION *s, PACKET *packet,
                 SSL_R_UNSOLICITED_EXTENSION);
             goto err;
         }
-        if (thisex != NULL) {
-            thisex->data = extension;
-            thisex->present = 1;
-            thisex->type = type;
-            thisex->received_order = i++;
-            if (s->ext.debug_cb)
-                s->ext.debug_cb(SSL_CONNECTION_GET_USER_SSL(s), !s->server,
-                    thisex->type, PACKET_data(&thisex->data),
-                    (int)PACKET_remaining(&thisex->data),
-                    s->ext.debug_arg);
-        }
+        thisex->data = extension;
+        thisex->present = 1;
+        thisex->type = type;
+        thisex->received_order = i++;
+        if (s->ext.debug_cb)
+            s->ext.debug_cb(SSL_CONNECTION_GET_USER_SSL(s), !s->server,
+                thisex->type, PACKET_data(&thisex->data),
+                (int)PACKET_remaining(&thisex->data),
+                s->ext.debug_arg);
     }

     if (init) {
diff --git a/test/sslapitest.c b/test/sslapitest.c
index 8baf38b93d..d751385fd2 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -12920,6 +12920,51 @@ end:
     SSL_CTX_free(cctx);
     return testresult;
 }
+
+static int un_ext_add_cb(SSL *s, unsigned int ext_type,
+    unsigned int context, const unsigned char **out, size_t *outlen, X509 *x,
+    size_t chainidx, int *al, void *add_arg)
+{
+    static const unsigned char data[] = { 0xaa };
+    *out = data;
+    *outlen = sizeof(data);
+    return 1;
+}
+
+static int un_ext_parse_cb(SSL *s, unsigned int ext_type,
+    unsigned int context, const unsigned char *in, size_t inlen, X509 *x,
+    size_t chainidx, int *al, void *parse_arg)
+{
+    return 1;
+}
+
+/*
+ * Test that a handshake succeeds when the peer sends an extension type we do
+ * not recognise. The client registers a custom extension in its ClientHello
+ * that the server knows nothing about, so on the server tls_collect_extensions()
+ * takes the "unknown extension" branch.
+ */
+static int test_tls13_unknown_extension(void)
+{
+    SSL_CTX *s = NULL, *c = NULL;
+    SSL *s_ssl = NULL, *c_ssl = NULL;
+    int test;
+
+    test = TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
+               TLS_client_method(), TLS1_3_VERSION, TLS1_3_VERSION, &s, &c, cert, privkey))
+        && TEST_true(SSL_CTX_add_custom_ext(c, 0xfefe, SSL_EXT_CLIENT_HELLO,
+            un_ext_add_cb, NULL, NULL, un_ext_parse_cb, NULL))
+        && TEST_true(create_ssl_objects(s, c, &s_ssl, &c_ssl, NULL, NULL))
+        /* The server must tolerate the unknown extension and complete. */
+        && TEST_true(create_ssl_connection(s_ssl, c_ssl, SSL_ERROR_NONE));
+
+    SSL_free(s_ssl);
+    SSL_free(c_ssl);
+    SSL_CTX_free(s);
+    SSL_CTX_free(c);
+    return test;
+}
+
 #endif /* OSSL_NO_USABLE_TLS1_3 */

 static int check_version_string(SSL *s, int version)
@@ -15380,6 +15425,7 @@ int setup_tests(void)
 #ifndef OSSL_NO_USABLE_TLS1_3
     ADD_TEST(test_read_ahead_key_change);
     ADD_ALL_TESTS(test_tls13_record_padding, 6);
+    ADD_TEST(test_tls13_unknown_extension);
 #endif
 #if !defined(OPENSSL_NO_TLS1_2) && !defined(OSSL_NO_USABLE_TLS1_3)
     ADD_ALL_TESTS(test_serverinfo_custom, 4);