| Message ID | 20261002123943.2208277-1-joaomarcos.costa@bootlin.com |
|---|---|
| State | Changes Requested, archived |
| Delegated to: | Yoann Congal |
| Headers | show |
| Series | [scarthgap] python3: fix CVE-2026-19445 | expand |
We should upgrade python version instead of picking patches included in latest tag. https://github.com/python/cpython/releases/tag/v3.12.15 I'll probably give it a try over weekend. Peter > -----Original Message----- > From: openembedded-core@lists.openembedded.org <openembedded- > core@lists.openembedded.org> On Behalf Of Joao Marcos Costa via > lists.openembedded.org > Sent: Friday, October 2, 2026 2:40 PM > To: openembedded-core@lists.openembedded.org > Cc: thomas.petazzoni@bootlin.com; yoann.congal@smile.fr; João Marcos Costa > (Schneider Electric) <joaomarcos.costa@bootlin.com> > Subject: [OE-core] [scarthgap][PATCH] python3: fix CVE-2026-19445 > > From: João Marcos Costa (Schneider Electric) <joaomarcos.costa@bootlin.com> > > This critical vulnerability [1] was fixed originally on 3.14, and then backported to > 3.12 through a PR (already merged) [2]: > > "[3.12] gh-156293: Use-after-free for server-side SSLContext with sni_callback" > > Backport the fix to oe-core's v3.12.14. > > [1] https://security-tracker.debian.org/tracker/CVE-2026-19445 > [2] https://github.com/python/cpython/pull/158517 > > Signed-off-by: João Marcos Costa (Schneider Electric) > <joaomarcos.costa@bootlin.com> > --- > .../python/python3/CVE-2026-19445.patch | 265 ++++++++++++++++++ > .../python/python3_3.12.14.bb | 1 + > 2 files changed, 266 insertions(+) > create mode 100644 meta/recipes-devtools/python/python3/CVE-2026- > 19445.patch > > diff --git a/meta/recipes-devtools/python/python3/CVE-2026-19445.patch > b/meta/recipes-devtools/python/python3/CVE-2026-19445.patch > new file mode 100644 > index 0000000000..40fe283ba6 > --- /dev/null > +++ b/meta/recipes-devtools/python/python3/CVE-2026-19445.patch > @@ -0,0 +1,265 @@ > +From 61ba3afbaab1394b5e47402e5122cb5c55d4884f Mon Sep 17 00:00:00 > 2001 > +From: Seth Michael Larson <seth@python.org> > +Date: Wed, 30 Sep 2026 10:53:31 -0500 > +Subject: [PATCH] [3.12] gh-156293: Use-after-free for server-side SSLContext > + with sni_callback > +MIME-Version: 1.0 > +Content-Type: text/plain; charset=UTF-8 > +Content-Transfer-Encoding: 8bit > + > +CVE: CVE-2026-19445 > +Upstream-Status: Backport [https://github.com/python/cpython/pull/158517] > + > +Co-authored-by: Gregory P. Smith > <68491+gpshead@users.noreply.github.com> > +Signed-off-by: João Marcos Costa (Schneider Electric) > <joaomarcos.costa@bootlin.com> > +--- > + Doc/library/ssl.rst | 11 +++ > + Lib/test/test_ssl.py | 80 +++++++++++++++++++ > + ...-08-10-12-00-00.gh-issue-156293.sNIcbk.rst | 7 ++ > + Modules/_ssl.c | 40 +++++++--- > + 4 files changed, 126 insertions(+), 12 deletions(-) > + create mode 100644 Misc/NEWS.d/next/Security/2026-08-10-12-00-00.gh- > issue-156293.sNIcbk.rst > + > +diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst > +index 67360a5079f..71199ceabc6 100644 > +--- a/Doc/library/ssl.rst > ++++ b/Doc/library/ssl.rst > +@@ -1666,6 +1666,12 @@ to speed up repeated connections from the same > clients. > + :class:`SSLContext` representing a certificate chain that matches the server > + name. > + > ++ If the callback assigns a new context to :attr:`SSLSocket.context`, any > ++ further ClientHello message on the same connection (for example after a > ++ TLS 1.3 HelloRetryRequest) is dispatched to the new context's > ++ *sni_callback*, if it has one; the original callback is not called again > ++ for that connection. > ++ > + Due to the early negotiation phase of the TLS connection, only limited > + methods and attributes are usable like > + :meth:`SSLSocket.selected_alpn_protocol` and :attr:`SSLSocket.context`. > +@@ -1689,6 +1695,11 @@ to speed up repeated connections from the same > clients. > + > + .. versionadded:: 3.7 > + > ++ .. versionchanged:: next > ++ After the callback assigns a new :attr:`SSLSocket.context`, later > ++ ClientHello messages on the connection are dispatched to the new > ++ context's *sni_callback*. > ++ > + .. attribute:: SSLContext.set_servername_callback(server_name_callback) > + > + This is a legacy API retained for backwards compatibility. When possible, > +diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py > +index b13e37d0cd1..accc030d2f3 100644 > +--- a/Lib/test/test_ssl.py > ++++ b/Lib/test/test_ssl.py > +@@ -1818,6 +1818,86 @@ class SSLObjectTests(unittest.TestCase): > + c_in.write(s_out.read()) > + client.unwrap() > + > ++ def test_sni_callback_context_released_and_callback_raises(self): > ++ # Variant of the test below without a HelloRetryRequest: the callback > ++ # switches the connection to another context, drops the last > ++ # references to the context that carries it, and raises. The C > ++ # callback must not touch that context after the Python callback > ++ # returned. > ++ client_ctx, server_ctx, hostname = testing_context() > ++ leaf_ctx = server_ctx > ++ > ++ def sni_cb(sslobj, server_name, ctx): > ++ sslobj.context = leaf_ctx > ++ del ctx > ++ raise LookupError("no certificate for " + repr(server_name)) > ++ > ++ def make_server(): > ++ dispatch_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) > ++ dispatch_ctx.load_cert_chain(SIGNED_CERTFILE) > ++ dispatch_ctx.sni_callback = sni_cb > ++ s_in, s_out = ssl.MemoryBIO(), ssl.MemoryBIO() > ++ server = dispatch_ctx.wrap_bio(s_in, s_out, server_side=True) > ++ return server, s_in, s_out > ++ > ++ server, s_in, s_out = make_server() > ++ c_in, c_out = ssl.MemoryBIO(), ssl.MemoryBIO() > ++ client = client_ctx.wrap_bio(c_in, c_out, server_hostname=hostname) > ++ with self.assertRaises(ssl.SSLWantReadError): > ++ client.do_handshake() > ++ s_in.write(c_out.read()) > ++ with support.catch_unraisable_exception() as cm: > ++ with self.assertRaises(ssl.SSLError): > ++ server.do_handshake() > ++ self.assertIsInstance(cm.unraisable.exc_value, LookupError) > ++ self.assertIs(server.context, leaf_ctx) > ++ > ++ def test_sni_callback_context_released_before_second_client_hello(self): > ++ # The SSLContext carrying sni_callback may be released by the > ++ # application once the callback has switched the connection over to > ++ # another context. If the server then sends a HelloRetryRequest, the > ++ # second ClientHello makes OpenSSL consult the original SSL_CTX's > ++ # servername callback again; that must not use the deallocated > ++ # SSLContext object. > ++ client_ctx, leaf_ctx, hostname = testing_context() > ++ calls = [] > ++ > ++ def make_server(): > ++ dispatch_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) > ++ dispatch_ctx.load_cert_chain(SIGNED_CERTFILE) > ++ # Force a HelloRetryRequest: the client offers an X25519 key > ++ # share first, the server only accepts P-384. > ++ dispatch_ctx.set_ecdh_curve("secp384r1") > ++ def sni_cb(sslobj, server_name, ctx): > ++ calls.append(server_name) > ++ sslobj.context = leaf_ctx > ++ dispatch_ctx.sni_callback = sni_cb > ++ s_in, s_out = ssl.MemoryBIO(), ssl.MemoryBIO() > ++ server = dispatch_ctx.wrap_bio(s_in, s_out, server_side=True) > ++ return server, s_in, s_out, weakref.ref(dispatch_ctx) > ++ > ++ # After this only the C-level SSL object references dispatch_ctx. > ++ server, s_in, s_out, dispatch_ref = make_server() > ++ c_in, c_out = ssl.MemoryBIO(), ssl.MemoryBIO() > ++ client = client_ctx.wrap_bio(c_in, c_out, server_hostname=hostname) > ++ for _ in range(10): > ++ for obj, out, peer_in in ((client, c_out, s_in), > ++ (server, s_out, c_in)): > ++ try: > ++ obj.do_handshake() > ++ except ssl.SSLWantReadError: > ++ pass > ++ if out.pending: > ++ peer_in.write(out.read()) > ++ client.do_handshake() > ++ server.do_handshake() > ++ support.gc_collect() > ++ self.assertIsNone(dispatch_ref()) > ++ self.assertGreaterEqual(len(calls), 1) > ++ self.assertEqual(calls[0], hostname) > ++ self.assertIs(server.context, leaf_ctx) > ++ self.assertIsNotNone(client.cipher()) > ++ > + class SimpleBackgroundTests(unittest.TestCase): > + """Tests that connect to a simple server running in the background""" > + > +diff --git a/Misc/NEWS.d/next/Security/2026-08-10-12-00-00.gh-issue- > 156293.sNIcbk.rst b/Misc/NEWS.d/next/Security/2026-08-10-12-00-00.gh-issue- > 156293.sNIcbk.rst > +new file mode 100644 > +index 00000000000..0cc620b3fdc > +--- /dev/null > ++++ b/Misc/NEWS.d/next/Security/2026-08-10-12-00-00.gh-issue- > 156293.sNIcbk.rst > +@@ -0,0 +1,7 @@ > ++Fix a crash in :mod:`ssl` when an :attr:`~ssl.SSLContext.sni_callback` > ++switches a connection to another :class:`~ssl.SSLContext` and the context > ++that carries the callback is no longer referenced by the application. > ++Servers that keep their ``sni_callback`` context alive (the usual case when > ++it wraps the listening socket or is stored on the server object) were not > ++affected. > ++This addresses :cve:`2026-19445`. > +diff --git a/Modules/_ssl.c b/Modules/_ssl.c > +index aae4dc323dd..91ca1362d4e 100644 > +--- a/Modules/_ssl.c > ++++ b/Modules/_ssl.c > +@@ -3184,6 +3184,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); > +@@ -4346,27 +4349,37 @@ > _ssl__SSLContext_set_ecdh_curve(PySSLContext *self, PyObject *name) > + } > + > + 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 *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); > ++ sslctx = (PySSLContext *)Py_NewRef(ssl->ctx); > ++ assert(Py_IS_TYPE(ssl, get_state_ctx(sslctx)->PySSLSocket_Type)); > ++ sni_cb = Py_XNewRef(sslctx->set_sni_cb); > ++ 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 > +@@ -4387,7 +4400,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 { > +@@ -4410,14 +4423,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; > + } > +@@ -4438,11 +4451,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_DECREF(ssl_socket); > ++ Py_DECREF(sni_cb); > ++ Py_DECREF(sslctx); > + *al = SSL_AD_INTERNAL_ERROR; > + ret = SSL_TLSEXT_ERR_ALERT_FATAL; > + PyGILState_Release(gstate); > +@@ -4480,7 +4497,6 @@ set_sni_callback(PySSLContext *self, PyObject *arg, > void *c) > + } > + self->set_sni_cb = Py_NewRef(arg); > + SSL_CTX_set_tlsext_servername_callback(self->ctx, > _servername_callback); > +- SSL_CTX_set_tlsext_servername_arg(self->ctx, self); > + } > + return 0; > + } > diff --git a/meta/recipes-devtools/python/python3_3.12.14.bb b/meta/recipes- > devtools/python/python3_3.12.14.bb > index 14be125180..bf2c7938b5 100644 > --- a/meta/recipes-devtools/python/python3_3.12.14.bb > +++ b/meta/recipes-devtools/python/python3_3.12.14.bb > @@ -36,6 +36,7 @@ SRC_URI = "http://www.python.org/ftp/python/${PV}/Python- > ${PV}.tar.xz \ > file://0001-test_readline-skip-limited-history-test.patch \ > file://CVE-2026-6019_p1.patch \ > file://CVE-2026-6019_p2.patch \ > + file://CVE-2026-19445.patch \ > " > > SRC_URI:append:class-native = " \ > -- > 2.55.0
On Fri Oct 2, 2026 at 2:45 PM CEST, Peter Marko wrote: > We should upgrade python version instead of picking patches included in latest tag. > https://github.com/python/cpython/releases/tag/v3.12.15 Agreed. @João, python is one of the rare recipes we'd rather upgrade than patch with backports on stable. As long as it is maintained upstream, of course. > > I'll probably give it a try over weekend. Thanks Peter! > > Peter > >> -----Original Message----- >> From: openembedded-core@lists.openembedded.org <openembedded- >> core@lists.openembedded.org> On Behalf Of Joao Marcos Costa via >> lists.openembedded.org >> Sent: Friday, October 2, 2026 2:40 PM >> To: openembedded-core@lists.openembedded.org >> Cc: thomas.petazzoni@bootlin.com; yoann.congal@smile.fr; João Marcos Costa >> (Schneider Electric) <joaomarcos.costa@bootlin.com> >> Subject: [OE-core] [scarthgap][PATCH] python3: fix CVE-2026-19445 >> >> From: João Marcos Costa (Schneider Electric) <joaomarcos.costa@bootlin.com> >> >> This critical vulnerability [1] was fixed originally on 3.14, and then backported to >> 3.12 through a PR (already merged) [2]: >> >> "[3.12] gh-156293: Use-after-free for server-side SSLContext with sni_callback" >> >> Backport the fix to oe-core's v3.12.14. >> >> [1] https://security-tracker.debian.org/tracker/CVE-2026-19445 >> [2] https://github.com/python/cpython/pull/158517 >> >> Signed-off-by: João Marcos Costa (Schneider Electric) >> <joaomarcos.costa@bootlin.com> >> --- >> .../python/python3/CVE-2026-19445.patch | 265 ++++++++++++++++++ >> .../python/python3_3.12.14.bb | 1 + >> 2 files changed, 266 insertions(+) >> create mode 100644 meta/recipes-devtools/python/python3/CVE-2026- >> 19445.patch
diff --git a/meta/recipes-devtools/python/python3/CVE-2026-19445.patch b/meta/recipes-devtools/python/python3/CVE-2026-19445.patch new file mode 100644 index 0000000000..40fe283ba6 --- /dev/null +++ b/meta/recipes-devtools/python/python3/CVE-2026-19445.patch @@ -0,0 +1,265 @@ +From 61ba3afbaab1394b5e47402e5122cb5c55d4884f Mon Sep 17 00:00:00 2001 +From: Seth Michael Larson <seth@python.org> +Date: Wed, 30 Sep 2026 10:53:31 -0500 +Subject: [PATCH] [3.12] gh-156293: Use-after-free for server-side SSLContext + with sni_callback +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +CVE: CVE-2026-19445 +Upstream-Status: Backport [https://github.com/python/cpython/pull/158517] + +Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> +Signed-off-by: João Marcos Costa (Schneider Electric) <joaomarcos.costa@bootlin.com> +--- + Doc/library/ssl.rst | 11 +++ + Lib/test/test_ssl.py | 80 +++++++++++++++++++ + ...-08-10-12-00-00.gh-issue-156293.sNIcbk.rst | 7 ++ + Modules/_ssl.c | 40 +++++++--- + 4 files changed, 126 insertions(+), 12 deletions(-) + create mode 100644 Misc/NEWS.d/next/Security/2026-08-10-12-00-00.gh-issue-156293.sNIcbk.rst + +diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst +index 67360a5079f..71199ceabc6 100644 +--- a/Doc/library/ssl.rst ++++ b/Doc/library/ssl.rst +@@ -1666,6 +1666,12 @@ to speed up repeated connections from the same clients. + :class:`SSLContext` representing a certificate chain that matches the server + name. + ++ If the callback assigns a new context to :attr:`SSLSocket.context`, any ++ further ClientHello message on the same connection (for example after a ++ TLS 1.3 HelloRetryRequest) is dispatched to the new context's ++ *sni_callback*, if it has one; the original callback is not called again ++ for that connection. ++ + Due to the early negotiation phase of the TLS connection, only limited + methods and attributes are usable like + :meth:`SSLSocket.selected_alpn_protocol` and :attr:`SSLSocket.context`. +@@ -1689,6 +1695,11 @@ to speed up repeated connections from the same clients. + + .. versionadded:: 3.7 + ++ .. versionchanged:: next ++ After the callback assigns a new :attr:`SSLSocket.context`, later ++ ClientHello messages on the connection are dispatched to the new ++ context's *sni_callback*. ++ + .. attribute:: SSLContext.set_servername_callback(server_name_callback) + + This is a legacy API retained for backwards compatibility. When possible, +diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py +index b13e37d0cd1..accc030d2f3 100644 +--- a/Lib/test/test_ssl.py ++++ b/Lib/test/test_ssl.py +@@ -1818,6 +1818,86 @@ class SSLObjectTests(unittest.TestCase): + c_in.write(s_out.read()) + client.unwrap() + ++ def test_sni_callback_context_released_and_callback_raises(self): ++ # Variant of the test below without a HelloRetryRequest: the callback ++ # switches the connection to another context, drops the last ++ # references to the context that carries it, and raises. The C ++ # callback must not touch that context after the Python callback ++ # returned. ++ client_ctx, server_ctx, hostname = testing_context() ++ leaf_ctx = server_ctx ++ ++ def sni_cb(sslobj, server_name, ctx): ++ sslobj.context = leaf_ctx ++ del ctx ++ raise LookupError("no certificate for " + repr(server_name)) ++ ++ def make_server(): ++ dispatch_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ++ dispatch_ctx.load_cert_chain(SIGNED_CERTFILE) ++ dispatch_ctx.sni_callback = sni_cb ++ s_in, s_out = ssl.MemoryBIO(), ssl.MemoryBIO() ++ server = dispatch_ctx.wrap_bio(s_in, s_out, server_side=True) ++ return server, s_in, s_out ++ ++ server, s_in, s_out = make_server() ++ c_in, c_out = ssl.MemoryBIO(), ssl.MemoryBIO() ++ client = client_ctx.wrap_bio(c_in, c_out, server_hostname=hostname) ++ with self.assertRaises(ssl.SSLWantReadError): ++ client.do_handshake() ++ s_in.write(c_out.read()) ++ with support.catch_unraisable_exception() as cm: ++ with self.assertRaises(ssl.SSLError): ++ server.do_handshake() ++ self.assertIsInstance(cm.unraisable.exc_value, LookupError) ++ self.assertIs(server.context, leaf_ctx) ++ ++ def test_sni_callback_context_released_before_second_client_hello(self): ++ # The SSLContext carrying sni_callback may be released by the ++ # application once the callback has switched the connection over to ++ # another context. If the server then sends a HelloRetryRequest, the ++ # second ClientHello makes OpenSSL consult the original SSL_CTX's ++ # servername callback again; that must not use the deallocated ++ # SSLContext object. ++ client_ctx, leaf_ctx, hostname = testing_context() ++ calls = [] ++ ++ def make_server(): ++ dispatch_ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) ++ dispatch_ctx.load_cert_chain(SIGNED_CERTFILE) ++ # Force a HelloRetryRequest: the client offers an X25519 key ++ # share first, the server only accepts P-384. ++ dispatch_ctx.set_ecdh_curve("secp384r1") ++ def sni_cb(sslobj, server_name, ctx): ++ calls.append(server_name) ++ sslobj.context = leaf_ctx ++ dispatch_ctx.sni_callback = sni_cb ++ s_in, s_out = ssl.MemoryBIO(), ssl.MemoryBIO() ++ server = dispatch_ctx.wrap_bio(s_in, s_out, server_side=True) ++ return server, s_in, s_out, weakref.ref(dispatch_ctx) ++ ++ # After this only the C-level SSL object references dispatch_ctx. ++ server, s_in, s_out, dispatch_ref = make_server() ++ c_in, c_out = ssl.MemoryBIO(), ssl.MemoryBIO() ++ client = client_ctx.wrap_bio(c_in, c_out, server_hostname=hostname) ++ for _ in range(10): ++ for obj, out, peer_in in ((client, c_out, s_in), ++ (server, s_out, c_in)): ++ try: ++ obj.do_handshake() ++ except ssl.SSLWantReadError: ++ pass ++ if out.pending: ++ peer_in.write(out.read()) ++ client.do_handshake() ++ server.do_handshake() ++ support.gc_collect() ++ self.assertIsNone(dispatch_ref()) ++ self.assertGreaterEqual(len(calls), 1) ++ self.assertEqual(calls[0], hostname) ++ self.assertIs(server.context, leaf_ctx) ++ self.assertIsNotNone(client.cipher()) ++ + class SimpleBackgroundTests(unittest.TestCase): + """Tests that connect to a simple server running in the background""" + +diff --git a/Misc/NEWS.d/next/Security/2026-08-10-12-00-00.gh-issue-156293.sNIcbk.rst b/Misc/NEWS.d/next/Security/2026-08-10-12-00-00.gh-issue-156293.sNIcbk.rst +new file mode 100644 +index 00000000000..0cc620b3fdc +--- /dev/null ++++ b/Misc/NEWS.d/next/Security/2026-08-10-12-00-00.gh-issue-156293.sNIcbk.rst +@@ -0,0 +1,7 @@ ++Fix a crash in :mod:`ssl` when an :attr:`~ssl.SSLContext.sni_callback` ++switches a connection to another :class:`~ssl.SSLContext` and the context ++that carries the callback is no longer referenced by the application. ++Servers that keep their ``sni_callback`` context alive (the usual case when ++it wraps the listening socket or is stored on the server object) were not ++affected. ++This addresses :cve:`2026-19445`. +diff --git a/Modules/_ssl.c b/Modules/_ssl.c +index aae4dc323dd..91ca1362d4e 100644 +--- a/Modules/_ssl.c ++++ b/Modules/_ssl.c +@@ -3184,6 +3184,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); +@@ -4346,27 +4349,37 @@ _ssl__SSLContext_set_ecdh_curve(PySSLContext *self, PyObject *name) + } + + 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 *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); ++ sslctx = (PySSLContext *)Py_NewRef(ssl->ctx); ++ assert(Py_IS_TYPE(ssl, get_state_ctx(sslctx)->PySSLSocket_Type)); ++ sni_cb = Py_XNewRef(sslctx->set_sni_cb); ++ 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 +@@ -4387,7 +4400,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 { +@@ -4410,14 +4423,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; + } +@@ -4438,11 +4451,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_DECREF(ssl_socket); ++ Py_DECREF(sni_cb); ++ Py_DECREF(sslctx); + *al = SSL_AD_INTERNAL_ERROR; + ret = SSL_TLSEXT_ERR_ALERT_FATAL; + PyGILState_Release(gstate); +@@ -4480,7 +4497,6 @@ set_sni_callback(PySSLContext *self, PyObject *arg, void *c) + } + self->set_sni_cb = Py_NewRef(arg); + SSL_CTX_set_tlsext_servername_callback(self->ctx, _servername_callback); +- SSL_CTX_set_tlsext_servername_arg(self->ctx, self); + } + return 0; + } diff --git a/meta/recipes-devtools/python/python3_3.12.14.bb b/meta/recipes-devtools/python/python3_3.12.14.bb index 14be125180..bf2c7938b5 100644 --- a/meta/recipes-devtools/python/python3_3.12.14.bb +++ b/meta/recipes-devtools/python/python3_3.12.14.bb @@ -36,6 +36,7 @@ SRC_URI = "http://www.python.org/ftp/python/${PV}/Python-${PV}.tar.xz \ file://0001-test_readline-skip-limited-history-test.patch \ file://CVE-2026-6019_p1.patch \ file://CVE-2026-6019_p2.patch \ + file://CVE-2026-19445.patch \ " SRC_URI:append:class-native = " \