You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
itertools.count use-after-free via re-entrant step.__radd__ #154670
itertools.count has a use-after-free when the step object's __radd__ re-enters the iterator.
count_nextlong() borrows lz->long_cnt (the running total) without Py_INCREF, then calls PyNumber_Add(result, step). If step.__radd__ calls next() on the same count, the inner call moves lz->long_cnt to the new value and hands the old total's only reference back, which is then dropped and freed. The outer call still returns that freed total as a dangling pointer.
The step needs __index__ so count() accepts it as a number, and the returned value has to be used to hit the freed memory:
fromitertoolsimportcountclassStep:
armed=Truedef__index__(self): # so count() accepts it as a numberreturn1def__radd__(self, other):
ifStep.armed:
Step.armed=Falseinner=next(c) # re-enter; steals the running total's reff"{inner!r}"# churn the heap so the freed slot is reusedreturnother+1c=count(1<<100, Step())
val=next(c)
val+1# use the returned (dangling) total
Run with PYTHONMALLOC=debug python repro.py -> SIGSEGV. With the fix it prints the correct value.
You're right, sorry -- the step has to pass the number check, so a bare class fails. It needs __index__. I have updated the issue with a working reproducer.
The short version: the step's __radd__ calls next() on the same count. That inner call frees the running total, and the outer call still returns it as a dangling pointer. With PYTHONMALLOC=debug it segfaults on unfixed main; with the fix it prints the right value.
@rhettinger I think we should be careful here for existing code that does not crash or segfaults. It could be a breaking change so I believe we can forbid re-entrancy as long as there is no crash.
I however do not know of any legitimate code that would need such reentrancy (like advancing something twice).
Maybe a DPO thread or a wider discussion for that (at least an issue) would be great.
Sorry for the typo. Yes I meant that, i.e.: if legitimate code works currently, making reentrancy forbidden will break it and this is usually something we rather want to avoid.
I think the decision of blocking reentrancy needs to be more motivated and may even need a PEP.
Activity
terryjreedy commentedon Jul 25, 2026
On Windows main and older and macOS 10.5 13.5,
the step argument has to be a number, and likely an int, not a Step(). How did that run?
tonghuaroot commentedon Jul 26, 2026
You're right, sorry -- the step has to pass the number check, so a bare class fails. It needs
__index__. I have updated the issue with a working reproducer.The short version: the step's
__radd__callsnext()on the same count. That inner call frees the running total, and the outer call still returns it as a dangling pointer. WithPYTHONMALLOC=debugit segfaults on unfixed main; with the fix it prints the right value.rhettinger commentedon Jul 27, 2026
My plan for itertools is to block reentrancy entirely, just like we do for generators.
If people want concurrent access to itertools (or generators), they can wrap use
threading.serialize_iteratorto enforce reliable, sequential access.picnixz commentedon Jul 28, 2026
@rhettinger I think we should be careful here for existing code that does not crash or segfaults. It could be a breaking change so I believe we can forbid re-entrancy as long as there is no crash.
I however do not know of any legitimate code that would need such reentrancy (like advancing something twice).
Maybe a DPO thread or a wider discussion for that (at least an issue) would be great.
pochmann commentedon Jul 28, 2026
@picnixz Do you mean "can't forbid"?
Concurrent access isn't the only possible cause for reentry, so I also think a discussion would be good before blocking that.
picnixz commentedon Jul 28, 2026
Sorry for the typo. Yes I meant that, i.e.: if legitimate code works currently, making reentrancy forbidden will break it and this is usually something we rather want to avoid.
I think the decision of blocking reentrancy needs to be more motivated and may even need a PEP.