gh-149816: Fix SNI callback callable race#150018
Conversation
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
In Modules/_ssl.c
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
|
Most changes to Python require a NEWS entry. Add one using the blurb_it web app or the blurb command-line tool. If this change has little impact on Python users, wait for a maintainer to apply the |
| if (!PyCallable_Check(value)) { | ||
| SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL); | ||
| } | ||
| else { | ||
| if (!PyCallable_Check(value)) { | ||
| SSL_CTX_set_tlsext_servername_callback(self->ctx, NULL); | ||
| PyErr_SetString(PyExc_TypeError, | ||
| "not a callable object"); | ||
| Py_CLEAR(self->set_sni_cb); | ||
| if (value != Py_None) { | ||
| PyErr_SetString(PyExc_TypeError, "not a callable object"); | ||
| return -1; | ||
| } | ||
| self->set_sni_cb = Py_NewRef(value); | ||
| SSL_CTX_set_tlsext_servername_callback(self->ctx, _servername_callback); | ||
| } | ||
| else { | ||
| Py_INCREF(value); | ||
| PyObject *old_cb = _Py_atomic_exchange_ptr(&self->set_sni_cb, value); | ||
| Py_XDECREF(old_cb); | ||
| SSL_CTX_set_tlsext_servername_arg(self->ctx, self); | ||
| SSL_CTX_set_tlsext_servername_callback(self->ctx, _servername_callback); |
There was a problem hiding this comment.
These changes look unnecessary if set_sni_cb is always accessed in a critical section.
There was a problem hiding this comment.
Unfortunately just the critical section didn't help (thread sanitizer kept complaining). Probably because it's getting accessed from within ssl library itself, so we can't guard against that.
Problem
_servername_callbackcallsPyObject_CallFunctionObjArgs(sslctx->set_sni_cb, ...)using a shared field directly (cpython/Modules/_ssl.c:5188,cpython/Modules/_ssl.c:5215) without lockingsslctxand without taking a strong local reference. Another thread can concurrently clear the field in_ssl__SSLContext_sni_callback_set_implviaPy_CLEAR(self->set_sni_cb)(cpython/Modules/_ssl.c:5304), whose macro writes NULL then decrefs/frees the old object (cpython/Include/refcount.h:483-490). A concrete UAF path is: load callable pointer in_servername_callback→ concurrentPy_CLEARfrees callable → call dereferences stale pointer in_PyObject_VectorcallTstate(cpython/Objects/call.c:823).Fix
Fix adds a critical section and saves a copy into
sni_cb.Additionally, we ensure the swap of the new callback is atomic, without allowing race conditions.
Testing
Tested on MacOS.
Before
After
Using Repro:
More info