Skip to content

Commit 0a070e2

Browse files
committed
Only lock the SSL context, not the SSL socket.
1 parent 8943bb7 commit 0a070e2

2 files changed

Lines changed: 41 additions & 14 deletions

File tree

Lib/test/test_ssl.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4620,6 +4620,28 @@ def server_callback(identity):
46204620
with client_context.wrap_socket(socket.socket()) as s:
46214621
s.connect((HOST, server.port))
46224622

4623+
def test_thread_recv_while_main_thread_sends(self):
4624+
# GH-137583: Locking was added to calls to send() and recv() on SSL
4625+
# socket objects. This seemed fine at the surface level because those
4626+
# calls weren't re-entrant, but recv() calls would implicitly mimick
4627+
# holding a lock by blocking until it received data. This means that
4628+
# if a thread started to infinitely block until data was received, calls
4629+
# to send() would deadlock, because it would wait forever on the lock
4630+
# that the recv() call held.
4631+
data = b"A" * 5000
4632+
def background(sock):
4633+
received = sock.recv(5000)
4634+
assert received == data
4635+
4636+
client_context, server_context, _ = testing_context()
4637+
server = ThreadedEchoServer(context=server_context)
4638+
with server:
4639+
with client_context.wrap_socket(socket.socket()) as sock:
4640+
sock.connect((HOST, server.port))
4641+
thread = threading.Thread(target=background, args=(sock,))
4642+
thread.start()
4643+
sock.sendall(data)
4644+
46234645

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

Modules/_ssl.c

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -994,12 +994,11 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
994994
BIO_set_nbio(SSL_get_wbio(self->ssl), 1);
995995
}
996996

997-
PySSL_BEGIN_ALLOW_THREADS(self)
997+
// No other threads can have access to this, so no need to lock it.
998998
if (socket_type == PY_SSL_CLIENT)
999999
SSL_set_connect_state(self->ssl);
10001000
else
10011001
SSL_set_accept_state(self->ssl);
1002-
PySSL_END_ALLOW_THREADS(self)
10031002

10041003
self->socket_type = socket_type;
10051004
if (sock != NULL) {
@@ -1068,10 +1067,11 @@ _ssl__SSLSocket_do_handshake_impl(PySSLSocket *self)
10681067
/* Actually negotiate SSL connection */
10691068
/* XXX If SSL_do_handshake() returns 0, it's also a failure. */
10701069
do {
1071-
PySSL_BEGIN_ALLOW_THREADS(self)
1070+
Py_BEGIN_ALLOW_THREADS
10721071
ret = SSL_do_handshake(self->ssl);
10731072
err = _PySSL_errno(ret < 1, self->ssl, ret);
1074-
PySSL_END_ALLOW_THREADS(self)
1073+
Py_END_ALLOW_THREADS;
1074+
_PySSL_FIX_ERRNO;
10751075
self->err = err;
10761076

10771077
if (PyErr_CheckSignals())
@@ -2615,10 +2615,11 @@ _ssl__SSLSocket_sendfile_impl(PySSLSocket *self, int fd, Py_off_t offset,
26152615
}
26162616

26172617
do {
2618-
PySSL_BEGIN_ALLOW_THREADS(self)
2618+
Py_BEGIN_ALLOW_THREADS
26192619
retval = SSL_sendfile(self->ssl, fd, (off_t)offset, size, flags);
26202620
err = _PySSL_errno(retval < 0, self->ssl, (int)retval);
2621-
PySSL_END_ALLOW_THREADS(self)
2621+
Py_END_ALLOW_THREADS;
2622+
_PySSL_FIX_ERRNO;
26222623
self->err = err;
26232624

26242625
if (PyErr_CheckSignals()) {
@@ -2746,10 +2747,11 @@ _ssl__SSLSocket_write_impl(PySSLSocket *self, Py_buffer *b)
27462747
}
27472748

27482749
do {
2749-
PySSL_BEGIN_ALLOW_THREADS(self)
2750+
Py_BEGIN_ALLOW_THREADS;
27502751
retval = SSL_write_ex(self->ssl, b->buf, (size_t)b->len, &count);
27512752
err = _PySSL_errno(retval == 0, self->ssl, retval);
2752-
PySSL_END_ALLOW_THREADS(self)
2753+
Py_END_ALLOW_THREADS;
2754+
_PySSL_FIX_ERRNO;
27532755
self->err = err;
27542756

27552757
if (PyErr_CheckSignals())
@@ -2807,10 +2809,11 @@ _ssl__SSLSocket_pending_impl(PySSLSocket *self)
28072809
int count = 0;
28082810
_PySSLError err;
28092811

2810-
PySSL_BEGIN_ALLOW_THREADS(self)
2812+
Py_BEGIN_ALLOW_THREADS;
28112813
count = SSL_pending(self->ssl);
28122814
err = _PySSL_errno(count < 0, self->ssl, count);
2813-
PySSL_END_ALLOW_THREADS(self)
2815+
Py_END_ALLOW_THREADS;
2816+
_PySSL_FIX_ERRNO;
28142817
self->err = err;
28152818

28162819
if (count < 0)
@@ -2901,10 +2904,11 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, Py_ssize_t len,
29012904
deadline = _PyDeadline_Init(timeout);
29022905

29032906
do {
2904-
PySSL_BEGIN_ALLOW_THREADS(self)
2907+
Py_BEGIN_ALLOW_THREADS;
29052908
retval = SSL_read_ex(self->ssl, mem, (size_t)len, &count);
29062909
err = _PySSL_errno(retval == 0, self->ssl, retval);
2907-
PySSL_END_ALLOW_THREADS(self)
2910+
Py_END_ALLOW_THREADS;
2911+
_PySSL_FIX_ERRNO;
29082912
self->err = err;
29092913

29102914
if (PyErr_CheckSignals())
@@ -3003,7 +3007,7 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
30033007
}
30043008

30053009
while (1) {
3006-
PySSL_BEGIN_ALLOW_THREADS(self)
3010+
Py_BEGIN_ALLOW_THREADS;
30073011
/* Disable read-ahead so that unwrap can work correctly.
30083012
* Otherwise OpenSSL might read in too much data,
30093013
* eating clear text data that happens to be
@@ -3016,7 +3020,8 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
30163020
SSL_set_read_ahead(self->ssl, 0);
30173021
ret = SSL_shutdown(self->ssl);
30183022
err = _PySSL_errno(ret < 0, self->ssl, ret);
3019-
PySSL_END_ALLOW_THREADS(self)
3023+
Py_END_ALLOW_THREADS;
3024+
_PySSL_FIX_ERRNO;
30203025
self->err = err;
30213026

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

0 commit comments

Comments
 (0)