Skip to content

Commit 8b31d08

Browse files
authored
gh-149816: Fix SNI callback callable race (GH-150018)
1 parent 409fa8e commit 8b31d08

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()
@@ -5153,12 +5154,15 @@ _servername_callback(SSL *s, int *al, void *args)
51535154
PyObject *result;
51545155
/* The high-level ssl.SSLSocket object */
51555156
PyObject *ssl_socket;
5157+
PyObject *sni_cb;
51565158
const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
51575159
PyGILState_STATE gstate = PyGILState_Ensure();
51585160

5159-
if (sslctx->set_sni_cb == NULL) {
5160-
/* remove race condition in this the call back while if removing the
5161-
* callback is in progress */
5161+
Py_BEGIN_CRITICAL_SECTION(sslctx);
5162+
sni_cb = Py_XNewRef(sslctx->set_sni_cb);
5163+
Py_END_CRITICAL_SECTION();
5164+
5165+
if (sni_cb == NULL) {
51625166
PyGILState_Release(gstate);
51635167
return SSL_TLSEXT_ERR_OK;
51645168
}
@@ -5185,7 +5189,7 @@ _servername_callback(SSL *s, int *al, void *args)
51855189
goto error;
51865190

51875191
if (servername == NULL) {
5188-
result = PyObject_CallFunctionObjArgs(sslctx->set_sni_cb, ssl_socket,
5192+
result = PyObject_CallFunctionObjArgs(sni_cb, ssl_socket,
51895193
Py_None, sslctx, NULL);
51905194
}
51915195
else {
@@ -5212,7 +5216,7 @@ _servername_callback(SSL *s, int *al, void *args)
52125216
}
52135217
Py_DECREF(servername_bytes);
52145218
result = PyObject_CallFunctionObjArgs(
5215-
sslctx->set_sni_cb, ssl_socket, servername_str,
5219+
sni_cb, ssl_socket, servername_str,
52165220
sslctx, NULL);
52175221
Py_DECREF(servername_str);
52185222
}
@@ -5222,7 +5226,7 @@ _servername_callback(SSL *s, int *al, void *args)
52225226
PyErr_FormatUnraisable("Exception ignored "
52235227
"in ssl servername callback "
52245228
"while calling set SNI callback %R",
5225-
sslctx->set_sni_cb);
5229+
sni_cb);
52265230
*al = SSL_AD_HANDSHAKE_FAILURE;
52275231
ret = SSL_TLSEXT_ERR_ALERT_FATAL;
52285232
}
@@ -5247,11 +5251,13 @@ _servername_callback(SSL *s, int *al, void *args)
52475251
Py_DECREF(result);
52485252
}
52495253

5254+
Py_DECREF(sni_cb);
52505255
PyGILState_Release(gstate);
52515256
return ret;
52525257

52535258
error:
52545259
Py_XDECREF(ssl_socket);
5260+
Py_XDECREF(sni_cb);
52555261
*al = SSL_AD_INTERNAL_ERROR;
52565262
ret = SSL_TLSEXT_ERR_ALERT_FATAL;
52575263
PyGILState_Release(gstate);
@@ -5301,20 +5307,18 @@ _ssl__SSLContext_sni_callback_set_impl(PySSLContext *self, PyObject *value)
53015307
"sni_callback cannot be set on TLS_CLIENT context");
53025308
return -1;
53035309
}
5304-
Py_CLEAR(self->set_sni_cb);
5305-
if (value == Py_None) {
5310+
if (!PyCallable_Check(value)) {
53065311
SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL);
5307-
}
5308-
else {
5309-
if (!PyCallable_Check(value)) {
5310-
SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL);
5311-
PyErr_SetString(PyExc_TypeError,
5312-
"not a callable object");
5312+
Py_CLEAR(self->set_sni_cb);
5313+
if (value != Py_None) {
5314+
PyErr_SetString(PyExc_TypeError, "not a callable object");
53135315
return -1;
53145316
}
5315-
self->set_sni_cb = Py_NewRef(value);
5316-
SSL_CTX_set_tlsext_servername_callback(self->ctx, _servername_callback);
5317+
}
5318+
else {
5319+
Py_XSETREF(self->set_sni_cb, Py_NewRef(value));
53175320
SSL_CTX_set_tlsext_servername_arg(self->ctx, self);
5321+
SSL_CTX_set_tlsext_servername_callback(self->ctx, _servername_callback);
53185322
}
53195323
return 0;
53205324
}

0 commit comments

Comments
 (0)