]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
GH-97592: Fix crash in C remove_done_callback due to evil code (GH-97660)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Fri, 30 Sep 2022 20:28:27 +0000 (13:28 -0700)
committerGitHub <noreply@github.com>
Fri, 30 Sep 2022 20:28:27 +0000 (13:28 -0700)
Evil code could cause fut_callbacks to be cleared when PyObject_RichCompareBool is called.
(cherry picked from commit 63780f4599acc2c5ee8af5f37ab76c162ad21065)

Co-authored-by: Guido van Rossum <guido@python.org>
Lib/test/test_asyncio/test_futures.py
Misc/NEWS.d/next/Library/2022-09-29-23-22-24.gh-issue-97592.tpJg_J.rst [new file with mode: 0644]
Modules/_asynciomodule.c

index 838147b1e65f4eda87a056d2c0f618d601b0e3a6..0126742603702cf5316d5cc2446f9d1e38e6f9d2 100644 (file)
@@ -827,6 +827,21 @@ class BaseFutureDoneCallbackTests():
 
         fut.remove_done_callback(evil())
 
+    def test_remove_done_callbacks_list_clear(self):
+        # see https://github.com/python/cpython/issues/97592 for details
+
+        fut = self._new_future()
+        fut.add_done_callback(str)
+
+        for _ in range(63):
+            fut.add_done_callback(id)
+
+        class evil:
+            def __eq__(self, other):
+                fut.remove_done_callback(other)
+
+        fut.remove_done_callback(evil())
+
     def test_schedule_callbacks_list_mutation_1(self):
         # see http://bugs.python.org/issue28963 for details
 
diff --git a/Misc/NEWS.d/next/Library/2022-09-29-23-22-24.gh-issue-97592.tpJg_J.rst b/Misc/NEWS.d/next/Library/2022-09-29-23-22-24.gh-issue-97592.tpJg_J.rst
new file mode 100644 (file)
index 0000000..aa245cf
--- /dev/null
@@ -0,0 +1 @@
+Avoid a crash in the C version of :meth:`asyncio.Future.remove_done_callback` when an evil argument is passed.
index 1f6de2177a37afeb8da86fd97b87e993b399304c..c627382a53f054c7ab0273f86bb40faaa2832b1b 100644 (file)
@@ -1042,7 +1042,11 @@ _asyncio_Future_remove_done_callback(FutureObj *self, PyObject *fn)
         return NULL;
     }
 
-    for (i = 0; i < PyList_GET_SIZE(self->fut_callbacks); i++) {
+    // Beware: PyObject_RichCompareBool below may change fut_callbacks.
+    // See GH-97592.
+    for (i = 0;
+         self->fut_callbacks != NULL && i < PyList_GET_SIZE(self->fut_callbacks);
+         i++) {
         int ret;
         PyObject *item = PyList_GET_ITEM(self->fut_callbacks, i);
         Py_INCREF(item);
@@ -1061,7 +1065,8 @@ _asyncio_Future_remove_done_callback(FutureObj *self, PyObject *fn)
         }
     }
 
-    if (j == 0) {
+    // Note: fut_callbacks may have been cleared.
+    if (j == 0 || self->fut_callbacks == NULL) {
         Py_CLEAR(self->fut_callbacks);
         Py_DECREF(newlist);
         return PyLong_FromSsize_t(len + cleared_callback0);