Bug Description
PyClassInitializer can be given a (chain of) struct to initialize the class, or a Py<Struct>. But in the second case, it propagates the instance down the inheritance line, when subclasses gets a pointer to it and treat it like it were of their type - while it was originally an other type, with less bytes! As such, a user can exploit this to cause a OOB read/write.
Steps to Reproduce
The following code (you may need to increase the bytes count):
use pyo3::prelude::*;
#[pyclass(subclass)]
pub struct BaseClass {}
#[pymethods]
impl BaseClass {
#[new]
pub fn new() -> Self {
Self {}
}
}
#[pyclass(extends=BaseClass)]
pub struct SubClass {
data: [u8; 100_000],
}
#[pymethods]
impl SubClass {
#[new]
pub fn new(py: Python<'_>) -> PyResult<PyClassInitializer<Self>> {
Ok(
PyClassInitializer::from(Py::new(py, BaseClass::new())?).add_subclass(SubClass {
data: [1; 100_000],
}),
)
}
}
/// A Python module implemented in Rust.
#[pymodule]
fn oob(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<BaseClass>()?;
m.add_class::<SubClass>()?;
Ok(())
}
Crashes the Python interpreter on my system when SubClass is instantiated, probably due to a page fault. Really, anything can happen here.
Backtrace
No response
Your operating system and version
Windows 11
Your Python version (python --version)
Python 3.12.0
Your Rust version (rustc --version)
rustc 1.80.1 (3f5fd8dd4 2024-08-06)
Your PyO3 version
0.22.2
How did you install python? Did you use a virtualenv?
The official website.
Additional Info
Unfortunately, I don't believe this bug can be fixed, since I don't think there exists a Python API that allows us to construct a new object, with a custom target class, that is a copy of an existing (base) class.
A partial fix may be to check we are using the same class (this can be enforced at compile time even). However, I believe it's unlikely that users that provide a value to PyClassInitializer actually want to overwrite it, rather they probably want to duplicate it (which is impossible). Furthermore, this is still unsound with frozen classes (we can forbid them too, though).
I think this safest way will be for this option to be removed.
Discovered while working on #4443.
Bug Description
PyClassInitializercan be given a (chain of) struct to initialize the class, or aPy<Struct>. But in the second case, it propagates the instance down the inheritance line, when subclasses gets a pointer to it and treat it like it were of their type - while it was originally an other type, with less bytes! As such, a user can exploit this to cause a OOB read/write.Steps to Reproduce
The following code (you may need to increase the bytes count):
Crashes the Python interpreter on my system when
SubClassis instantiated, probably due to a page fault. Really, anything can happen here.Backtrace
No response
Your operating system and version
Windows 11
Your Python version (
python --version)Python 3.12.0
Your Rust version (
rustc --version)rustc 1.80.1 (3f5fd8dd4 2024-08-06)
Your PyO3 version
0.22.2
How did you install python? Did you use a virtualenv?
The official website.
Additional Info
Unfortunately, I don't believe this bug can be fixed, since I don't think there exists a Python API that allows us to construct a new object, with a custom target class, that is a copy of an existing (base) class.
A partial fix may be to check we are using the same class (this can be enforced at compile time even). However, I believe it's unlikely that users that provide a value to
PyClassInitializeractually want to overwrite it, rather they probably want to duplicate it (which is impossible). Furthermore, this is still unsound with frozen classes (we can forbid them too, though).I think this safest way will be for this option to be removed.
Discovered while working on #4443.