From cd7e51e7d4563866fbaa1e2521ae69b45daf3698 Mon Sep 17 00:00:00 2001
From: "Miss Islington (bot)"
 <31488909+miss-islington@users.noreply.github.com>
Date: Wed, 30 Sep 2026 09:16:26 -0700
Subject: [PATCH] [3.14] gh-156293: Use-after-free for server-side SSLContext
 with sni_callback (GH-158504) (#158515)

Index: Modules/_ssl.c
--- Modules/_ssl.c.orig
+++ Modules/_ssl.c
@@ -3351,6 +3351,9 @@ context_dealloc(PyObject *op)
     /* bpo-31095: UnTrack is needed before calling any callbacks */
     PyObject_GC_UnTrack(self);
     (void)context_clear(op);
+    /* The SSL_CTX may outlive this object as the session_ctx of sockets that
+       were switched to another context; leave no Python callback behind. */
+    SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL);
     SSL_CTX_free(self->ctx);
     PyMem_FREE(self->alpn_protocols);
     tp->tp_free(self);
@@ -4664,10 +4667,10 @@ _ssl__SSLContext_set_ecdh_curve_impl(PySSLContext *sel
 }
 
 static int
-_servername_callback(SSL *s, int *al, void *args)
+_servername_callback(SSL *s, int *al, void *Py_UNUSED(args))
 {
     int ret;
-    PySSLContext *sslctx = (PySSLContext *) args;
+    PySSLContext *sslctx;
     PySSLSocket *ssl;
     PyObject *result;
     /* The high-level ssl.SSLSocket object */
@@ -4676,18 +4679,30 @@ _servername_callback(SSL *s, int *al, void *args)
     const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
     PyGILState_STATE gstate = PyGILState_Ensure();
 
+    /* Do not use the SSL_CTX's servername arg to find the context: it is a
+       borrowed pointer to whichever _SSLContext installed the callback, and
+       that object may already be gone while OpenSSL still reaches this
+       callback through the connection's session_ctx (e.g. on the second
+       ClientHello after a HelloRetryRequest, once sni_callback has switched
+       the socket to another context).  The socket's current context is
+       always alive. */
+    ssl = SSL_get_app_data(s);
+    assert(ssl != NULL);
+    Py_BEGIN_CRITICAL_SECTION(ssl);
+    sslctx = (PySSLContext *)Py_NewRef(ssl->ctx);
+    Py_END_CRITICAL_SECTION();
+    assert(Py_IS_TYPE(ssl, get_state_ctx(sslctx)->PySSLSocket_Type));
+
     Py_BEGIN_CRITICAL_SECTION(sslctx);
     sni_cb = Py_XNewRef(sslctx->set_sni_cb);
     Py_END_CRITICAL_SECTION();
 
     if (sni_cb == NULL) {
+        Py_DECREF(sslctx);
         PyGILState_Release(gstate);
         return SSL_TLSEXT_ERR_OK;
     }
 
-    ssl = SSL_get_app_data(s);
-    assert(Py_IS_TYPE(ssl, get_state_ctx(sslctx)->PySSLSocket_Type));
-
     /* The servername callback expects an argument that represents the current
      * SSL connection and that has a .context attribute that can be changed to
      * identify the requested hostname. Since the official API is the Python
@@ -4770,12 +4785,14 @@ _servername_callback(SSL *s, int *al, void *args)
     }
 
     Py_DECREF(sni_cb);
+    Py_DECREF(sslctx);
     PyGILState_Release(gstate);
     return ret;
 
 error:
     Py_XDECREF(ssl_socket);
     Py_XDECREF(sni_cb);
+    Py_DECREF(sslctx);
     *al = SSL_AD_INTERNAL_ERROR;
     ret = SSL_TLSEXT_ERR_ALERT_FATAL;
     PyGILState_Release(gstate);
@@ -4832,7 +4849,6 @@ _ssl__SSLContext_sni_callback_set_impl(PySSLContext *s
     }
     else {
         Py_XSETREF(self->set_sni_cb, Py_NewRef(value));
-        SSL_CTX_set_tlsext_servername_arg(self->ctx, self);
         SSL_CTX_set_tlsext_servername_callback(self->ctx, _servername_callback);
     }
     return 0;
