Skip to content

PyRefMut::as_super and PyClassGuardMut::as_super enable type confusion #6083

Description

@davidhewitt

Both PyRefMut::as_super and PyClassGuardMut::as_super return &mut references to the base-type guard.

e.g. PyRefMut<T>::as_super() -> &mut PyRefMut<T::BaseType>.

Unfortunately because of the &mut reference, they can be swapped with other guards to T::BaseType which are from base type objects, i.e. not the same subclass. After such a swap and the .as_super() borrow ends, the original PyRefMut<T> is not sound because it allows access to fields of T on a value which isn't actually T, i.e. creates an out of bounds read.

Here's a quick repro which shows an out of bounds read:

#[crate::pyclass]
#[pyo3(crate = "crate", subclass)]
struct BaseClass {
    val1: usize,
}

#[crate::pyclass]
#[pyo3(crate = "crate", extends=BaseClass, subclass)]
struct SubClass {
    val2: usize,
}

#[test]
fn test_pyrefmut_as_super() {
    Python::attach(|py| {
        let obj = Bound::new(py, PyClassInitializer::from(BaseClass { val1: 10 }).add_subclass(SubClass { val2: 20 })).unwrap();

        let base_obj = BaseClass { val1: 10 }.into_pyobject(py).unwrap();
        let mut obj_guard_mut = obj.borrow_mut();

        // after the swap, `obj_guard_mut` actually points to an instance of the base type
        std::mem::swap(
            obj_guard_mut.as_super(),
            &mut base_obj.borrow_mut(),
        );

        // XXX: Read of uninitialized memory, this panic message is basically arbitrary
        panic!("{}", obj_guard_mut.val2);
    });
}

cc @Icxolu (not your API but you most recently worked on these types with me 😄)

I think the only reasonable thing we can do is replace these APIs with types such as PyRefMutSuper<'_, T::BaseType> and PyClassGuardMutSuper<'_, T::BaseType> which don't allow for this invalid swap.

I think for PyClassGuardMut it makes sense to just fix the API now, similar to #6073. For PyRefMut I am less sure, it will be a bigger change. One option is that we deprecate PyRefMut::as_super with a note why the API should be used with caution, and aim to land PyClassGuardMut as the more complete replacement for PyRefMut in 0.30 or 0.31 (as we'd like to do that anyway).

(Credit to Codex security scanning for this discovery.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions