Skip to content

Deadlock when writer mutates the same array via array.tofile #6551

@jackfromeast

Description

@jackfromeast

What happened?

array.tofile holds a read lock on the array buffer while calling the user-provided file object's write method. If that write re-enters and mutates the array (e.g., append), it tries to take a write lock that is blocked by the outstanding read lock, causing the interpreter to hang.

Proof of Concept:

import array

class Writer:
    def __init__(self, arr):
        self.arr = arr
        self.reentered = False

    def write(self, chunk):
        if not self.reentered:
            self.reentered = True
            self.arr.append(0)
        return len(chunk)

arr = array.array('b', range(128))
arr.tofile(Writer(arr))
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 tofile(&self, f: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
    const BLOCKSIZE: usize = 64 * 1024;

    let bytes = self.read();                 // Holds the array read lock for the whole write
    let bytes = bytes.get_bytes();

    for b in bytes.chunks(BLOCKSIZE) {
        let b = PyBytes::from(b.to_vec()).into_ref(&vm.ctx);
        vm.call_method(&f, "write", (b,))?;  // Re-enters Python; user `write` may mutate the same array
    }
    Ok(())
}

#[pymethod]
fn append(zelf: &Py<Self>, x: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
    zelf.try_resizable(vm)?.push(x, vm)       // Requires a write lock while `tofile` still holds the read lock
}

fn try_resizable_opt(&self) -> Option<PyRwLockWriteGuard<'_, ArrayContentType>> {
    let w = self.write();                     // Blocks forever when `append` runs during `tofile`'s read lock
    (self.exports.load(atomic::Ordering::SeqCst) == 0).then_some(w)
}
Rust Output
Program hangs forever
CPython Output
(No output)

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