there are some other Pytgon issues but this is the nastiest,
I think worth backporting.

still building - py313 (7.9-stable) and py314 (current) attached


----- Forwarded message from Alan Coopersmith <[email protected]> 
-----

From: Alan Coopersmith <[email protected]>
Date: Wed, 30 Sep 2026 09:58:23 -0700
To: [email protected]
Reply-To: [email protected]
User-Agent: Mozilla Thunderbird
Subject: [oss-security] CPython [CVE-2026-19445]
        Use-after-free of a server-side SSLContext when
        sni_callback switches contexts




-------- Forwarded Message --------
Subject:        [Security-announce][CVE-2026-19445] Use-after-free of a
server-side SSLContext when sni_callback switches contexts
Date:   Wed, 30 Sep 2026 16:10:08 +0000
From:   Seth Larson <[email protected]>
Reply-To:       [email protected]
To:     [email protected]

There is a CRITICAL severity vulnerability affecting CPython.

A remote, unauthenticated TLS client can make a server crash or
call through a freed pointer if its sni_callback assigns a
different context to SSLSocket.context (the documented way to
select a certificate per server name) and nothing else keeps the
original ssl.SSLContext alive. Typical cases are servers that
create an SSLContext per connection or replace it while
connections are open; servers that wrap their listening socket
with it are not affected.

Mitigation: keep a reference to every SSLContext that sets
sni_callback for the lifetime of the server. TLS clients are not
affected.

Please see the linked CVE ID for the latest information on affected versions:

* https://www.cve.org/CVERecord?id=CVE-2026-19445
* https://github.com/python/cpython/pull/158504

_______________________________________________
Security-announce mailing list -- [email protected]
https://mail.python.org/mailman3//lists/security-announce.python.org

----- End forwarded message -----
? pkg/PLIST-main.int
? pkg/PLIST-tests.int
Index: Makefile
===================================================================
RCS file: /cvs/ports/lang/python/3/Makefile,v
diff -u -p -r1.25.2.2 Makefile
--- Makefile    28 Sep 2026 08:40:19 -0000      1.25.2.2
+++ Makefile    30 Sep 2026 20:07:06 -0000
@@ -4,6 +4,7 @@
 # Python itself.
 
 FULL_VERSION =         3.13.15
+REVISION-main =                0
 SHARED_LIBS =          python3.13 0.0
 VERSION_SPEC =         >=3.13,<3.14
 PORTROACH =            limit:^3\.13
Index: patches/patch-Modules__ssl_c
===================================================================
RCS file: patches/patch-Modules__ssl_c
diff -N patches/patch-Modules__ssl_c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ patches/patch-Modules__ssl_c        30 Sep 2026 20:07:06 -0000
@@ -0,0 +1,121 @@
+From d8717ed01717a9641686e6e6f83f0ab8af235e2c Mon Sep 17 00:00:00 2001
+From: Seth Larson <[email protected]>
+Date: Wed, 30 Sep 2026 10:35:27 -0500
+Subject: [PATCH] =?UTF-8?q?[3.13]=20gh-156293:=20Use-after-free=20for=20se?=
+ =?UTF-8?q?rver-side=20SSLContext=20with=20sni=5F=E2=80=A6=20(#158507)?=
+
+Index: Modules/_ssl.c
+--- Modules/_ssl.c.orig
++++ Modules/_ssl.c
+@@ -3296,6 +3296,9 @@ context_dealloc(PySSLContext *self)
+     /* bpo-31095: UnTrack is needed before calling any callbacks */
+     PyObject_GC_UnTrack(self);
+     context_clear(self);
++    /* 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);
+     Py_TYPE(self)->tp_free(self);
+@@ -4606,27 +4609,42 @@ _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 */
+-    PyObject *ssl_socket;
++    PyObject *ssl_socket = NULL;
++    PyObject *sni_cb;
+     const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
+     PyGILState_STATE gstate = PyGILState_Ensure();
+ 
+-    if (sslctx->set_sni_cb == NULL) {
+-        /* remove race condition in this the call back while if removing the
+-         * callback is in progress */
++    /* 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; hold strong references to it and to the callback while
++       they are used here. */
++    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
+@@ -4646,7 +4664,7 @@ _servername_callback(SSL *s, int *al, void *args)
+         goto error;
+ 
+     if (servername == NULL) {
+-        result = PyObject_CallFunctionObjArgs(sslctx->set_sni_cb, ssl_socket,
++        result = PyObject_CallFunctionObjArgs(sni_cb, ssl_socket,
+                                               Py_None, sslctx, NULL);
+     }
+     else {
+@@ -4669,14 +4687,14 @@ _servername_callback(SSL *s, int *al, void *args)
+         }
+         Py_DECREF(servername_bytes);
+         result = PyObject_CallFunctionObjArgs(
+-            sslctx->set_sni_cb, ssl_socket, servername_str,
++            sni_cb, ssl_socket, servername_str,
+             sslctx, NULL);
+         Py_DECREF(servername_str);
+     }
+     Py_DECREF(ssl_socket);
+ 
+     if (result == NULL) {
+-        PyErr_WriteUnraisable(sslctx->set_sni_cb);
++        PyErr_WriteUnraisable(sni_cb);
+         *al = SSL_AD_HANDSHAKE_FAILURE;
+         ret = SSL_TLSEXT_ERR_ALERT_FATAL;
+     }
+@@ -4697,11 +4715,15 @@ _servername_callback(SSL *s, int *al, void *args)
+         Py_DECREF(result);
+     }
+ 
++    Py_DECREF(sni_cb);
++    Py_DECREF(sslctx);
+     PyGILState_Release(gstate);
+     return ret;
+ 
+ error:
+     Py_XDECREF(ssl_socket);
++    Py_DECREF(sni_cb);
++    Py_DECREF(sslctx);
+     *al = SSL_AD_INTERNAL_ERROR;
+     ret = SSL_TLSEXT_ERR_ALERT_FATAL;
+     PyGILState_Release(gstate);
+@@ -4761,7 +4783,6 @@ _ssl__SSLContext_sni_callback_set_impl(PySSLContext *s
+         }
+         self->set_sni_cb = Py_NewRef(value);
+         SSL_CTX_set_tlsext_servername_callback(self->ctx, 
_servername_callback);
+-        SSL_CTX_set_tlsext_servername_arg(self->ctx, self);
+     }
+     return 0;
+ }
? a
? python.port.mk.yes-diff
? wheels.diff
? pkg/PLIST-main.int
Index: Makefile
===================================================================
RCS file: /cvs/ports/lang/python/3/Makefile,v
diff -u -p -r1.34 Makefile
--- Makefile    28 Sep 2026 08:39:45 -0000      1.34
+++ Makefile    30 Sep 2026 20:07:26 -0000
@@ -4,6 +4,7 @@
 # Python itself.
 
 FULL_VERSION =         3.14.7
+REVISION-main =                0
 SHARED_LIBS =          python3.14 0.0
 VERSION_SPEC =         >=3.14
 PORTROACH =            limit:^3\.14
Index: patches/patch-Modules__ssl_c
===================================================================
RCS file: patches/patch-Modules__ssl_c
diff -N patches/patch-Modules__ssl_c
--- /dev/null   1 Jan 1970 00:00:00 -0000
+++ patches/patch-Modules__ssl_c        30 Sep 2026 20:07:26 -0000
@@ -0,0 +1,90 @@
+From cd7e51e7d4563866fbaa1e2521ae69b45daf3698 Mon Sep 17 00:00:00 2001
+From: "Miss Islington (bot)"
+ <[email protected]>
+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;

Reply via email to