Skip to content

Commit ac31a26

Browse files
[3.13] gh-125966: fix use-after-free on fut->fut_callback0 due to an evil callback's __eq__ in asyncio (GH-125967) (#126047)
gh-125966: fix use-after-free on `fut->fut_callback0` due to an evil callback's `__eq__` in asyncio (GH-125967) (cherry picked from commit ed5059e) Co-authored-by: Bénédikt Tran <[email protected]>
1 parent 25421c7 commit ac31a26

File tree

3 files changed

+26
-1
lines changed

3 files changed

+26
-1
lines changed

Lib/test/test_asyncio/test_futures.py

+18
Original file line numberDiff line numberDiff line change
@@ -1007,6 +1007,24 @@ def evil_call_soon(*args, **kwargs):
10071007
# returns an empty list but the C implementation returns None.
10081008
self.assertIn(fut._callbacks, (None, []))
10091009

1010+
def test_use_after_free_on_fut_callback_0_with_evil__eq__(self):
1011+
# Special thanks to Nico-Posada for the original PoC.
1012+
# See https://github.com/python/cpython/issues/125966.
1013+
1014+
fut = self._new_future()
1015+
1016+
class cb_pad:
1017+
def __eq__(self, other):
1018+
return True
1019+
1020+
class evil(cb_pad):
1021+
def __eq__(self, other):
1022+
fut.remove_done_callback(None)
1023+
return NotImplemented
1024+
1025+
fut.add_done_callback(cb_pad())
1026+
fut.remove_done_callback(evil())
1027+
10101028
def test_use_after_free_on_fut_callback_0_with_evil__getattribute__(self):
10111029
# see: https://github.com/python/cpython/issues/125984
10121030

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix a use-after-free crash in :meth:`asyncio.Future.remove_done_callback`.
2+
Patch by Bénédikt Tran.

Modules/_asynciomodule.c

+6-1
Original file line numberDiff line numberDiff line change
@@ -965,7 +965,12 @@ _asyncio_Future_remove_done_callback_impl(FutureObj *self, PyTypeObject *cls,
965965
ENSURE_FUTURE_ALIVE(state, self)
966966

967967
if (self->fut_callback0 != NULL) {
968-
int cmp = PyObject_RichCompareBool(self->fut_callback0, fn, Py_EQ);
968+
// Beware: An evil PyObject_RichCompareBool could free fut_callback0
969+
// before a recursive call is made with that same arg. For details, see
970+
// https://github.com/python/cpython/pull/125967#discussion_r1816593340.
971+
PyObject *fut_callback0 = Py_NewRef(self->fut_callback0);
972+
int cmp = PyObject_RichCompareBool(fut_callback0, fn, Py_EQ);
973+
Py_DECREF(fut_callback0);
969974
if (cmp == -1) {
970975
return NULL;
971976
}

0 commit comments

Comments
 (0)