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 <alan.coopersmith@oracle.com> ----- From: Alan Coopersmith <alan.coopersmith@oracle.com> Date: Wed, 30 Sep 2026 09:58:23 -0700 To: oss-security@lists.openwall.com Reply-To: oss-security@lists.openwall.com 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 <seth@python.org> Reply-To: security-sig@python.org To: security-announce@python.org 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 -- security-announce@python.org 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 <seth@python.org> +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)" + <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;