Skip to content

Commit

Permalink
GH-97592: Fix crash in C remove_done_callback due to evil code (GH-97660
Browse files Browse the repository at this point in the history
)

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 <guido@python.org>
  • Loading branch information
miss-islington and gvanrossum authored Sep 30, 2022
1 parent a5c503f commit 54bbb5e
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 2 deletions.
15 changes: 15 additions & 0 deletions Lib/test/test_asyncio/test_futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -827,6 +827,21 @@ def __eq__(self, other):

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Avoid a crash in the C version of :meth:`asyncio.Future.remove_done_callback` when an evil argument is passed.
9 changes: 7 additions & 2 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down

0 comments on commit 54bbb5e

Please sign in to comment.