Skip to content

Deadlock when element __repr__ mutates the list via list __repr__ #6591

@jackfromeast

Description

@jackfromeast

What happened?

list.__repr__ holds a read-lock on the backing vector while rendering elements. If any element's __repr__ mutates the same list (e.g., append), the re-entrant call blocks on the write-lock while __repr__ waits for the element to finish, producing a hard deadlock instead of a Python-level exception.

Proof of Concept:

class Evil:
    def __repr__(self):
        lst.append(1)
        return "evil"

lst = [Evil()]
print(lst)
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 repr_str(zelf: &Py<Self>, vm: &VirtualMachine) -> PyResult<String> {
    if zelf.__len__() == 0 {
        return Ok("[]".to_owned());
    }
    if let Some(_guard) = ReprGuard::enter(vm, zelf.as_object()) {
        let iter = zelf.borrow_vec().iter(); // holds a read lock on elements for the whole render
        return collection_repr(None, "[", "]", iter, vm); // re-enters element __repr__ while lock is held
    }
    Ok("[...]".to_owned())
}

pub(crate) fn collection_repr<'a, I>(
    class_name: Option<&str>,
    prefix: &str,
    suffix: &str,
    iter: I,
    vm: &VirtualMachine,
) -> PyResult<String>
where
    I: Iterator<Item = &'a PyObjectRef>,
{
    let mut repr = String::new();
    let mut parts_iter = iter.map(|o| o.repr(vm)); // re-enters Python-level __repr__ for each element
    repr.push_str(
        parts_iter
            .next()
            .transpose()?
            .expect("this is not called for empty collection")
            .as_str(),
    );
    // ...
}

#[pymethod]
pub(crate) fn append(&self, x: PyObjectRef) {
    self.borrow_vec_mut().push(x); // attempts write lock while read lock from repr_str is held -> deadlock
}
Rust Output
Program hangs forever
CPython Output
[evil, 1]

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions