diff --git a/Lib/test/test_asyncgen.py b/Lib/test/test_asyncgen.py index b5e0891feb794e..e05dcd6203bd3a 100644 --- a/Lib/test/test_asyncgen.py +++ b/Lib/test/test_asyncgen.py @@ -905,6 +905,47 @@ def test_aiter_callable_exhausted(self): with self.assertRaises(StopAsyncIteration): self.loop.run_until_complete(anext(it)) + def test_aiter_callable_reentrant_sentinel_compare(self): + stop = False + destroyed = [] + compared = [] + + async def produce(): + return Result() + + def fetch(): + nonlocal stop + if stop: + raise StopAsyncIteration + return produce() + + class Sentinel: + def __del__(self): + destroyed.append(True) + + def __eq__(self, other): + nonlocal stop + stop = True + aw = ait.__anext__() + try: + aw.__await__().send(None) + except StopAsyncIteration: + pass + return NotImplemented + + class Result: + def __eq__(self, other): + compared.append(isinstance(other, Sentinel) and not destroyed) + return NotImplemented + + ait = aiter(fetch, Sentinel()) + with self.assertRaises(StopIteration) as cm: + ait.__anext__().__await__().send(None) + self.assertIsInstance(cm.exception.value, Result) + self.assertEqual(compared, [True]) + with self.assertRaises(StopAsyncIteration): + ait.__anext__().__await__().send(None) + def test_aiter_callable_lazy(self): # The callable is only called when the awaitable is awaited calls = [] diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-09-23-21-41-01.gh-issue-158033.mK8rLt.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-23-21-41-01.gh-issue-158033.mK8rLt.rst new file mode 100644 index 00000000000000..3dd5aac5625d87 --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2026-09-23-21-41-01.gh-issue-158033.mK8rLt.rst @@ -0,0 +1,3 @@ +Fix a use-after-free in the two-argument form of :func:`aiter` when comparing +the sentinel. A reentrant equality comparison could exhaust the iterator and +free the sentinel while the comparison was still using it. diff --git a/Objects/iterobject.c b/Objects/iterobject.c index 2d5e3709a27dfb..a8278d81713146 100644 --- a/Objects/iterobject.c +++ b/Objects/iterobject.c @@ -641,7 +641,9 @@ acallawaitable_handle_error(acallawaitableobject *aw) } int ok = 0; if (it->it_sentinel != NULL) { - ok = PyObject_RichCompareBool(it->it_sentinel, value, Py_EQ); + PyObject *sentinel = Py_NewRef(it->it_sentinel); + ok = PyObject_RichCompareBool(sentinel, value, Py_EQ); + Py_DECREF(sentinel); } if (ok == 0) { (void)_PyGen_SetStopIterationValue(value);