Download raw body.
[alan.coopersmith@oracle.com: CPython [CVE-2026-19445] Use-after-free of a server-side SSLContext
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;
[alan.coopersmith@oracle.com: CPython [CVE-2026-19445] Use-after-free of a server-side SSLContext