[3.14] gh-156166: Fix setting and deleting SSLContext._msg_callback (GH-156167) (GH-156174)

serhiy-storchaka <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/e810e1aeeed59674cc6c030842bebdd843b0d421
commit: e810e1aeeed59674cc6c030842bebdd843b0d421
branch: 3.14
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-21T16:47:54+03:00
summary:

[3.14] gh-156166: Fix setting and deleting SSLContext._msg_callback (GH-156167) (GH-156174)

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)

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 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;
 }
 

_______________________________________________
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.