From nobody Thu Apr 9 12:08:11 2026 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id F3B473A7F4E; Mon, 9 Mar 2026 16:35:18 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773074119; cv=none; b=U68F0AYo9uUuL9rW6YUsCz7rrzJbFB4eAog9SgBdjpJyhTCw4GrXpRrh5H8o/GtlKzYSO/AFQ4rfqTA6KlsRbGSi1Z2FHqyiJ6NbJxYmQ9YFO6hqsDEk7sbQEWpqLNK/Zel98yg97nJIjxhto/2T1Gpf2EZYUULC7PgRRB8ofo0= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1773074119; c=relaxed/simple; bh=M2wvf00S2/NepjcwOTg0nKGdQToaxfiaijo9qaj8rYw=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=FzEsL3V3r3CqASfk+MlBWDKhkCzcRuiMY7S9rsBqzYD/TTGsB57TA5Of5ghSfuoX5LjPrukjNr7168YsJVbqSBhXJIsoYzfW5HBSG9Q7j1imrMf8Cs+tFDm8vxxo9wDTDfRAvHKSv09EVzlHSBaBLtHhSyCZp0A6qSvOoR5S5Kg= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=mDwXvrAR; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="mDwXvrAR" Received: by smtp.kernel.org (Postfix) with ESMTPS id A38D9C2BCB2; Mon, 9 Mar 2026 16:35:18 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1773074118; bh=M2wvf00S2/NepjcwOTg0nKGdQToaxfiaijo9qaj8rYw=; h=From:Date:Subject:References:In-Reply-To:To:Cc:Reply-To:From; b=mDwXvrARmG0Rt9IVtcr+6GfxEHOP+AG2Dcvw1DlyWu+9Nf90YTVknJtFd7YJVY4gQ HM7NyomUO5SdjBno//mRbEXLqXwgUYB5I3SIHW3CQp0kcXYG828Qwm34fkZnUvkzfL 4XjS9j0Jc36Ykuo5j3X7wE9nTQMkifqQmXGdLuczsgaM1ga/ghfY4C2XYGrLJ+ESs3 M1KVNKsgUZEcTcWkTQkvjHEEwgluUhL9lpvEEXE3x+2OytCLGXXU+1enYfguwS2R2j QJebrYRANlMVxHb+ZAwhspAtlJT3PR/s4ix8To4fQIW5dN5EK2Ull8vnugvwnneKg2 3/qaXz2Q/tqMA== Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8FFE7F4180E; Mon, 9 Mar 2026 16:35:18 +0000 (UTC) From: Tim Kovalenko via B4 Relay Date: Mon, 09 Mar 2026 12:34:19 -0400 Subject: [PATCH v4 2/4] rust: ptr: add projection infrastructure Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260309-drm-rust-next-v4-2-4ef485b19a4c@proton.me> References: <20260309-drm-rust-next-v4-0-4ef485b19a4c@proton.me> In-Reply-To: <20260309-drm-rust-next-v4-0-4ef485b19a4c@proton.me> To: Alexandre Courbot , Danilo Krummrich , Alice Ryhl , David Airlie , Simona Vetter , Miguel Ojeda , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Trevor Gross , Boqun Feng , Nathan Chancellor , Nicolas Schier , Abdiel Janulgue , Daniel Almeida , Robin Murphy , Boqun Feng Cc: nouveau@lists.freedesktop.org, dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-kbuild@vger.kernel.org, driver-core@lists.linux.dev X-Mailer: b4 0.14.2 X-Developer-Signature: v=1; a=ed25519-sha256; t=1773074117; l=15953; i=tim.kovalenko@proton.me; s=20260212; h=from:subject:message-id; bh=miLP0yl3FjGCpj96QFx68fa6u7hqlgNIww43hMgjNq8=; b=eA0m7RkR/mSGXK0WMRmnkmdjAaQ+cSBVISnJrKWvXCW5yra/kqFDo2rbTPpWQrbv/rw/l7HK0 3DN0iiv4XALCtKc8uHMdcxNRIIWlTKx758rQtfIlPT7rczFVgSv3NWM X-Developer-Key: i=tim.kovalenko@proton.me; a=ed25519; pk=/+OiulEpgeZifgP4mDE4e5YlV6nMeY+frze/lY/xiHI= X-Endpoint-Received: by B4 Relay for tim.kovalenko@proton.me/20260212 with auth_id=635 X-Original-From: Tim Kovalenko Reply-To: tim.kovalenko@proton.me From: Gary Guo Add a generic infrastructure for performing field and index projections on raw pointers. This will form the basis of performing I/O projections. Pointers manipulations are intentionally using the safe wrapping variants instead of the unsafe variants, as the latter requires pointers to be inside an allocation which is not necessarily true for I/O pointers. This projection macro protects against rogue `Deref` implementation, which can causes the projected pointer to be outside the bounds of starting pointer. This is extremely unlikely and Rust has a lint to catch this, but is unsoundness regardless. The protection works by inducing type inference ambiguity when `Deref` is implemented. This projection macro also stops projecting into unaligned fields (i.e. fields of `#[repr(packed)]` structs), as misaligned pointers require special handling. This is implemented by attempting to create reference to projected field inside a `if false` block. Despite being unreachable, Rust still checks that they're not unaligned fields. The projection macro supports both fallible and infallible index projections. These are described in detail inside the documentation. Signed-off-by: Gary Guo Reviewed-by: Benno Lossin Acked-by: Miguel Ojeda --- rust/kernel/lib.rs | 3 + rust/kernel/ptr.rs | 3 + rust/kernel/ptr/projection.rs | 294 ++++++++++++++++++++++++++++++++++++++= ++++ scripts/Makefile.build | 4 +- 4 files changed, 303 insertions(+), 1 deletion(-) diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs index 510cc7fe496113f85c34f420b1c4be95596297ad..d93292d47420f1f298a452ade5f= eefedce5ade86 100644 --- a/rust/kernel/lib.rs +++ b/rust/kernel/lib.rs @@ -38,6 +38,9 @@ #![feature(const_ptr_write)] #![feature(const_refs_to_cell)] // +// Stable since Rust 1.84.0. +#![feature(strict_provenance)] +// // Expected to become stable. #![feature(arbitrary_self_types)] // diff --git a/rust/kernel/ptr.rs b/rust/kernel/ptr.rs index cf980a103acf19ee3bd17bb1dfdbcadfe30467ae..930f523a4659b2cd5974e681ebf= 4c15c892a5e5a 100644 --- a/rust/kernel/ptr.rs +++ b/rust/kernel/ptr.rs @@ -2,6 +2,9 @@ =20 //! Types and functions to work with pointers and addresses. =20 +pub mod projection; +pub use crate::project_pointer as project; + use core::mem::{ align_of, size_of, // diff --git a/rust/kernel/ptr/projection.rs b/rust/kernel/ptr/projection.rs new file mode 100644 index 0000000000000000000000000000000000000000..7686d8502095108b90e863e90cb= c3a626c65031e --- /dev/null +++ b/rust/kernel/ptr/projection.rs @@ -0,0 +1,294 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! Infrastructure for handling projections. + +use core::{ + mem::MaybeUninit, + ops::Deref, // +}; + +use crate::prelude::*; + +/// Error raised when a projection is attempted on array or slices out of = bounds. +pub struct OutOfBound; + +impl From for Error { + #[inline(always)] + fn from(_: OutOfBound) -> Self { + ERANGE + } +} + +/// A helper trait to perform index projection. +/// +/// This is similar to `core::slice::SliceIndex`, but operate on raw point= ers safely and fallibly. +/// +/// # Safety +/// +/// The implementation of `index` and `get` (if `Some` is returned) must e= nsure that, if provided +/// input pointer `slice` and returned pointer `output`, then: +/// - `output` has the same provenance as `slice`; +/// - `output.byte_offset_from(slice)` is between 0 to +/// `KnownSize::size(slice) - KnownSize::size(output)`. +/// +/// This means that if the input pointer is valid, then pointer returned b= y `get` or `index` is +/// also valid. +#[diagnostic::on_unimplemented(message =3D "`{Self}` cannot be used to ind= ex `{T}`")] +#[doc(hidden)] +pub unsafe trait ProjectIndex: Sized { + type Output: ?Sized; + + /// Returns an index-projected pointer, if in bounds. + fn get(self, slice: *mut T) -> Option<*mut Self::Output>; + + /// Returns an index-projected pointer; fail the build if it cannot be= proved to be in bounds. + #[inline(always)] + fn index(self, slice: *mut T) -> *mut Self::Output { + Self::get(self, slice).unwrap_or_else(|| build_error!()) + } +} + +// Forward array impl to slice impl. +// SAFETY: safety requirement guaranteed by the forwarded impl. +unsafe impl ProjectIndex<[T; N]> for I +where + I: ProjectIndex<[T]>, +{ + type Output =3D >::Output; + + #[inline(always)] + fn get(self, slice: *mut [T; N]) -> Option<*mut Self::Output> { + >::get(self, slice) + } + + #[inline(always)] + fn index(self, slice: *mut [T; N]) -> *mut Self::Output { + >::index(self, slice) + } +} + +// SAFETY: `get` returned pointers has same provenance as `slice` and the = offset is checked to not +// exceed the required bound. +unsafe impl ProjectIndex<[T]> for usize { + type Output =3D T; + + #[inline(always)] + fn get(self, slice: *mut [T]) -> Option<*mut T> { + if self >=3D slice.len() { + None + } else { + Some(slice.cast::().wrapping_add(self)) + } + } +} + +// SAFETY: `get` returned pointers has same provenance as `slice` and the = offset is checked to not +// exceed the required bound. +unsafe impl ProjectIndex<[T]> for core::ops::Range { + type Output =3D [T]; + + #[inline(always)] + fn get(self, slice: *mut [T]) -> Option<*mut [T]> { + let new_len =3D self.end.checked_sub(self.start)?; + if self.end > slice.len() { + return None; + } + Some(core::ptr::slice_from_raw_parts_mut( + slice.cast::().wrapping_add(self.start), + new_len, + )) + } +} + +// SAFETY: safety requirement guaranteed by the forwarded impl. +unsafe impl ProjectIndex<[T]> for core::ops::RangeTo { + type Output =3D [T]; + + #[inline(always)] + fn get(self, slice: *mut [T]) -> Option<*mut [T]> { + (0..self.end).get(slice) + } +} + +// SAFETY: safety requirement guaranteed by the forwarded impl. +unsafe impl ProjectIndex<[T]> for core::ops::RangeFrom { + type Output =3D [T]; + + #[inline(always)] + fn get(self, slice: *mut [T]) -> Option<*mut [T]> { + (self.start..slice.len()).get(slice) + } +} + +// SAFETY: `get` returned the pointer as is, so it always have the same pr= ovenance and offset of 0. +unsafe impl ProjectIndex<[T]> for core::ops::RangeFull { + type Output =3D [T]; + + #[inline(always)] + fn get(self, slice: *mut [T]) -> Option<*mut [T]> { + Some(slice) + } +} + +/// A helper trait to perform field projection. +/// +/// This trait has a `DEREF` generic parameter so it can be implemented tw= ice for types that +/// implement `Deref`. This will cause an ambiguity error and thus block `= Deref` types being used +/// as base of projection, as they can inject unsoundness. Users therefore= must not specify `DEREF` +/// and should always leave it to be inferred. +/// +/// # Safety +/// +/// `proj` may only invoke `f` with a valid allocation, as documentation d= escribed. +#[doc(hidden)] +pub unsafe trait ProjectField { + /// Project a pointer to a type to a pointer of a field. + /// + /// `f` may only be invoked with a valid allocation so it can safely o= btain raw pointers to + /// fields using `&raw mut`. + /// + /// This is needed because `base` might not point to a valid allocatio= n, while `&raw mut` + /// requires pointers to be in bounds of a valid allocation. + /// + /// # Safety + /// + /// `f` must return a pointer in bounds of the provided pointer. + unsafe fn proj(base: *mut Self, f: impl FnOnce(*mut Self) -> *mut F= ) -> *mut F; +} + +// NOTE: in theory, this API should work for `T: ?Sized` and `F: ?Sized`, = too. However we cannot +// currently support that as we need to obtain a valid allocation that `&r= aw const` can operate on. +// SAFETY: `proj` invokes `f` with valid allocation. +unsafe impl ProjectField for T { + #[inline(always)] + unsafe fn proj(base: *mut Self, f: impl FnOnce(*mut Self) -> *mut F= ) -> *mut F { + // Create a valid allocation to start projection, as `base` is not= necessarily so. The + // memory is never actually used so it will be optimized out, so i= t should work even for + // very large `T` (`memoffset` crate also relies on this). To be e= xtra certain, we also + // annotate `f` closure with `#[inline(always)]` in the macro. + let mut place =3D MaybeUninit::uninit(); + let place_base =3D place.as_mut_ptr(); + let field =3D f(place_base); + // SAFETY: `field` is in bounds from `base` per safety requirement. + let offset =3D unsafe { field.byte_offset_from(place_base) }; + // Use `wrapping_byte_offset` as `base` does not need to be of val= id allocation. + base.wrapping_byte_offset(offset).cast() + } +} + +// SAFETY: vacuously satisfied. +unsafe impl ProjectField for T { + #[inline(always)] + unsafe fn proj(_: *mut Self, _: impl FnOnce(*mut Self) -> *mut F) -= > *mut F { + build_error!("this function is a guard against `Deref` impl and is= never invoked"); + } +} + +/// Create a projection from a raw pointer. +/// +/// The projected pointer is within the memory region marked by the input = pointer. There is no +/// requirement that the input raw pointer needs to be valid, so this macr= o may be used for +/// projecting pointers outside normal address space, e.g. I/O pointers. H= owever, if the input +/// pointer is valid, the projected pointer is also valid. +/// +/// Supported projections include field projections and index projections. +/// It is not allowed to project into types that implement custom `Deref` = or `Index`. +/// +/// The macro has basic syntax of `kernel::ptr::project!(ptr, projection)`= , where `ptr` is an +/// expression that evaluates to a raw pointer which serves as the base of= projection. `projection` +/// can be a projection expression of form `.field` (normally identifer, o= r numeral in case of +/// tuple structs) or of form `[index]`. +/// +/// If mutable pointer is needed, the macro input can be prefixed with `mu= t` keyword, i.e. +/// `kernel::ptr::project!(mut ptr, projection)`. By default, a const poin= ter is created. +/// +/// `ptr::project!` macro can perform both fallible indexing and build-tim= e checked indexing. +/// `[index]` form performs build-time bounds checking; if compiler fails = to prove `[index]` is in +/// bounds, compilation will fail. `[index]?` can be used to perform runti= me bounds checking; +/// `OutOfBound` error is raised via `?` if the index is out of bounds. +/// +/// # Examples +/// +/// Field projections are performed with `.field_name`: +/// ``` +/// struct MyStruct { field: u32, } +/// let ptr: *const MyStruct =3D core::ptr::dangling(); +/// let field_ptr: *const u32 =3D kernel::ptr::project!(ptr, .field); +/// +/// struct MyTupleStruct(u32, u32); +/// +/// fn proj(ptr: *const MyTupleStruct) { +/// let field_ptr: *const u32 =3D kernel::ptr::project!(ptr, .1); +/// } +/// ``` +/// +/// Index projections are performed with `[index]`: +/// ``` +/// fn proj(ptr: *const [u8; 32]) -> Result { +/// let field_ptr: *const u8 =3D kernel::ptr::project!(ptr, [1]); +/// // This will fail the build. +/// // kernel::ptr::project!(ptr, [128]); +/// // This will raise an `OutOfBound` error (which is convertable to = `ERANGE`). +/// kernel::ptr::project!(ptr, [128]?); +/// Ok(()) +/// } +/// ``` +/// +/// If you need to match on the error instead of propagate, put the invoca= tion inside a closure: +/// ``` +/// let ptr: *const [u8; 32] =3D core::ptr::dangling(); +/// let field_ptr: Result<*const u8> =3D (|| -> Result<_> { +/// Ok(kernel::ptr::project!(ptr, [128]?)) +/// })(); +/// assert!(field_ptr.is_err()); +/// ``` +/// +/// For mutable pointers, put `mut` as the first token in macro invocation. +/// ``` +/// let ptr: *mut [(u8, u16); 32] =3D core::ptr::dangling_mut(); +/// let field_ptr: *mut u16 =3D kernel::ptr::project!(mut ptr, [1].1); +/// ``` +#[macro_export] +macro_rules! project_pointer { + (@gen $ptr:ident, ) =3D> {}; + // Field projection. `$field` needs to be `tt` to support tuple index = like `.0`. + (@gen $ptr:ident, .$field:tt $($rest:tt)*) =3D> { + // SAFETY: the provided closure always return in bounds pointer. + let $ptr =3D unsafe { + $crate::ptr::projection::ProjectField::proj($ptr, #[inline(alw= ays)] |ptr| { + // Check unaligned field. Not all users (e.g. DMA) can han= dle unaligned + // projections. + if false { + let _ =3D &(*ptr).$field; + } + // SAFETY: `$field` is in bounds, and no implicit `Deref` = is possible (if the + // type implements `Deref`, Rust cannot infer the generic = parameter `DEREF`). + &raw mut (*ptr).$field + }) + }; + $crate::ptr::project!(@gen $ptr, $($rest)*) + }; + // Fallible index projection. + (@gen $ptr:ident, [$index:expr]? $($rest:tt)*) =3D> { + let $ptr =3D $crate::ptr::projection::ProjectIndex::get($index, $p= tr) + .ok_or($crate::ptr::projection::OutOfBound)?; + $crate::ptr::project!(@gen $ptr, $($rest)*) + }; + // Build-time checked index projection. + (@gen $ptr:ident, [$index:expr] $($rest:tt)*) =3D> { + let $ptr =3D $crate::ptr::projection::ProjectIndex::index($index, = $ptr); + $crate::ptr::project!(@gen $ptr, $($rest)*) + }; + (mut $ptr:expr, $($proj:tt)*) =3D> {{ + let ptr: *mut _ =3D $ptr; + $crate::ptr::project!(@gen ptr, $($proj)*); + ptr + }}; + ($ptr:expr, $($proj:tt)*) =3D> {{ + let ptr =3D <*const _>::cast_mut($ptr); + // We currently always project using mutable pointer, as it is not= decided whether `&raw + // const` allows the resulting pointer to be mutated (see document= ation of `addr_of!`). + $crate::ptr::project!(@gen ptr, $($proj)*); + ptr.cast_const() + }}; +} diff --git a/scripts/Makefile.build b/scripts/Makefile.build index 32e209bc7985cbf26ad8af2b986fbd1920aa3bd4..3652b85be54594e3616be5a92c1= 967035e9d919d 100644 --- a/scripts/Makefile.build +++ b/scripts/Makefile.build @@ -310,16 +310,18 @@ $(obj)/%.lst: $(obj)/%.c FORCE =20 # The features in this list are the ones allowed for non-`rust/` code. # +# - Stable since Rust 1.79.0: `feature(slice_ptr_len)`. # - Stable since Rust 1.81.0: `feature(lint_reasons)`. # - Stable since Rust 1.82.0: `feature(asm_const)`, # `feature(offset_of_nested)`, `feature(raw_ref_op)`. +# - Stable since Rust 1.84.0: `feature(strict_provenance)`. # - Stable since Rust 1.87.0: `feature(asm_goto)`. # - Expected to become stable: `feature(arbitrary_self_types)`. # - To be determined: `feature(used_with_arg)`. # # Please see https://github.com/Rust-for-Linux/linux/issues/2 for details = on # the unstable features in use. -rust_allowed_features :=3D asm_const,asm_goto,arbitrary_self_types,lint_re= asons,offset_of_nested,raw_ref_op,used_with_arg +rust_allowed_features :=3D asm_const,asm_goto,arbitrary_self_types,lint_re= asons,offset_of_nested,raw_ref_op,slice_ptr_len,strict_provenance,used_with= _arg =20 # `--out-dir` is required to avoid temporaries being created by `rustc` in= the # current working directory, which may be not accessible in the out-of-tree --=20 2.53.0