Skip to content

Commit 4a37dd6

Browse files
[3.13] gh-134698: Hold a lock when the thread state is detached in ssl (GH-134724) (#137126)
Lock when the thread state is detached. (cherry picked from commit e047a35) or really from the 3.14 backport fd565fd Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
1 parent 89f256e commit 4a37dd6

File tree

4 files changed

+87
-48
lines changed

4 files changed

+87
-48
lines changed

Lib/test/test_ssl.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1266,6 +1266,25 @@ def getpass(self):
12661266
# Make sure the password function isn't called if it isn't needed
12671267
ctx.load_cert_chain(CERTFILE, password=getpass_exception)
12681268

1269+
@threading_helper.requires_working_threading()
1270+
def test_load_cert_chain_thread_safety(self):
1271+
# gh-134698: _ssl detaches the thread state (and as such,
1272+
# releases the GIL and critical sections) around expensive
1273+
# OpenSSL calls. Unfortunately, OpenSSL structures aren't
1274+
# thread-safe, so executing these calls concurrently led
1275+
# to crashes.
1276+
ctx = ssl.create_default_context()
1277+
1278+
def race():
1279+
ctx.load_cert_chain(CERTFILE)
1280+
1281+
threads = [threading.Thread(target=race) for _ in range(8)]
1282+
with threading_helper.catch_threading_exception() as cm:
1283+
with threading_helper.start_threads(threads):
1284+
pass
1285+
1286+
self.assertIsNone(cm.exc_value)
1287+
12691288
def test_load_verify_locations(self):
12701289
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
12711290
ctx.load_verify_locations(CERTFILE)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a crash when calling methods of :class:`ssl.SSLContext` or
2+
:class:`ssl.SSLSocket` across multiple threads.

Modules/_ssl.c

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,14 @@
4242
/* Redefined below for Windows debug builds after important #includes */
4343
#define _PySSL_FIX_ERRNO
4444

45-
#define PySSL_BEGIN_ALLOW_THREADS_S(save) \
46-
do { (save) = PyEval_SaveThread(); } while(0)
47-
#define PySSL_END_ALLOW_THREADS_S(save) \
48-
do { PyEval_RestoreThread(save); _PySSL_FIX_ERRNO; } while(0)
49-
#define PySSL_BEGIN_ALLOW_THREADS { \
45+
#define PySSL_BEGIN_ALLOW_THREADS_S(save, mutex) \
46+
do { (save) = PyEval_SaveThread(); PyMutex_Lock(mutex); } while(0)
47+
#define PySSL_END_ALLOW_THREADS_S(save, mutex) \
48+
do { PyMutex_Unlock(mutex); PyEval_RestoreThread(save); _PySSL_FIX_ERRNO; } while(0)
49+
#define PySSL_BEGIN_ALLOW_THREADS(self) { \
5050
PyThreadState *_save = NULL; \
51-
PySSL_BEGIN_ALLOW_THREADS_S(_save);
52-
#define PySSL_END_ALLOW_THREADS PySSL_END_ALLOW_THREADS_S(_save); }
51+
PySSL_BEGIN_ALLOW_THREADS_S(_save, &self->tstate_mutex);
52+
#define PySSL_END_ALLOW_THREADS(self) PySSL_END_ALLOW_THREADS_S(_save, &self->tstate_mutex); }
5353

5454
#if defined(HAVE_POLL_H)
5555
#include <poll.h>
@@ -304,6 +304,9 @@ typedef struct {
304304
PyObject *psk_client_callback;
305305
PyObject *psk_server_callback;
306306
#endif
307+
/* Lock to synchronize calls when the thread state is detached.
308+
See also gh-134698. */
309+
PyMutex tstate_mutex;
307310
} PySSLContext;
308311

309312
typedef struct {
@@ -329,6 +332,9 @@ typedef struct {
329332
* and shutdown methods check for chained exceptions.
330333
*/
331334
PyObject *exc;
335+
/* Lock to synchronize calls when the thread state is detached.
336+
See also gh-134698. */
337+
PyMutex tstate_mutex;
332338
} PySSLSocket;
333339

334340
typedef struct {
@@ -840,13 +846,14 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
840846
self->server_hostname = NULL;
841847
self->err = err;
842848
self->exc = NULL;
849+
self->tstate_mutex = (PyMutex){0};
843850

844851
/* Make sure the SSL error state is initialized */
845852
ERR_clear_error();
846853

847-
PySSL_BEGIN_ALLOW_THREADS
854+
PySSL_BEGIN_ALLOW_THREADS(sslctx)
848855
self->ssl = SSL_new(ctx);
849-
PySSL_END_ALLOW_THREADS
856+
PySSL_END_ALLOW_THREADS(sslctx)
850857
if (self->ssl == NULL) {
851858
Py_DECREF(self);
852859
_setSSLError(get_state_ctx(self), NULL, 0, __FILE__, __LINE__);
@@ -912,12 +919,12 @@ newPySSLSocket(PySSLContext *sslctx, PySocketSockObject *sock,
912919
BIO_set_nbio(SSL_get_wbio(self->ssl), 1);
913920
}
914921

915-
PySSL_BEGIN_ALLOW_THREADS
922+
PySSL_BEGIN_ALLOW_THREADS(self)
916923
if (socket_type == PY_SSL_CLIENT)
917924
SSL_set_connect_state(self->ssl);
918925
else
919926
SSL_set_accept_state(self->ssl);
920-
PySSL_END_ALLOW_THREADS
927+
PySSL_END_ALLOW_THREADS(self)
921928

922929
self->socket_type = socket_type;
923930
if (sock != NULL) {
@@ -986,10 +993,10 @@ _ssl__SSLSocket_do_handshake_impl(PySSLSocket *self)
986993
/* Actually negotiate SSL connection */
987994
/* XXX If SSL_do_handshake() returns 0, it's also a failure. */
988995
do {
989-
PySSL_BEGIN_ALLOW_THREADS
996+
PySSL_BEGIN_ALLOW_THREADS(self)
990997
ret = SSL_do_handshake(self->ssl);
991998
err = _PySSL_errno(ret < 1, self->ssl, ret);
992-
PySSL_END_ALLOW_THREADS
999+
PySSL_END_ALLOW_THREADS(self)
9931000
self->err = err;
9941001

9951002
if (PyErr_CheckSignals())
@@ -2362,9 +2369,10 @@ PySSL_select(PySocketSockObject *s, int writing, PyTime_t timeout)
23622369
ms = (int)_PyTime_AsMilliseconds(timeout, _PyTime_ROUND_CEILING);
23632370
assert(ms <= INT_MAX);
23642371

2365-
PySSL_BEGIN_ALLOW_THREADS
2372+
Py_BEGIN_ALLOW_THREADS
23662373
rc = poll(&pollfd, 1, (int)ms);
2367-
PySSL_END_ALLOW_THREADS
2374+
Py_END_ALLOW_THREADS
2375+
_PySSL_FIX_ERRNO;
23682376
#else
23692377
/* Guard against socket too large for select*/
23702378
if (!_PyIsSelectable_fd(s->sock_fd))
@@ -2376,13 +2384,14 @@ PySSL_select(PySocketSockObject *s, int writing, PyTime_t timeout)
23762384
FD_SET(s->sock_fd, &fds);
23772385

23782386
/* Wait until the socket becomes ready */
2379-
PySSL_BEGIN_ALLOW_THREADS
2387+
Py_BEGIN_ALLOW_THREADS
23802388
nfds = Py_SAFE_DOWNCAST(s->sock_fd+1, SOCKET_T, int);
23812389
if (writing)
23822390
rc = select(nfds, NULL, &fds, NULL, &tv);
23832391
else
23842392
rc = select(nfds, &fds, NULL, NULL, &tv);
2385-
PySSL_END_ALLOW_THREADS
2393+
Py_END_ALLOW_THREADS
2394+
_PySSL_FIX_ERRNO;
23862395
#endif
23872396

23882397
/* Return SOCKET_TIMED_OUT on timeout, SOCKET_OPERATION_OK otherwise
@@ -2453,10 +2462,10 @@ _ssl__SSLSocket_write_impl(PySSLSocket *self, Py_buffer *b)
24532462
}
24542463

24552464
do {
2456-
PySSL_BEGIN_ALLOW_THREADS
2465+
PySSL_BEGIN_ALLOW_THREADS(self)
24572466
retval = SSL_write_ex(self->ssl, b->buf, (size_t)b->len, &count);
24582467
err = _PySSL_errno(retval == 0, self->ssl, retval);
2459-
PySSL_END_ALLOW_THREADS
2468+
PySSL_END_ALLOW_THREADS(self)
24602469
self->err = err;
24612470

24622471
if (PyErr_CheckSignals())
@@ -2514,10 +2523,10 @@ _ssl__SSLSocket_pending_impl(PySSLSocket *self)
25142523
int count = 0;
25152524
_PySSLError err;
25162525

2517-
PySSL_BEGIN_ALLOW_THREADS
2526+
PySSL_BEGIN_ALLOW_THREADS(self)
25182527
count = SSL_pending(self->ssl);
25192528
err = _PySSL_errno(count < 0, self->ssl, count);
2520-
PySSL_END_ALLOW_THREADS
2529+
PySSL_END_ALLOW_THREADS(self)
25212530
self->err = err;
25222531

25232532
if (count < 0)
@@ -2608,10 +2617,10 @@ _ssl__SSLSocket_read_impl(PySSLSocket *self, Py_ssize_t len,
26082617
deadline = _PyDeadline_Init(timeout);
26092618

26102619
do {
2611-
PySSL_BEGIN_ALLOW_THREADS
2620+
PySSL_BEGIN_ALLOW_THREADS(self)
26122621
retval = SSL_read_ex(self->ssl, mem, (size_t)len, &count);
26132622
err = _PySSL_errno(retval == 0, self->ssl, retval);
2614-
PySSL_END_ALLOW_THREADS
2623+
PySSL_END_ALLOW_THREADS(self)
26152624
self->err = err;
26162625

26172626
if (PyErr_CheckSignals())
@@ -2710,7 +2719,7 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
27102719
}
27112720

27122721
while (1) {
2713-
PySSL_BEGIN_ALLOW_THREADS
2722+
PySSL_BEGIN_ALLOW_THREADS(self)
27142723
/* Disable read-ahead so that unwrap can work correctly.
27152724
* Otherwise OpenSSL might read in too much data,
27162725
* eating clear text data that happens to be
@@ -2723,7 +2732,7 @@ _ssl__SSLSocket_shutdown_impl(PySSLSocket *self)
27232732
SSL_set_read_ahead(self->ssl, 0);
27242733
ret = SSL_shutdown(self->ssl);
27252734
err = _PySSL_errno(ret < 0, self->ssl, ret);
2726-
PySSL_END_ALLOW_THREADS
2735+
PySSL_END_ALLOW_THREADS(self)
27272736
self->err = err;
27282737

27292738
/* If err == 1, a secure shutdown with SSL_shutdown() is complete */
@@ -3115,9 +3124,10 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
31153124
// no other thread can be touching this object yet.
31163125
// (Technically, we can't even lock if we wanted to, as the
31173126
// lock hasn't been initialized yet.)
3118-
PySSL_BEGIN_ALLOW_THREADS
3127+
Py_BEGIN_ALLOW_THREADS
31193128
ctx = SSL_CTX_new(method);
3120-
PySSL_END_ALLOW_THREADS
3129+
Py_END_ALLOW_THREADS
3130+
_PySSL_FIX_ERRNO;
31213131

31223132
if (ctx == NULL) {
31233133
_setSSLError(get_ssl_state(module), NULL, 0, __FILE__, __LINE__);
@@ -3143,6 +3153,7 @@ _ssl__SSLContext_impl(PyTypeObject *type, int proto_version)
31433153
self->psk_client_callback = NULL;
31443154
self->psk_server_callback = NULL;
31453155
#endif
3156+
self->tstate_mutex = (PyMutex){0};
31463157

31473158
/* Don't check host name by default */
31483159
if (proto_version == PY_SSL_VERSION_TLS_CLIENT) {
@@ -3259,9 +3270,10 @@ context_clear(PySSLContext *self)
32593270
Py_CLEAR(self->psk_server_callback);
32603271
#endif
32613272
if (self->keylog_bio != NULL) {
3262-
PySSL_BEGIN_ALLOW_THREADS
3273+
Py_BEGIN_ALLOW_THREADS
32633274
BIO_free_all(self->keylog_bio);
3264-
PySSL_END_ALLOW_THREADS
3275+
Py_END_ALLOW_THREADS
3276+
_PySSL_FIX_ERRNO;
32653277
self->keylog_bio = NULL;
32663278
}
32673279
return 0;
@@ -3980,7 +3992,8 @@ _password_callback(char *buf, int size, int rwflag, void *userdata)
39803992
_PySSLPasswordInfo *pw_info = (_PySSLPasswordInfo*) userdata;
39813993
PyObject *fn_ret = NULL;
39823994

3983-
PySSL_END_ALLOW_THREADS_S(pw_info->thread_state);
3995+
pw_info->thread_state = PyThreadState_Swap(pw_info->thread_state);
3996+
_PySSL_FIX_ERRNO;
39843997

39853998
if (pw_info->error) {
39863999
/* already failed previously. OpenSSL 3.0.0-alpha14 invokes the
@@ -4010,13 +4023,13 @@ _password_callback(char *buf, int size, int rwflag, void *userdata)
40104023
goto error;
40114024
}
40124025

4013-
PySSL_BEGIN_ALLOW_THREADS_S(pw_info->thread_state);
4026+
pw_info->thread_state = PyThreadState_Swap(pw_info->thread_state);
40144027
memcpy(buf, pw_info->password, pw_info->size);
40154028
return pw_info->size;
40164029

40174030
error:
40184031
Py_XDECREF(fn_ret);
4019-
PySSL_BEGIN_ALLOW_THREADS_S(pw_info->thread_state);
4032+
pw_info->thread_state = PyThreadState_Swap(pw_info->thread_state);
40204033
pw_info->error = 1;
40214034
return -1;
40224035
}
@@ -4069,10 +4082,10 @@ _ssl__SSLContext_load_cert_chain_impl(PySSLContext *self, PyObject *certfile,
40694082
SSL_CTX_set_default_passwd_cb(self->ctx, _password_callback);
40704083
SSL_CTX_set_default_passwd_cb_userdata(self->ctx, &pw_info);
40714084
}
4072-
PySSL_BEGIN_ALLOW_THREADS_S(pw_info.thread_state);
4085+
PySSL_BEGIN_ALLOW_THREADS_S(pw_info.thread_state, &self->tstate_mutex);
40734086
r = SSL_CTX_use_certificate_chain_file(self->ctx,
40744087
PyBytes_AS_STRING(certfile_bytes));
4075-
PySSL_END_ALLOW_THREADS_S(pw_info.thread_state);
4088+
PySSL_END_ALLOW_THREADS_S(pw_info.thread_state, &self->tstate_mutex);
40764089
if (r != 1) {
40774090
if (pw_info.error) {
40784091
ERR_clear_error();
@@ -4087,11 +4100,11 @@ _ssl__SSLContext_load_cert_chain_impl(PySSLContext *self, PyObject *certfile,
40874100
}
40884101
goto error;
40894102
}
4090-
PySSL_BEGIN_ALLOW_THREADS_S(pw_info.thread_state);
4103+
PySSL_BEGIN_ALLOW_THREADS_S(pw_info.thread_state, &self->tstate_mutex);
40914104
r = SSL_CTX_use_PrivateKey_file(self->ctx,
40924105
PyBytes_AS_STRING(keyfile ? keyfile_bytes : certfile_bytes),
40934106
SSL_FILETYPE_PEM);
4094-
PySSL_END_ALLOW_THREADS_S(pw_info.thread_state);
4107+
PySSL_END_ALLOW_THREADS_S(pw_info.thread_state, &self->tstate_mutex);
40954108
Py_CLEAR(keyfile_bytes);
40964109
Py_CLEAR(certfile_bytes);
40974110
if (r != 1) {
@@ -4108,9 +4121,9 @@ _ssl__SSLContext_load_cert_chain_impl(PySSLContext *self, PyObject *certfile,
41084121
}
41094122
goto error;
41104123
}
4111-
PySSL_BEGIN_ALLOW_THREADS_S(pw_info.thread_state);
4124+
PySSL_BEGIN_ALLOW_THREADS_S(pw_info.thread_state, &self->tstate_mutex);
41124125
r = SSL_CTX_check_private_key(self->ctx);
4113-
PySSL_END_ALLOW_THREADS_S(pw_info.thread_state);
4126+
PySSL_END_ALLOW_THREADS_S(pw_info.thread_state, &self->tstate_mutex);
41144127
if (r != 1) {
41154128
_setSSLError(get_state_ctx(self), NULL, 0, __FILE__, __LINE__);
41164129
goto error;
@@ -4327,9 +4340,9 @@ _ssl__SSLContext_load_verify_locations_impl(PySSLContext *self,
43274340
cafile_buf = PyBytes_AS_STRING(cafile_bytes);
43284341
if (capath)
43294342
capath_buf = PyBytes_AS_STRING(capath_bytes);
4330-
PySSL_BEGIN_ALLOW_THREADS
4343+
PySSL_BEGIN_ALLOW_THREADS(self)
43314344
r = SSL_CTX_load_verify_locations(self->ctx, cafile_buf, capath_buf);
4332-
PySSL_END_ALLOW_THREADS
4345+
PySSL_END_ALLOW_THREADS(self)
43334346
if (r != 1) {
43344347
if (errno != 0) {
43354348
PyErr_SetFromErrno(PyExc_OSError);
@@ -4381,10 +4394,11 @@ _ssl__SSLContext_load_dh_params_impl(PySSLContext *self, PyObject *filepath)
43814394
return NULL;
43824395

43834396
errno = 0;
4384-
PySSL_BEGIN_ALLOW_THREADS
4397+
Py_BEGIN_ALLOW_THREADS
43854398
dh = PEM_read_DHparams(f, NULL, NULL, NULL);
43864399
fclose(f);
4387-
PySSL_END_ALLOW_THREADS
4400+
Py_END_ALLOW_THREADS
4401+
_PySSL_FIX_ERRNO;
43884402
if (dh == NULL) {
43894403
if (errno != 0) {
43904404
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, filepath);
@@ -4536,6 +4550,7 @@ _ssl__SSLContext_set_default_verify_paths_impl(PySSLContext *self)
45364550
Py_BEGIN_ALLOW_THREADS
45374551
rc = SSL_CTX_set_default_verify_paths(self->ctx);
45384552
Py_END_ALLOW_THREADS
4553+
_PySSL_FIX_ERRNO;
45394554
if (!rc) {
45404555
_setSSLError(get_state_ctx(self), NULL, 0, __FILE__, __LINE__);
45414556
return NULL;

Modules/_ssl/debughelpers.c

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -135,13 +135,15 @@ _PySSL_keylog_callback(const SSL *ssl, const char *line)
135135
* critical debug helper.
136136
*/
137137

138-
PySSL_BEGIN_ALLOW_THREADS
138+
assert(PyMutex_IsLocked(&ssl_obj->tstate_mutex));
139+
Py_BEGIN_ALLOW_THREADS
139140
PyThread_acquire_lock(lock, 1);
140141
res = BIO_printf(ssl_obj->ctx->keylog_bio, "%s\n", line);
141142
e = errno;
142143
(void)BIO_flush(ssl_obj->ctx->keylog_bio);
143144
PyThread_release_lock(lock);
144-
PySSL_END_ALLOW_THREADS
145+
Py_END_ALLOW_THREADS
146+
_PySSL_FIX_ERRNO;
145147

146148
if (res == -1) {
147149
errno = e;
@@ -177,9 +179,10 @@ _PySSLContext_set_keylog_filename(PySSLContext *self, PyObject *arg, void *c) {
177179
if (self->keylog_bio != NULL) {
178180
BIO *bio = self->keylog_bio;
179181
self->keylog_bio = NULL;
180-
PySSL_BEGIN_ALLOW_THREADS
182+
Py_BEGIN_ALLOW_THREADS
181183
BIO_free_all(bio);
182-
PySSL_END_ALLOW_THREADS
184+
Py_END_ALLOW_THREADS
185+
_PySSL_FIX_ERRNO;
183186
}
184187

185188
if (arg == Py_None) {
@@ -201,13 +204,13 @@ _PySSLContext_set_keylog_filename(PySSLContext *self, PyObject *arg, void *c) {
201204
self->keylog_filename = Py_NewRef(arg);
202205

203206
/* Write a header for seekable, empty files (this excludes pipes). */
204-
PySSL_BEGIN_ALLOW_THREADS
207+
PySSL_BEGIN_ALLOW_THREADS(self)
205208
if (BIO_tell(self->keylog_bio) == 0) {
206209
BIO_puts(self->keylog_bio,
207210
"# TLS secrets log file, generated by OpenSSL / Python\n");
208211
(void)BIO_flush(self->keylog_bio);
209212
}
210-
PySSL_END_ALLOW_THREADS
213+
PySSL_END_ALLOW_THREADS(self)
211214
SSL_CTX_set_keylog_callback(self->ctx, _PySSL_keylog_callback);
212215
return 0;
213216
}

0 commit comments

Comments
 (0)