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