Commit dd68de6790 for openssl.org

commit dd68de679008e4a581561bc756e5ad1e8e85e9a1
Author: Matt Caswell <matt@openssl.foundation>
Date:   Mon Nov 2 12:03:20 2026 +0000

    ssl/ssl_lib.c: fail SSL_copy_session_id() if the provider sigalg counts differ

    SSL_copy_session_id(t, f) makes t share f's CERT, but t keeps its own
    SSL_CTX and ssl_pkey_num.  If the two SSL_CTXs have different numbers
    of provider signature algorithms, the sig_idx values from t's SSL_CTX
    no longer match the CERT's pkeys array, and a later handshake on t reads
    past its end.

    Refreshing ssl_pkey_num is not enough: t's SSL_CTX would still hand out
    sig_idx values beyond it, and valid_flags would be allocated too small.
    Copying between such SSL_CTXs makes no sense, so fail before t is
    modified.

    Resolves: https://github.com/openssl/openssl/issues/33459
    Reported-by: Bhabani Sankar Das <007bsd@proton.me>
    Reported-by: Anthropic's OSS Scanner (finding ANT-2026-8A5XNKF9)
    Assisted-by: Claude Code:claude-opus-5-5
    Reviewed-by: Nikola Pajkovsky <nikolap@openssl.org>
    Reviewed-by: Tomas Mraz <tomas@openssl.foundation>
    Reviewed-by: Eugene Syromiatnikov <esyr@openssl.org>
    Merge-date: Fri Nov  6 12:55:13 2026
    Merged-from: https://github.com/openssl/openssl/pull/33489

diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 4eb3e584e8..de0309d539 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -2223,6 +2223,15 @@ int SSL_copy_session_id(SSL *t, const SSL *f)
     if (tsc == NULL || fsc == NULL)
         return 0;

+    /*
+     * |t| keeps its own SSL_CTX, whose provider sigalgs index the CERT's
+     * pkeys, so it cannot share a CERT sized for a different set of them.
+     */
+    if (tsc->ssl_pkey_num != fsc->cert->ssl_pkey_num) {
+        ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
+        return 0;
+    }
+
     /* Do we need to do SSL locking? */
     if (!SSL_set_session(t, SSL_get_session(f))) {
         return 0;
diff --git a/test/sslapitest.c b/test/sslapitest.c
index 46b0fab710..933d58fbcb 100644
--- a/test/sslapitest.c
+++ b/test/sslapitest.c
@@ -15347,6 +15347,48 @@ end:
 }
 #endif /* OPENSSL_NO_TLS1_2 */

+/*
+ * SSL_copy_session_id() makes |t| share |f|'s CERT, but |t| keeps its own
+ * SSL_CTX. It must refuse a CERT sized for a different number of provider
+ * sigalgs, and leave |t| unchanged when it does.
+ */
+static int test_copy_session_id_ctx_mismatch(void)
+{
+    OSSL_PROVIDER *tlsprov = NULL;
+    SSL_CTX *sctx = NULL, *newsctx = NULL, *cctx = NULL;
+    SSL *from = NULL, *same = NULL, *to = NULL;
+    SSL_CONNECTION *tosc;
+    CERT *tocert;
+    int testresult = 0;
+
+    if (!sigalg_ctx_switch_setup(&tlsprov, 0, &sctx, &newsctx, &cctx))
+        goto end;
+
+    if (!TEST_ptr(from = SSL_new(sctx))
+        || !TEST_ptr(same = SSL_new(sctx))
+        || !TEST_ptr(to = SSL_new(newsctx))
+        || !TEST_ptr(tosc = SSL_CONNECTION_FROM_SSL_ONLY(to)))
+        goto end;
+    tocert = tosc->cert;
+
+    if (!TEST_false(SSL_copy_session_id(to, from))
+        || !TEST_ptr_eq(tosc->cert, tocert)
+        || !TEST_true(SSL_copy_session_id(same, from)))
+        goto end;
+
+    testresult = 1;
+end:
+    SSL_free(from);
+    SSL_free(same);
+    SSL_free(to);
+    SSL_CTX_free(sctx);
+    SSL_CTX_free(newsctx);
+    SSL_CTX_free(cctx);
+    OSSL_PROVIDER_unload(tlsprov);
+
+    return testresult;
+}
+
 /*
  * The ClientHello raw extension array is sized when the ClientHello is
  * collected. Switching to an SSL_CTX with more custom extensions from the
@@ -19745,6 +19787,7 @@ int setup_tests(void)
     ADD_ALL_TESTS(test_sigalg_ctx_switch, 4);
     ADD_ALL_TESTS(test_ctx_switch_more_custom_exts, 2);
     ADD_ALL_TESTS(test_sigalg_ctx_switch_cert_cb, 2);
+    ADD_TEST(test_copy_session_id_ctx_mismatch);
 #ifndef OPENSSL_NO_TLS1_2
     ADD_TEST(test_sigalg_ctx_switch_reneg);
 #endif