From 850d685e2d5a20abbd932aa972f105332d9fb655 Mon Sep 17 00:00:00 2001 From: Andrew Walbran Date: Tue, 4 Aug 2026 13:48:45 +0100 Subject: [PATCH 1/2] Fix undefined behaviour in LinkedPerCore::get. --- CHANGELOG.md | 1 + src/derive.rs | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41d0861..5dc1bb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ ### Bugfixes - Don't allow unsafe code in initialisation expression of `#[percore]` variables. +- Fixed undefined behaviour in `LinkedPerCore::get` due to pointer provenance. ## 0.2.5 diff --git a/src/derive.rs b/src/derive.rs index 5b1a813..fb84dad 100644 --- a/src/derive.rs +++ b/src/derive.rs @@ -77,7 +77,7 @@ pub mod aarch64; use crate::lock::ExceptionLock; -use core::ptr::NonNull; +use core::{num::NonZero, ptr::NonNull}; pub use percore_derive::percore; #[allow(improper_ctypes)] @@ -181,8 +181,20 @@ impl LinkedPerCore { /// Returns a shared reference to the value for the current CPU core. #[inline(always)] pub fn get(&self) -> &T { - // SAFETY: PercoreLocalOffset guarantees a valid offset. - let percore_ptr = unsafe { NonNull::from_ref(&self.0).byte_offset(percore_local_offset()) }; + // We need to construct a new pointer with exposed provenance rather than just using + // `byte_offset` on the pointer to `self.0` because the per-core copy is not part of the + // same allocation. + let percore_ptr = NonNull::with_exposed_provenance( + NonZero::new( + (&raw const self.0) + .expose_provenance() + .cast_signed() + .checked_add(percore_local_offset()) + .unwrap() + .cast_unsigned(), + ) + .unwrap(), + ); debug_assert!(percore_ptr.is_aligned()); @@ -240,7 +252,7 @@ mod tests { use super::*; use crate as percore; use crate::ExceptionFree; - use core::{cell::RefCell, num::NonZero}; + use core::cell::RefCell; #[percore] static VALUE: ExceptionLock> = From 38f0afa29bf0b08339e432269d074065b266cc35 Mon Sep 17 00:00:00 2001 From: Andrew Walbran Date: Tue, 4 Aug 2026 17:43:05 +0100 Subject: [PATCH 2/2] Avoid overflow and null checks. The safety requirements of the PercoreLocalOffset trait should make these unneccessary. --- src/derive.rs | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/src/derive.rs b/src/derive.rs index fb84dad..09834a0 100644 --- a/src/derive.rs +++ b/src/derive.rs @@ -77,7 +77,7 @@ pub mod aarch64; use crate::lock::ExceptionLock; -use core::{num::NonZero, ptr::NonNull}; +use core::ptr::with_exposed_provenance; pub use percore_derive::percore; #[allow(improper_ctypes)] @@ -184,18 +184,12 @@ impl LinkedPerCore { // We need to construct a new pointer with exposed provenance rather than just using // `byte_offset` on the pointer to `self.0` because the per-core copy is not part of the // same allocation. - let percore_ptr = NonNull::with_exposed_provenance( - NonZero::new( - (&raw const self.0) - .expose_provenance() - .cast_signed() - .checked_add(percore_local_offset()) - .unwrap() - .cast_unsigned(), - ) - .unwrap(), + let percore_ptr = with_exposed_provenance::( + ((&raw const self.0).expose_provenance().cast_signed() + percore_local_offset()) + .cast_unsigned(), ); + debug_assert!(!percore_ptr.is_null()); debug_assert!(percore_ptr.is_aligned()); // SAFETY: @@ -203,13 +197,12 @@ impl LinkedPerCore { // and `&self.0` must be aligned as it comes from a reference, so adding the offset to it // must still be properly aligned. (In debug builds we also double-check with the // debug_assert above.) - // * The pointer is non-null because it is constructed from NonNull and the offset produces - // a valid address. + // * The pointer is non-null because the offset is guaranteed to produce a valid address. // * The PercoreLocalOffset implementation promises that the calculated pointer points into // * the percore memory area which is initialized and it is dereferenceable for the T type. // * Aliasing is prevented by each core having its own instance of the variable and by // requiring `ExceptionLock` for `Sync` implementation. - unsafe { percore_ptr.as_ref() } + unsafe { percore_ptr.as_ref_unchecked() } } } @@ -252,7 +245,7 @@ mod tests { use super::*; use crate as percore; use crate::ExceptionFree; - use core::cell::RefCell; + use core::{cell::RefCell, num::NonZero}; #[percore] static VALUE: ExceptionLock> =