Skip to content

Commit 9997975

Browse files
miss-islingtonkiri11encukou
authored
[3.15] gh-149816: Fix SNI callback callable race (GH-150018) (GH-150099)
(cherry picked from commit 8b31d08) Co-authored-by: Kirill Ignatev <kiri11@users.noreply.github.com> Co-authored-by: Petr Viktorin <encukou@gmail.com>
1 parent 199751e commit 9997975

3 files changed

Lines changed: 74 additions & 16 deletions

File tree

Lib/test/test_ssl.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1606,6 +1606,59 @@ def dummycallback(sock, servername, ctx, cycle=ctx):
16061606
gc.collect()
16071607
self.assertIs(wr(), None)
16081608

1609+
@unittest.skipUnless(support.Py_GIL_DISABLED,
1610+
"test is only useful if the GIL is disabled")
1611+
@threading_helper.requires_working_threading()
1612+
def test_sni_callback_race(self):
1613+
# Replacing sni_callback while handshakes are in-flight must not
1614+
# crash (use-after-free on the callback in free-threaded builds).
1615+
client_ctx, server_ctx, hostname = testing_context()
1616+
1617+
server_ctx.sni_callback = lambda *a: None
1618+
done = threading.Event()
1619+
1620+
def do_handshakes():
1621+
while not done.is_set():
1622+
c_in = ssl.MemoryBIO()
1623+
c_out = ssl.MemoryBIO()
1624+
s_in = ssl.MemoryBIO()
1625+
s_out = ssl.MemoryBIO()
1626+
client = client_ctx.wrap_bio(
1627+
c_in, c_out, server_hostname=hostname)
1628+
server = server_ctx.wrap_bio(s_in, s_out, server_side=True)
1629+
for _ in range(50):
1630+
try:
1631+
client.do_handshake()
1632+
except ssl.SSLWantReadError:
1633+
pass
1634+
except ssl.SSLError:
1635+
break
1636+
if c_out.pending:
1637+
s_in.write(c_out.read())
1638+
try:
1639+
server.do_handshake()
1640+
except ssl.SSLWantReadError:
1641+
pass
1642+
except ssl.SSLError:
1643+
break
1644+
if s_out.pending:
1645+
c_in.write(s_out.read())
1646+
1647+
def toggle_callback():
1648+
while not done.is_set():
1649+
server_ctx.sni_callback = lambda *a: None
1650+
server_ctx.sni_callback = None
1651+
1652+
workers = max(4, (os.cpu_count() or 4) * 2)
1653+
threads = [threading.Thread(target=do_handshakes)
1654+
for _ in range(workers)]
1655+
threads.append(threading.Thread(target=toggle_callback))
1656+
1657+
with threading_helper.catch_threading_exception() as cm:
1658+
with threading_helper.start_threads(threads):
1659+
done.set()
1660+
self.assertIsNone(cm.exc_value)
1661+
16091662
def test_cert_store_stats(self):
16101663
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
16111664
self.assertEqual(ctx.cert_store_stats(),
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fix race condition in :attr:`ssl.SSLContext.sni_callback`

Modules/_ssl.c

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#define OPENSSL_NO_DEPRECATED 1
2727

2828
#include "Python.h"
29+
#include "pycore_critical_section.h" // Py_BEGIN_CRITICAL_SECTION()
2930
#include "pycore_fileutils.h" // _PyIsSelectable_fd()
3031
#include "pycore_long.h" // _PyLong_UnsignedLongLong_Converter()
3132
#include "pycore_pyerrors.h" // _PyErr_ChainExceptions1()
@@ -5151,12 +5152,15 @@ _servername_callback(SSL *s, int *al, void *args)
51515152
PyObject *result;
51525153
/* The high-level ssl.SSLSocket object */
51535154
PyObject *ssl_socket;
5155+
PyObject *sni_cb;
51545156
const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
51555157
PyGILState_STATE gstate = PyGILState_Ensure();
51565158

5157-
if (sslctx->set_sni_cb == NULL) {
5158-
/* remove race condition in this the call back while if removing the
5159-
* callback is in progress */
5159+
Py_BEGIN_CRITICAL_SECTION(sslctx);
5160+
sni_cb = Py_XNewRef(sslctx->set_sni_cb);
5161+
Py_END_CRITICAL_SECTION();
5162+
5163+
if (sni_cb == NULL) {
51605164
PyGILState_Release(gstate);
51615165
return SSL_TLSEXT_ERR_OK;
51625166
}
@@ -5183,7 +5187,7 @@ _servername_callback(SSL *s, int *al, void *args)
51835187
goto error;
51845188

51855189
if (servername == NULL) {
5186-
result = PyObject_CallFunctionObjArgs(sslctx->set_sni_cb, ssl_socket,
5190+
result = PyObject_CallFunctionObjArgs(sni_cb, ssl_socket,
51875191
Py_None, sslctx, NULL);
51885192
}
51895193
else {
@@ -5210,7 +5214,7 @@ _servername_callback(SSL *s, int *al, void *args)
52105214
}
52115215
Py_DECREF(servername_bytes);
52125216
result = PyObject_CallFunctionObjArgs(
5213-
sslctx->set_sni_cb, ssl_socket, servername_str,
5217+
sni_cb, ssl_socket, servername_str,
52145218
sslctx, NULL);
52155219
Py_DECREF(servername_str);
52165220
}
@@ -5220,7 +5224,7 @@ _servername_callback(SSL *s, int *al, void *args)
52205224
PyErr_FormatUnraisable("Exception ignored "
52215225
"in ssl servername callback "
52225226
"while calling set SNI callback %R",
5223-
sslctx->set_sni_cb);
5227+
sni_cb);
52245228
*al = SSL_AD_HANDSHAKE_FAILURE;
52255229
ret = SSL_TLSEXT_ERR_ALERT_FATAL;
52265230
}
@@ -5245,11 +5249,13 @@ _servername_callback(SSL *s, int *al, void *args)
52455249
Py_DECREF(result);
52465250
}
52475251

