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;

Reply via email to