`PyRefMut::as_super` and `PyClassGuardMut::as_super` enable type confusion
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 16.2k
- Forks
- 1k
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 66
Description
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.)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the supplied reproduction and inspect the PyRefMut::as_super and PyClassGuardMut::as_super entry points. Confirm that swapping the returned mutable references permits access to the subclass field through the wrong object. Done means preventing that invalid swap while resolving the separate PyRefMut API compatibility decision described in the issue.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, rust
- Domain
- api, security
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100