Skip to content

Commit 8ff02f8

Browse files
committed
Only lock the SSL context, not the SSL socket.
This solves a deadlock when a socket is blocked while waiting on data, which ended up causing a major regression in 3.13.6 (see gh-137583).
1 parent fd565fd commit 8ff02f8

File tree

3 files changed

+53
-17
lines changed

3 files changed

+53
-17
lines changed

Lib/test/test_ssl.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4557,6 +4557,42 @@ def server_callback(identity):
45574557
with client_context.wrap_socket(socket.socket()) as s:
45584558
s.connect((HOST, server.port))
45594559

4560+
def test_thread_recv_while_main_thread_sends(self):
4561+
# GH-137583: Locking was added to calls to send() and recv() on SSL
4562+
# socket objects. This seemed fine at the surface level because those
4563+
# calls weren't re-entrant, but recv() calls would implicitly mimick
4564+
# holding a lock by blocking until it received data. This means that
4565+
# if a thread started to infinitely block until data was received, calls
4566+
# to send() would deadlock, because it would wait forever on the lock
4567+
# that the recv() call held.
4568+
data = b"1" * 1024
4569+
event = threading.Event()
4570+
def background(sock):
4571+
event.set()
4572+
received = sock.recv(len(data))
4573+
self.assertEqual(received, data)
4574+
4575+
client_context, server_context, hostname = testing_context()
4576+
server = ThreadedEchoServer(context=server_context)
4577+
with server:
4578+
with client_context.wrap_socket(socket.socket(),
4579+
server_hostname=hostname) as sock:
4580+
sock.connect((HOST, server.port))
4581+
sock.settimeout(1)
4582+
sock.setblocking(1)
4583+
# Ensure that the server is ready to accept requests
4584+
sock.sendall(b"123")
4585+
self.assertEqual(sock.recv(3), b"123")
4586+
with threading_helper.catch_threading_exception() as cm:
4587+
thread = threading.Thread(target=background,
4588+
args=(sock,), daemon=True)
4589+
thread.start()
4590+
event.wait()
4591+
sock.sendall(data)
4592+
thread.join()
4593+
if cm.exc_value is not None:
4594+
raise cm.exc_value
4595+
45604596

45614597
@unittest.skipUnless(has_tls_version('TLSv1_3') and ssl.HAS_PHA,
45624598
"Test needs TLS 1.3 PHA")

Modules/_ssl.c

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -339,9 +339,6 @@ typedef struct {
339339
* and shutdown methods check for chained exceptions.
340340
*/
341341
PyObject *exc;
342-
/* Lock to synchronize calls when the thread state is detached.
343-
See also gh-134698. */
344-
PyMutex tstate_mutex;
345342
} PySSLSocket;
346343

347344
#define PySSLSocket_CAST(op) ((PySSLSocket *)(op))
@@ -891,7 +888,6 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
891888
self->server_hostname = NULL;
892889
self->err = err;
893890
self->exc = NULL;
894-
self->tstate_mutex = (PyMutex){0};
895891

896892
/* Make sure the SSL error state is initialized */
897893
ERR_clear_error();
@@ -967,12 +963,12 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
967963
BIO_set_nbio(SSL_get_wbio(self->ssl), 1);
968964
}
969965

970-
PySSL_BEGIN_ALLOW_THREADS(self)
966+
Py_BEGIN_ALLOW_THREADS;
971967
if (socket_type == PY_SSL_CLIENT)
972968
SSL_set_connect_state(self->ssl);
973969
else
974970
SSL_set_accept_state(self->ssl);
975-
PySSL_END_ALLOW_THREADS(self)
971+
Py_END_ALLOW_THREADS;
976972

