gh-156166: Fix setting and deleting SSLContext._msg_callback (GH-156167)

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/67f4d53425d4f7df559b4a0ba5bfb66e795854c7
commit: 67f4d53425d4f7df559b4a0ba5bfb66e795854c7
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-21T10:50:25Z
summary:

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.

files:
A Misc/NEWS.d/next/Library/2026-08-21-13-30-00.gh-issue-156166.Xv8pQm.rst
M Lib/test/test_ssl.py
M Modules/_ssl/debughelpers.c

diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py
index 14e4620669491f..285bcac65ab57b 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -5598,6 +5598,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_exception(self):
         client_context, server_context, hostname = testing_context()
 
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 b2d552f97e5b0e..e8da76907971ed 100644
--- a/Modules/_ssl/debughelpers.c
+++ b/Modules/_ssl/debughelpers.c
@@ -102,20 +102,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;
 }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.