From c93b3036c008fa0173827e64b49b4e31bef0c8e7 Mon Sep 17 00:00:00 2001 From: person93 Date: Fri, 22 May 2026 15:25:01 -0400 Subject: [PATCH 1/5] add method `PyCapsule::import_raw` --- src/types/capsule.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/types/capsule.rs b/src/types/capsule.rs index 12d2232c56c..0e99655770d 100644 --- a/src/types/capsule.rs +++ b/src/types/capsule.rs @@ -387,14 +387,25 @@ impl PyCapsule { /// /// It must be known that the capsule imported by `name` contains an item of type `T`. pub unsafe fn import<'py, T>(py: Python<'py>, name: &CStr) -> PyResult<&'py T> { + Self::import_raw(py, name).map(|ptr| { + // SAFETY: caller has upheld the safety contract + unsafe { ptr.cast().as_ref() } + }) + } + + /// Imports an existing capsule as a pointer. + /// + /// The `name` should match the path to the module attribute exactly in the form + /// of `"module.attribute"`, which should be the same as the name within the capsule. + /// + /// # Safety + /// + /// This function is safe to call, but the pointer it returns is not safe to use. + /// The python interpreter does _NOT_ provide any synchronization guarantees for capsules. + pub fn import_raw(py: Python<'_>, name: &CStr) -> PyResult> { // SAFETY: `name` is a valid C string, thread is attached to the Python interpreter let ptr = unsafe { ffi::PyCapsule_Import(name.as_ptr(), false as c_int) }; - if ptr.is_null() { - Err(PyErr::fetch(py)) - } else { - // SAFETY: caller has upheld the safety contract - Ok(unsafe { &*ptr.cast::() }) - } + NonNull::new(ptr).ok_or_else(|| PyErr::fetch(py)) } } From cff77f4b57a19b73d3919319c869f123dbb6b821 Mon Sep 17 00:00:00 2001 From: person93 Date: Fri, 22 May 2026 15:27:19 -0400 Subject: [PATCH 2/5] add newsfragment --- newsfragments/6066.added.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 newsfragments/6066.added.md diff --git a/newsfragments/6066.added.md b/newsfragments/6066.added.md new file mode 100644 index 00000000000..78696f22d99 --- /dev/null +++ b/newsfragments/6066.added.md @@ -0,0 +1 @@ +Add method `PyCapsule::import_raw` From 52cb0e25bd87d9b152a12b0dcd684969a433e3bc Mon Sep 17 00:00:00 2001 From: person93 Date: Sun, 24 May 2026 07:25:30 -0400 Subject: [PATCH 3/5] rename `PyCapsule::import_raw` to `import_pointer` --- newsfragments/6066.added.md | 2 +- src/types/capsule.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/newsfragments/6066.added.md b/newsfragments/6066.added.md index 78696f22d99..22328824248 100644 --- a/newsfragments/6066.added.md +++ b/newsfragments/6066.added.md @@ -1 +1 @@ -Add method `PyCapsule::import_raw` +Add method `PyCapsule::import_pointer` diff --git a/src/types/capsule.rs b/src/types/capsule.rs index 0e99655770d..d84b33b6a03 100644 --- a/src/types/capsule.rs +++ b/src/types/capsule.rs @@ -387,7 +387,7 @@ impl PyCapsule { /// /// It must be known that the capsule imported by `name` contains an item of type `T`. pub unsafe fn import<'py, T>(py: Python<'py>, name: &CStr) -> PyResult<&'py T> { - Self::import_raw(py, name).map(|ptr| { + Self::import_pointer(py, name).map(|ptr| { // SAFETY: caller has upheld the safety contract unsafe { ptr.cast().as_ref() } }) @@ -402,7 +402,7 @@ impl PyCapsule { /// /// This function is safe to call, but the pointer it returns is not safe to use. /// The python interpreter does _NOT_ provide any synchronization guarantees for capsules. - pub fn import_raw(py: Python<'_>, name: &CStr) -> PyResult> { + pub fn import_pointer(py: Python<'_>, name: &CStr) -> PyResult> { // SAFETY: `name` is a valid C string, thread is attached to the Python interpreter let ptr = unsafe { ffi::PyCapsule_Import(name.as_ptr(), false as c_int) }; NonNull::new(ptr).ok_or_else(|| PyErr::fetch(py)) From c7c07094b510e558362038167bb1e508db03972f Mon Sep 17 00:00:00 2001 From: person93 Date: Sun, 24 May 2026 07:42:39 -0400 Subject: [PATCH 4/5] check pointer alignment in `PyCapsule::import` --- newsfragments/6066.changed.md | 1 + src/types/capsule.rs | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 newsfragments/6066.changed.md diff --git a/newsfragments/6066.changed.md b/newsfragments/6066.changed.md new file mode 100644 index 00000000000..746c2f89573 --- /dev/null +++ b/newsfragments/6066.changed.md @@ -0,0 +1 @@ +`PyCapsule::import` returns an error if the pointer is not properly aligned. diff --git a/src/types/capsule.rs b/src/types/capsule.rs index d84b33b6a03..887dfe9243e 100644 --- a/src/types/capsule.rs +++ b/src/types/capsule.rs @@ -1,5 +1,6 @@ #![deny(clippy::undocumented_unsafe_blocks)] +use crate::exceptions::PySystemError; use crate::ffi_ptr_ext::FfiPtrExt; use crate::internal_tricks::box_into_non_null; use crate::py_result_ext::PyResultExt; @@ -387,10 +388,18 @@ impl PyCapsule { /// /// It must be known that the capsule imported by `name` contains an item of type `T`. pub unsafe fn import<'py, T>(py: Python<'py>, name: &CStr) -> PyResult<&'py T> { - Self::import_pointer(py, name).map(|ptr| { - // SAFETY: caller has upheld the safety contract - unsafe { ptr.cast().as_ref() } - }) + let ptr = Self::import_pointer(py, name)?.cast(); + + if !ptr.is_aligned() { + return Err(PySystemError::new_err(format!( + "The pointer from the `{}` capsule is not aligned to rust type {}", + name.to_string_lossy(), + core::any::type_name::() + ))); + } + + // SAFETY: caller has upheld the safety contract and we just checked pointer alignment + Ok(unsafe { ptr.as_ref() }) } /// Imports an existing capsule as a pointer. From ac6a4bc25e2f6da60140d25633a1fa0b67d78ec7 Mon Sep 17 00:00:00 2001 From: person93 Date: Sun, 24 May 2026 08:14:16 -0400 Subject: [PATCH 5/5] add test case for importing misaligned capsule --- src/types/capsule.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/types/capsule.rs b/src/types/capsule.rs index 887dfe9243e..e9d25968913 100644 --- a/src/types/capsule.rs +++ b/src/types/capsule.rs @@ -863,6 +863,28 @@ mod tests { }) } + #[test] + fn test_misaligned_import() { + #[repr(align(128))] + struct Align128 { + _n: usize, + } + + Python::attach(|py| { + let ptr = NonNull::new(129 as *mut c_void).unwrap(); + let name = c"builtins.capsule"; + + // SAFETY: the pointer will never be dereferenced + let capsule = unsafe { PyCapsule::new_with_pointer(py, ptr, name) }.unwrap(); + + let module = PyModule::import(py, "builtins").unwrap(); + module.add("capsule", capsule).unwrap(); + + // SAFETY: this should return an error so no reference will be created + assert!(unsafe { PyCapsule::import::(py, name) }.is_err()); + }); + } + #[test] fn test_vec_storage() { let cap: Py = Python::attach(|py| {