977973
self->socket_type = socket_type;
978974
if (sock != NULL) {
@@ -1041,10 +1037,11 @@ _ssl__SSLSocket_do_handshake_impl(PySSLSocket *self)
10411037
/* Actually negotiate SSL connection */
10421038
/* XXX If SSL_do_handshake() returns 0, it's also a failure. */
10431039
do {
1044-
PySSL_BEGIN_ALLOW_THREADS(self)
1040+
Py_BEGIN_ALLOW_THREADS
10451041
ret = SSL_do_handshake(self->ssl);
10461042
err = _PySSL_errno(ret < 1, self->ssl, ret);
1047-
PySSL_END_ALLOW_THREADS(self)
1043+
Py_END_ALLOW_THREADS;
1044+
_PySSL_FIX_ERRNO;
10481045
self->err = err;
10491046

10501047
if (PyErr_CheckSignals())
@@ -2514,10 +2511,11 @@ _ssl__SSLSocket_write_impl(PySSLSocket *self, Py_buffer *b)
25142511
}
25152512

25162513
do {
2517-
PySSL_BEGIN_ALLOW_THREADS(self)
2514+
Py_BEGIN_ALLOW_THREADS;
25182515
retval = SSL_write_ex(self->ssl, b->buf, (size_t)b->len, &count);
25192516
err = _PySSL_errno(retval == 0, self->ssl, retval);
2520-
PySSL_END_ALLOW_THREADS(self)
2517+
Py_END_ALLOW_THREADS;
2518+
_PySSL_FIX_ERRNO;
25212519
self->err = err;
25222520

25232521
if (PyErr_CheckSignals())
@@ -2575,10 +2573,11 @@ _ssl__SSLSocket_pending_impl(PySSLSocket *self)
25752573
int count = 0;
25762574
_PySSLError err;
25772575

2578-
PySSL_BEGIN_ALLOW_THREADS(self)
2576+
Py_BEGIN_ALLOW_THREADS;
25792577
count = SSL_pending(self->ssl);
25802578
err = _PySSL_errno(count < 0, self->ssl, count);
2581-
PySSL_END_ALLOW_THREADS(self)
2579+
Py_END_ALLOW_THREADS;
2580+
_PySSL_FIX_ERRNO;
25822581
self->err = err;
25832582

25842583
if (count < 0)
@@ -2669,10 +2668,11 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, Py_ssize_t len,
26692668
deadline = _PyDeadline_Init(timeout);
26702669

26712670
do {
2672-
PySSL_BEGIN_ALLOW_THREADS(self)
2671+
Py_BEGIN_ALLOW_THREADS;
26732672
retval = SSL_read_ex(self->ssl, mem, (size_t)len, &count);
26742673
err = _PySSL_errno(retval == 0, self->ssl, retval);
2675-
PySSL_END_ALLOW_THREADS(self)
2674+
Py_END_ALLOW_THREADS;
2675+
_PySSL_FIX_ERRNO;
26762676
self->err = err;
26772677

26782678
if (PyErr_CheckSignals())
@@ -2771,7 +2771,7 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
27712771
}
27722772

27732773
while (1) {
2774-
PySSL_BEGIN_ALLOW_THREADS(self)
2774+
Py_BEGIN_ALLOW_THREADS;
27752775
/* Disable read-ahead so that unwrap can work correctly.
27762776
* Otherwise OpenSSL might read in too much data,
27772777
* eating clear text data that happens to be
@@ -2784,7 +2784,8 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
27842784
SSL_set_read_ahead(self->ssl, 0);
27852785
ret = SSL_shutdown(self->ssl);
27862786
err = _PySSL_errno(ret < 0, self->ssl, ret);
2787-
PySSL_END_ALLOW_THREADS(self)
2787+
Py_END_ALLOW_THREADS;
2788+
_PySSL_FIX_ERRNO;
27882789
self->err = err;
27892790

27902791
/* If err == 1, a secure shutdown with SSL_shutdown() is complete */

Modules/_ssl/debughelpers.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ _PySSL_keylog_callback(const SSL *ssl, const char *line)
140140
* critical debug helper.
141141
*/
142142

143-
assert(PyMutex_IsLocked(&ssl_obj->tstate_mutex));
144143
Py_BEGIN_ALLOW_THREADS
145144
PyThread_acquire_lock(lock, 1);
146145
res = BIO_printf(ssl_obj->ctx->keylog_bio, "%s\n", line);

0 commit comments

Comments
 (0)