Skip to content

Commit 54bbb5e

Browse files
GH-97592: Fix crash in C remove_done_callback due to evil code (GH-97660)
Evil code could cause fut_callbacks to be cleared when PyObject_RichCompareBool is called. (cherry picked from commit 63780f4) Co-authored-by: Guido van Rossum <[email protected]>
1 parent a5c503f commit 54bbb5e

File tree

3 files changed

+23
-2
lines changed

3 files changed

+23
-2
lines changed

Lib/test/test_asyncio/test_futures.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -827,6 +827,21 @@ def __eq__(self, other):
827827

828828
fut.remove_done_callback(evil())
829829

830+
def test_remove_done_callbacks_list_clear(self):
831+
# see https://github.com/python/cpython/issues/97592 for details
832+
833+
fut = self._new_future()
834+
fut.add_done_callback(str)
835+
836+
for _ in range(63):
837+
fut.add_done_callback(id)
838+
839+
class evil:
840+
def __eq__(self, other):
841+
fut.remove_done_callback(other)
842+
843+
fut.remove_done_callback(evil())
844+
830845
def test_schedule_callbacks_list_mutation_1(self):
831846
# see http://bugs.python.org/issue28963 for details
832847

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Avoid a crash in the C version of :meth:`asyncio.Future.remove_done_callback` when an evil argument is passed.

Modules/_asynciomodule.c

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,7 +1042,11 @@ _asyncio_Future_remove_done_callback(FutureObj *self, PyObject *fn)
10421042
return NULL;
10431043
}
10441044

1045-
for (i = 0; i < PyList_GET_SIZE(self->fut_callbacks); i++) {
1045+
// Beware: PyObject_RichCompareBool below may change fut_callbacks.
1046+
// See GH-97592.
1047+
for (i = 0;
1048+
self->fut_callbacks != NULL && i < PyList_GET_SIZE(self->fut_callbacks);
1049+
i++) {
10461050
int ret;
10471051
PyObject *item = PyList_GET_ITEM(self->fut_callbacks, i);
10481052
Py_INCREF(item);
@@ -1061,7 +1065,8 @@ _asyncio_Future_remove_done_callback(FutureObj *self, PyObject *fn)
10611065
}
10621066
}
10631067

1064-
if (j == 0) {
1068+
// Note: fut_callbacks may have been cleared.
1069+
if (j == 0 || self->fut_callbacks == NULL) {
10651070
Py_CLEAR(self->fut_callbacks);
10661071
Py_DECREF(newlist);
10671072
return PyLong_FromSsize_t(len + cleared_callback0);

0 commit comments

Comments
 (0)