5252+
Py_DECREF(sni_cb);
52485253
PyGILState_Release(gstate);
52495254
return ret;
52505255

52515256
error:
52525257
Py_XDECREF(ssl_socket);
5258+
Py_XDECREF(sni_cb);
52535259
*al = SSL_AD_INTERNAL_ERROR;
52545260
ret = SSL_TLSEXT_ERR_ALERT_FATAL;
52555261
PyGILState_Release(gstate);
@@ -5298,20 +5304,18 @@ _ssl__SSLContext_sni_callback_set_impl(PySSLContext *self, PyObject *value)
52985304
"sni_callback cannot be set on TLS_CLIENT context");
52995305
return -1;
53005306
}
5301-
Py_CLEAR(self->set_sni_cb);
5302-
if (value == Py_None) {
5307+
if (!PyCallable_Check(value)) {
53035308
SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL);
5304-
}
5305-
else {
5306-
if (!PyCallable_Check(value)) {
5307-
SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL);
5308-
PyErr_SetString(PyExc_TypeError,
5309-
"not a callable object");
5309+
Py_CLEAR(self->set_sni_cb);
5310+
if (value != Py_None) {
5311+
PyErr_SetString(PyExc_TypeError, "not a callable object");
53105312
return -1;
53115313
}
5312-
self->set_sni_cb = Py_NewRef(value);
5313-
SSL_CTX_set_tlsext_servername_callback(self->ctx, _servername_callback);
5314+
}
5315+
else {
5316+
Py_XSETREF(self->set_sni_cb, Py_NewRef(value));
53145317
SSL_CTX_set_tlsext_servername_arg(self->ctx, self);
5318+
SSL_CTX_set_tlsext_servername_callback(self->ctx, _servername_callback);
53155319
}
53165320
return 0;
53175321
}

0 commit comments

Comments
 (0)