diff --git a/gh137583-only-lock-SSL-context.patch b/gh137583-only-lock-SSL-context.patch new file mode 100644 index 0000000..3a5ff62 --- /dev/null +++ b/gh137583-only-lock-SSL-context.patch @@ -0,0 +1,187 @@ +--- + Lib/test/test_ssl.py | 36 ++++++++++ + Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst | 4 + + Modules/_ssl.c | 33 ++++----- + Modules/_ssl/debughelpers.c | 1 + 4 files changed, 57 insertions(+), 17 deletions(-) + +Index: Python-3.13.6/Lib/test/test_ssl.py +=================================================================== +--- Python-3.13.6.orig/Lib/test/test_ssl.py 2025-08-06 15:05:20.000000000 +0200 ++++ Python-3.13.6/Lib/test/test_ssl.py 2025-08-12 11:34:50.729724565 +0200 +@@ -4584,6 +4584,42 @@ + with client_context.wrap_socket(socket.socket()) as s: + s.connect((HOST, server.port)) + ++ def test_thread_recv_while_main_thread_sends(self): ++ # GH-137583: Locking was added to calls to send() and recv() on SSL ++ # socket objects. This seemed fine at the surface level because those ++ # calls weren't re-entrant, but recv() calls would implicitly mimick ++ # holding a lock by blocking until it received data. This means that ++ # if a thread started to infinitely block until data was received, calls ++ # to send() would deadlock, because it would wait forever on the lock ++ # that the recv() call held. ++ data = b"1" * 1024 ++ event = threading.Event() ++ def background(sock): ++ event.set() ++ received = sock.recv(len(data)) ++ self.assertEqual(received, data) ++ ++ client_context, server_context, hostname = testing_context() ++ server = ThreadedEchoServer(context=server_context) ++ with server: ++ with client_context.wrap_socket(socket.socket(), ++ server_hostname=hostname) as sock: ++ sock.connect((HOST, server.port)) ++ sock.settimeout(1) ++ sock.setblocking(1) ++ # Ensure that the server is ready to accept requests ++ sock.sendall(b"123") ++ self.assertEqual(sock.recv(3), b"123") ++ with threading_helper.catch_threading_exception() as cm: ++ thread = threading.Thread(target=background, ++ args=(sock,), daemon=True) ++ thread.start() ++ event.wait() ++ sock.sendall(data) ++ thread.join() ++ if cm.exc_value is not None: ++ raise cm.exc_value ++ + + @unittest.skipUnless(has_tls_version('TLSv1_3'), "Test needs TLS 1.3") + class TestPostHandshakeAuth(unittest.TestCase): +Index: Python-3.13.6/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst +=================================================================== +--- /dev/null 1970-01-01 00:00:00.000000000 +0000 ++++ Python-3.13.6/Misc/NEWS.d/next/Library/2025-08-09-08-53-32.gh-issue-137583.s6OZud.rst 2025-08-12 11:34:50.730240349 +0200 +@@ -0,0 +1,4 @@ ++Fix a deadlock introduced in 3.13.6 when a call to ++:meth:`ssl.SSLSocket.recv ` was blocked in one thread, ++and then another method on the object (such as :meth:`ssl.SSLSocket.send `) ++was subsequently called in another thread. +Index: Python-3.13.6/Modules/_ssl.c +=================================================================== +--- Python-3.13.6.orig/Modules/_ssl.c 2025-08-06 15:05:20.000000000 +0200 ++++ Python-3.13.6/Modules/_ssl.c 2025-08-12 11:34:50.730789308 +0200 +@@ -332,9 +332,6 @@ + * and shutdown methods check for chained exceptions. + */ + PyObject *exc; +- /* Lock to synchronize calls when the thread state is detached. +- See also gh-134698. */ +- PyMutex tstate_mutex; + } PySSLSocket; + + typedef struct { +@@ -846,7 +843,6 @@ + self->server_hostname = NULL; + self->err = err; + self->exc = NULL; +- self->tstate_mutex = (PyMutex){0}; + + /* Make sure the SSL error state is initialized */ + ERR_clear_error(); +@@ -919,12 +915,12 @@ + BIO_set_nbio(SSL_get_wbio(self->ssl), 1); + } + +- PySSL_BEGIN_ALLOW_THREADS(self) ++ Py_BEGIN_ALLOW_THREADS; + if (socket_type == PY_SSL_CLIENT) + SSL_set_connect_state(self->ssl); + else + SSL_set_accept_state(self->ssl); +- PySSL_END_ALLOW_THREADS(self) ++ Py_END_ALLOW_THREADS; + + self->socket_type = socket_type; + if (sock != NULL) { +@@ -993,10 +989,11 @@ + /* Actually negotiate SSL connection */ + /* XXX If SSL_do_handshake() returns 0, it's also a failure. */ + do { +- PySSL_BEGIN_ALLOW_THREADS(self) ++ Py_BEGIN_ALLOW_THREADS + ret = SSL_do_handshake(self->ssl); + err = _PySSL_errno(ret < 1, self->ssl, ret); +- PySSL_END_ALLOW_THREADS(self) ++ Py_END_ALLOW_THREADS; ++ _PySSL_FIX_ERRNO; + self->err = err; + + if (PyErr_CheckSignals()) +@@ -2462,10 +2459,11 @@ + } + + do { +- PySSL_BEGIN_ALLOW_THREADS(self) ++ Py_BEGIN_ALLOW_THREADS; + retval = SSL_write_ex(self->ssl, b->buf, (size_t)b->len, &count); + err = _PySSL_errno(retval == 0, self->ssl, retval); +- PySSL_END_ALLOW_THREADS(self) ++ Py_END_ALLOW_THREADS; ++ _PySSL_FIX_ERRNO; + self->err = err; + + if (PyErr_CheckSignals()) +@@ -2523,10 +2521,11 @@ + int count = 0; + _PySSLError err; + +- PySSL_BEGIN_ALLOW_THREADS(self) ++ Py_BEGIN_ALLOW_THREADS; + count = SSL_pending(self->ssl); + err = _PySSL_errno(count < 0, self->ssl, count); +- PySSL_END_ALLOW_THREADS(self) ++ Py_END_ALLOW_THREADS; ++ _PySSL_FIX_ERRNO; + self->err = err; + + if (count < 0) +@@ -2617,10 +2616,11 @@ + deadline = _PyDeadline_Init(timeout); + + do { +- PySSL_BEGIN_ALLOW_THREADS(self) ++ Py_BEGIN_ALLOW_THREADS; + retval = SSL_read_ex(self->ssl, mem, (size_t)len, &count); + err = _PySSL_errno(retval == 0, self->ssl, retval); +- PySSL_END_ALLOW_THREADS(self) ++ Py_END_ALLOW_THREADS; ++ _PySSL_FIX_ERRNO; + self->err = err; + + if (PyErr_CheckSignals()) +@@ -2719,7 +2719,7 @@ + } + + while (1) { +- PySSL_BEGIN_ALLOW_THREADS(self) ++ Py_BEGIN_ALLOW_THREADS; + /* Disable read-ahead so that unwrap can work correctly. + * Otherwise OpenSSL might read in too much data, + * eating clear text data that happens to be +@@ -2732,7 +2732,8 @@ + SSL_set_read_ahead(self->ssl, 0); + ret = SSL_shutdown(self->ssl); + err = _PySSL_errno(ret < 0, self->ssl, ret); +- PySSL_END_ALLOW_THREADS(self) ++ Py_END_ALLOW_THREADS; ++ _PySSL_FIX_ERRNO; + self->err = err; + + /* If err == 1, a secure shutdown with SSL_shutdown() is complete */ +Index: Python-3.13.6/Modules/_ssl/debughelpers.c +=================================================================== +--- Python-3.13.6.orig/Modules/_ssl/debughelpers.c 2025-08-06 15:05:20.000000000 +0200 ++++ Python-3.13.6/Modules/_ssl/debughelpers.c 2025-08-12 11:34:50.731270311 +0200 +@@ -135,7 +135,6 @@ + * critical debug helper. + */ + +- assert(PyMutex_IsLocked(&ssl_obj->tstate_mutex)); + Py_BEGIN_ALLOW_THREADS + PyThread_acquire_lock(lock, 1); + res = BIO_printf(ssl_obj->ctx->keylog_bio, "%s\n", line); diff --git a/python313.changes b/python313.changes index 6e417f6..9107409 100644 --- a/python313.changes +++ b/python313.changes @@ -1,3 +1,10 @@ +------------------------------------------------------------------- +Tue Aug 12 09:16:40 UTC 2025 - Matej Cepl + +- Add gh137583-only-lock-SSL-context.patch fixing the + regression in 3.13.6 by breaking non-blocking TLS connections + (gh#python/cpython#137583). + ------------------------------------------------------------------- Thu Aug 7 10:08:11 UTC 2025 - Matej Cepl diff --git a/python313.spec b/python313.spec index ef9e2af..2207d5d 100644 --- a/python313.spec +++ b/python313.spec @@ -231,6 +231,9 @@ Patch42: gh126985-mv-pyvenv.cfg2getpath.patch # PATCH-FIX-UPSTREAM bsc1243155-sphinx-non-determinism.patch bsc#1243155 mcepl@suse.com # Doc: Generate ids for audit_events using docname Patch43: bsc1243155-sphinx-non-determinism.patch +# PATCH-FIX-UPSTREAM gh137583-only-lock-SSL-context.patch gh#python/cpython#137583 mcepl@suse.com +# Only lock the SSL context, not the SSL socket +Patch44: gh137583-only-lock-SSL-context.patch BuildRequires: autoconf-archive BuildRequires: automake BuildRequires: fdupes