On Wed, Sep 30, 2026 at 09:08:50PM +0100, Stuart Henderson wrote: > 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 I agree on backporting. ok kmos --Kurt > > ----- 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;
