Skip to content

Commit 6356b86

Browse files
[3.14] pythongh-134698: Hold a lock when the thread state is detached in ssl (pythonGH-134724)
Lock when the thread state is detached. (cherry picked from commit e047a35) Co-authored-by: Peter Bierma <zintensitydev@gmail.com> Co-authored-by: Gregory P. Smith <greg@krypto.org>
1 parent a995a11 commit 6356b86

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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy