From 5a175898b0f917375eb0b51f608fffbd80f83e69 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 21 Aug 2026 13:50:25 +0300 Subject: [PATCH] gh-156166: Fix setting and deleting SSLContext._msg_callback (GH-156167) The setter released the old callback before validating the new value, so a failed assignment or a deletion removed it. (cherry picked from commit 67f4d53425d4f7df559b4a0ba5bfb66e795854c7) --- Lib/test/test_ssl.py | 12 ++++++++++ ...-08-21-13-30-00.gh-issue-156166.Xv8pQm.rst | 3 +++ Modules/_ssl/debughelpers.c | 22 +++++++++++++------ 3 files changed, 30 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-08-21-13-30-00.gh-issue-156166.Xv8pQm.rst diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index a2de16a428abab..227b6f92cd5df6 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -5266,6 +5266,18 @@ def msg_cb(conn, direction, version, content_type, msg_type, data): with self.assertRaises(TypeError): client_context._msg_callback = object() + # the attribute of the underlying C type accepts only a callable + # and cannot be deleted + descr = _ssl._SSLContext.__dict__['_msg_callback'] + with self.assertRaises(TypeError): + descr.__set__(client_context, object()) + # a failed assignment does not change the value + self.assertIs(client_context._msg_callback, msg_cb) + with self.assertRaisesRegex(AttributeError, 'cannot be deleted'): + descr.__delete__(client_context) + # a failed deletion does not change the value + self.assertIs(client_context._msg_callback, msg_cb) + def test_msg_callback_tls12(self): client_context, server_context, hostname = testing_context() client_context.maximum_version = ssl.TLSVersion.TLSv1_2 diff --git a/Misc/NEWS.d/next/Library/2026-08-21-13-30-00.gh-issue-156166.Xv8pQm.rst b/Misc/NEWS.d/next/Library/2026-08-21-13-30-00.gh-issue-156166.Xv8pQm.rst new file mode 100644 index 00000000000000..2417479d333e3e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-21-13-30-00.gh-issue-156166.Xv8pQm.rst @@ -0,0 +1,3 @@ +:mod:`ssl`: A failed assignment or deletion of the ``_msg_callback`` +attribute of :class:`ssl.SSLContext` no longer removes the current callback. +Deleting it now raises :exc:`AttributeError` instead of :exc:`TypeError`. diff --git a/Modules/_ssl/debughelpers.c b/Modules/_ssl/debughelpers.c index 01a54b59e953cb..177019ea985af6 100644 --- a/Modules/_ssl/debughelpers.c +++ b/Modules/_ssl/debughelpers.c @@ -100,20 +100,28 @@ _PySSLContext_set_msg_callback(PyObject *op, PyObject *arg, void *Py_UNUSED(closure)) { PySSLContext *self = PySSLContext_CAST(op); - Py_CLEAR(self->msg_cb); + if (arg == NULL) { + PyErr_Format(PyExc_AttributeError, + "attribute '_msg_callback' of '%.100s' objects " + "cannot be deleted", Py_TYPE(op)->tp_name); + return -1; + } + if (arg != Py_None && !PyCallable_Check(arg)) { + PyErr_SetString(PyExc_TypeError, + "not a callable object"); + return -1; + } + /* Releasing the old callback can run arbitrary code. */ + PyObject *old_cb = self->msg_cb; if (arg == Py_None) { + self->msg_cb = NULL; SSL_CTX_set_msg_callback(self->ctx, NULL); } else { - if (!PyCallable_Check(arg)) { - SSL_CTX_set_msg_callback(self->ctx, NULL); - PyErr_SetString(PyExc_TypeError, - "not a callable object"); - return -1; - } self->msg_cb = Py_NewRef(arg); SSL_CTX_set_msg_callback(self->ctx, _PySSL_msg_callback); } + Py_XDECREF(old_cb); return 0; }