-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Open
Description
What happened?
PyCallableIterator::next keeps an upgradable read lock while comparing the callable result to the sentinel with vm.bool_eq. If the sentinel’s __eq__ calls next() on the same iterator (as in the PoC), the re-entrant next blocks on the same lock, hanging the interpreter instead of finishing iteration.
Proof of Concept:
class Evil:
def __init__(self):
self.it = None
def __eq__(self, other):
next(self.it)
return True
evil = Evil()
itr = iter(lambda: evil, evil)
evil.it = itr
next(itr)Affected Versions
| RustPython Version | Status | Exit Code |
|---|---|---|
Python 3.13.0alpha (heads/main-dirty:21300f689, Dec 13 2025, 22:16:49) [RustPython 0.4.0 with rustc 1.90.0-nightly (11ad40bb8 2025-06-28)] |
Deadlock | 124 |
Vulnerable Code
fn next(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<PyIterReturn> {
let status = zelf.status.upgradable_read(); // holds the lock across user comparisons
let next = if let IterStatus::Active(callable) = &*status {
let ret = callable.invoke((), vm)?; // user callable returns a value that may be sentinel
if vm.bool_eq(&ret, &zelf.sentinel)? { // runs sentinel.__eq__, which can call next() again
*PyRwLockUpgradableReadGuard::upgrade(status) = IterStatus::Exhausted;
PyIterReturn::StopIteration(None)
} else {
PyIterReturn::Return(ret)
}
} else {
PyIterReturn::StopIteration(None)
};
Ok(next) // re-entrant next() blocks on the upgradable_read above, causing deadlock
}Rust Output
Error: Execution timed out
CPython Output
Traceback (most recent call last):
File "<string>", line 11, in <module>
StopIteration
Metadata
Metadata
Assignees
Labels
No labels