GeezerVison is showing github.com — gh-158031, gh-158033: Fix UAF in iter/aiter sentinel comparison by iliasabk · Pull Request #158145 · python/cpython
Conversation
iliasabk
commented
Sep 25, 2026
•
edited by bedevere-app
Bot
Loading
Uh oh!
There was an error while loading. Please reload this page.
Issues
Fixes #158031, fixes #158033 — two same-class heap-use-after-free reports in the callable-with-sentinel iterators added by gh-98469.
Root cause
Both calliter_iternext() (sync iter(callable, sentinel)) and acallawaitable_handle_error() (async aiter(callable, sentinel)) compare it->it_sentinel against the produced value with PyObject_RichCompareBool() while holding only a borrowed reference. The comparison can run arbitrary Python code via a custom __eq__, and that code can release the sentinel mid-comparison:
- sync:
Sentinel.__eq__ calls it.__setstate__(((), StopIteration)), which does Py_XSETREF(it->it_sentinel, NULL); once the __eq__ frame is torn down the last reference is gone and do_richcompare() continues with a freed v (ASan: UAF read at iterobject.c:285).
- async:
Sentinel.__eq__ drives ait.__anext__().__await__().send(None), which reaches acallawaitable_start → callable raises StopAsyncIteration → acalliter_exhaust() → Py_CLEAR(it->it_sentinel); same freed-v continuation (ASan: UAF read at iterobject.c:644).
Fix
Hold a strong reference to the sentinel across the PyObject_RichCompareBool() call in both functions — the same pattern already used elsewhere for callbacks that can mutate the iterator (see the ordering note in calliter_reduce, gh-101765). No behavior change: the in-flight comparison still uses the sentinel that was current when the comparison started, and subsequent state (calliter_exhaust, _PyGen_SetStopIterationValue) reads the post-mutation fields as before.
Tests
test_calliter_sentinel_reentrant_setstate in test_iter.py — __eq__ re-entrantly calls __setstate__ during next().
test_aiter_callable_sentinel_reentrant_exhaustion in test_asyncgen.py — __eq__ re-entrantly exhausts the iterator via a nested __anext__() send.
Verification (local, ASan, --without-pymalloc, arm64 macOS)
- Both reporter PoCs:
heap-use-after-free inside do_richcompare before the fix; clean run and correct semantics after (sync: next() returns the result; async: StopIteration with the produced value).
- New regression tests pass;
test_iter.py and test_asyncgen.py suites unchanged otherwise.
This PR was prepared with AI-agent assistance; the changes are small, reviewed line-by-line, and covered by the added regression tests.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
The following commit authors need to sign the Contributor License Agreement:

|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
…arison
The sentinel comparison in calliter_iternext() and
acallawaitable_handle_error() ran arbitrary Python code (a custom
__eq__) while holding only a borrowed reference to it_sentinel.
A re-entrant __setstate__ or __anext__ could release the sentinel
mid-comparison, leaving the comparison machinery using freed memory.
Hold a strong reference to the sentinel across PyObject_RichCompareBool.
|
A note on scope, following picnixz's comment on #158033: since aiter is being reimplemented in pure Python, the async half may not matter for 3.16+. The two fixes in this PR are independent — I'm happy to split it so the iter() fix (#158031) lands on its own, and the aiter() half can then serve as the basis for the 3.13–3.15 backports once the approach there is decided.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
There are already other PRs. Do not open duplicates, especially not with LLMs. We will consider such behavior as spam and restrict you next time.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
|
Understood — apologies for the duplicate. I'll check for existing PRs before opening next time.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|