https://github.com/python/cpython/commit/3d937eb1f9ce0693624b6d06fee8597ed7622b8d
commit: 3d937eb1f9ce0693624b6d06fee8597ed7622b8d
branch: 3.13
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-21T16:48:12+03:00
summary:

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

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 aacc912b684e5c..1cca904e647e95 100644
--- a/Lib/test/test_ssl.py
+++ b/Lib/test/test_ssl.py
@@ -5228,6 +5228,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 9861f3b46ac005..40f653f4746d3d 100644
--- a/Modules/_ssl/debughelpers.c
+++ b/Modules/_ssl/debughelpers.c
@@ -95,20 +95,28 @@ _PySSLContext_get_msg_callback(PySSLContext *self, void *c) 
{
 
 static int
 _PySSLContext_set_msg_callback(PySSLContext *self, PyObject *arg, void *c) {
-    Py_CLEAR(self->msg_cb);
+    if (arg == NULL) {
+        PyErr_Format(PyExc_AttributeError,
+                     "attribute '_msg_callback' of '%.100s' objects "
+                     "cannot be deleted", Py_TYPE(self)->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]

Reply via email to