Rust for Linux List
 help / color / mirror / Atom feed
* [PATCH v15 0/2] rust: Add safe pointer formatting support
@ 2026-08-10  6:35 Ke Sun
  2026-08-10  6:35 ` [PATCH v15 1/2] rust: fmt: fix {:p} printing stack addresses Ke Sun
  2026-08-10  6:35 ` [PATCH v15 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks Ke Sun
  0 siblings, 2 replies; 6+ messages in thread
From: Ke Sun @ 2026-08-10  6:35 UTC (permalink / raw)
  To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich
  Cc: rust-for-linux, Ke Sun

This series fixes two issues with {:p} pointer formatting:
- The impl_fmt_adapter_forward! macro destructures self into a local
  variable, causing {:p} to print a stack address instead of the actual
  pointer
- Kernel address leak - {:p} prints raw pointer values, exposing kernel
  address space layout

---
Changes in v15:
- Clamp zero-pad width to buffer size, add boundary tests (Sashiko, Miguel)
- Fix {:00p} output mismatch introduced in v14
- Link to v14: https://lore.kernel.org/r/20260807-hashedptr-v14-0-817009769ffb@kylinos.cn

Changes in v14:
- Use `%#0*p` for `0x` prefix and zero-padding (Gary); this also avoids
  "0x(...)" for placeholder tokens like "(____ptrval____)" (Alice)
- Simplify pointer impls per Gary
- Link to v13: https://lore.kernel.org/r/20260706-hashedptr-v13-0-377a07f2f78d@kylinos.cn

Changes in v13:
- Add NonNull<T> pointer formatting support
- Add #[inline] to all Pointer impl methods
- Support zero-padding format ({:0width$p})
- Simplify SAFETY comments
- Rewrite tests: remove NoHashPointersGuard - modifying
  no_hash_pointers after boot panics since it's __ro_after_init; read
  current value and branch instead; expand format coverage
- Link to v12: https://lore.kernel.org/r/20260512-hashedptr-v12-0-61d5c7786889@kylinos.cn

Changes in v12:
- Split into 2 patches: fix {:p} printing stack addresses -> route {:p}
  through HashedPtr
- Test cleanup: NoHashPointersGuard RAII guard replaces raw
  save/restore; mod expected consolidates 32/64-bit constants
- Impl delegation: &T, &mut T, *mut T all forward to *const T (matching
  core library conventions), replacing v11's blanket impl + macro
- Link to v11: https://lore.kernel.org/r/20260205-hashedptr-v11-1-bd0fec7fe6f1@kylinos.cn

Changes in v11:
- Fix inaccurate or inappropriate descriptions in comments
- Use as_char_ptr instead of as_ptr so that a *const u8 pointer is
  always passed to scnprintf on all architectures
- Per Tamir's suggestion, replace doctests with mod tests and adjust
  test content to make the tests more meaningful
- Remove the RawPtr wrapper type: it and HashedPtr use different
  formatting mechanisms (HashedPtr uses scnprintf and pad; RawPtr would
  call core's Pointer impl directly). This series focuses on fixing the
  issue that without it {:p} would output the pointer's stack address,
  and on using HashedPtr to safely format raw pointers and avoid leaking
  kernel address space layout information
- Link to v10: https://lore.kernel.org/r/20260121050059.2315091-1-sunke@kylinos.cn

Changes in v10:
- Merge all patches into a single patch
- Improve `kernel::fmt::Pointer` trait implementation
Link to v9: https://lore.kernel.org/r/20260119033006.1453006-1-sunke@kylinos.cn

Changes in v9: https://lore.kernel.org/r/20260119033006.1453006-1-sunke@kylinos.cn
- Refactor implementation to use Pointer trait and Adapter pattern
  instead of exporting ptr_to_hashval() from lib/vsprintf.c. Use
  scnprintf directly in Rust for pointer hashing, eliminating the
  need for C function export
- Move pointer wrapper types from rust/kernel/ptr.rs to
  rust/kernel/fmt.rs
- Split implementation into more granular patches: Pointer trait
  foundation, HashedPtr type, raw pointer default behavior, and
  RawPtr type
- Remove documentation patch, integrate examples into code doctests
- Simplify API and improve code organization following Display trait
  pattern
- Link to v8: https://lore.kernel.org/r/20260101081605.1300953-1-sunke@kylinos.cn

Changes in v8:
- Remove RestrictedPtr (%pK) support: only export ptr_to_hashval() with
  EXPORT_SYMBOL_NS_GPL using "RUST_INTERNAL" namespace, provide only two
  pointer wrapper types (HashedPtr, RawPtr) for %p and %px
- Change API from HashedPtr::from(ptr) to HashedPtr(ptr) for direct
  construction
- Link to v7: https://lore.kernel.org/r/20251229072157.3857053-1-sunke@kylinos.cn

Changes in v7:
- Refactor kptr_restrict handling: extract kptr_restrict_value() from
  restricted_pointer() in lib/vsprintf.c and export it for Rust use, and
  improve RestrictedPtr::fmt() implementation to directly handle
  kptr_restrict_value() return values (0, 1, 2, -1) for better code
  clarity
- Remove Debug derive from pointer wrapper types (HashedPtr,
  RestrictedPtr, RawPtr)
- Link to v6: https://lore.kernel.org/r/20251227033958.3713232-1-sunke@kylinos.cn

Changes in v6:
- Fix placeholder formatting to use `f.pad()` instead of `f.write_str()`
  in format_hashed_ptr(), ensuring width, alignment, and padding options
  are correctly applied to PTR_PLACEHOLDER
- Link to v5: https://lore.kernel.org/r/20251226140751.2215563-1-sunke@kylinos.cn

Changes in v5: https://lore.kernel.org/r/20251226140751.2215563-1-sunke@kylinos.cn
- Format use statements in rust/kernel/ptr.rs and rust/kernel/fmt.rs
  using kernel vertical style with alphabetical ordering
- Remove unnecessary SAFETY comment in rust/kernel/ptr.rs (addressed
  Clippy warning)
- Update type ordering to alphabetical (HashedPtr, RawPtr,
  RestrictedPtr) in fmt.rs macro invocation
- Link to v4: https://lore.kernel.org/r/20251225225709.3944255-1-sunke@kylinos.cn

Changes in v4:
- Use Pointer::fmt() instead of write!(f, "{:p}", ...) to preserve
  formatting options (width, alignment, padding characters)
- Improve code structure: reduce unsafe block scope, use early return
  pattern
- Add doctests with formatting option tests for all pointer wrapper
  types
- Enhance documentation with detailed formatting options section,
  including examples for width, alignment, and padding
- Fix RestrictedPtr example to use pr_info! instead of seq_print! in
  docs
- Link to v3: https://lore.kernel.org/r/20251224081315.729684-1-sunke@kylinos.cn

Changes in v3:
- Export ptr_to_hashval() from lib/vsprintf.c for Rust pointer hashing
- Add three pointer wrapper types (HashedPtr, RestrictedPtr, RawPtr) in
  rust/kernel/ptr.rs corresponding to %p, %pK, and %px
- Make raw pointers automatically use HashedPtr when formatted with {:p}
- Add documentation for pointer wrapper types
- Link to v2: https://lore.kernel.org/r/20251223033018.2814732-1-sunke@kylinos.cn

Changes in v2:
- Disabled {:p} raw pointer printing by default to prevent accidental
  information leaks
- Link to v1: https://lore.kernel.org/r/20251218032709.2184890-1-sunke@kylinos.cn

Signed-off-by: Ke Sun <sunke@kylinos.cn>

---
Ke Sun (2):
      rust: fmt: fix {:p} printing stack addresses
      rust: fmt: route {:p} through HashedPtr to prevent address leaks

 rust/kernel/fmt.rs | 189 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 187 insertions(+), 2 deletions(-)
---
base-commit: 2ee859ebf156157609f71060ae472711c8cbc326
change-id: 20260512-hashedptr-22469b930113

Best regards,
-- 
Ke Sun <sunke@kylinos.cn>


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [PATCH v15 1/2] rust: fmt: fix {:p} printing stack addresses
  2026-08-10  6:35 [PATCH v15 0/2] rust: Add safe pointer formatting support Ke Sun
@ 2026-08-10  6:35 ` Ke Sun
  2026-08-10 10:48   ` Gary Guo
  2026-08-10  6:35 ` [PATCH v15 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks Ke Sun
  1 sibling, 1 reply; 6+ messages in thread
From: Ke Sun @ 2026-08-10  6:35 UTC (permalink / raw)
  To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich
  Cc: rust-for-linux, Ke Sun

The `impl_fmt_adapter_forward!` macro forwards `Pointer` for
`Adapter<T>` by destructuring `self` into a local `t`, causing `{:p}`
to print the address of that temporary stack variable rather than the
actual pointer.

Remove `Pointer` from the macro and provide a manual impl for
`Adapter<&T>` that passes `self.0` directly.

Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
 rust/kernel/fmt.rs | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/fmt.rs b/rust/kernel/fmt.rs
index 73afbc51ba33a..cd7d9664ff5b9 100644
--- a/rust/kernel/fmt.rs
+++ b/rust/kernel/fmt.rs
@@ -43,7 +43,14 @@ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
     UpperExp,
     UpperHex, //
 };
-impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, Pointer, LowerExp, UpperExp);
+impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, LowerExp, UpperExp);
+
+impl<T: ?Sized + Pointer> Pointer for Adapter<&T> {
+    #[inline]
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        Pointer::fmt(self.0, f)
+    }
+}
 
 /// A copy of [`core::fmt::Display`] that allows us to implement it for foreign types.
 ///

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [PATCH v15 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks
  2026-08-10  6:35 [PATCH v15 0/2] rust: Add safe pointer formatting support Ke Sun
  2026-08-10  6:35 ` [PATCH v15 1/2] rust: fmt: fix {:p} printing stack addresses Ke Sun
@ 2026-08-10  6:35 ` Ke Sun
  2026-08-10 10:59   ` Gary Guo
  1 sibling, 1 reply; 6+ messages in thread
From: Ke Sun @ 2026-08-10  6:35 UTC (permalink / raw)
  To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich
  Cc: rust-for-linux, Ke Sun

Define a custom `kernel::fmt::Pointer` trait and `HashedPtr` wrapper
so that `{:p}` formatting uses the kernel's `%p` hashed format instead
of printing raw pointer values, preventing kernel address space leaks.

Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
 rust/kernel/fmt.rs | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 180 insertions(+), 2 deletions(-)

diff --git a/rust/kernel/fmt.rs b/rust/kernel/fmt.rs
index cd7d9664ff5b9..6f7cb657bef7c 100644
--- a/rust/kernel/fmt.rs
+++ b/rust/kernel/fmt.rs
@@ -4,6 +4,8 @@
 //!
 //! This module is intended to be used in place of `core::fmt` in kernel code.
 
+use kernel::prelude::*;
+
 pub use core::fmt::{
     Arguments,
     Debug,
@@ -39,13 +41,110 @@ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
     LowerExp,
     LowerHex,
     Octal,
-    Pointer,
     UpperExp,
     UpperHex, //
 };
+use core::ptr::NonNull;
 impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, LowerExp, UpperExp);
 
-impl<T: ?Sized + Pointer> Pointer for Adapter<&T> {
+/// A copy of [`core::fmt::Pointer`] that allows implementing pointer formatting for foreign types.
+///
+/// Together with the [`Adapter`] type and [`fmt!`] macro, it enables raw pointer formatting to be
+/// intercepted and routed to [`HashedPtr`] (kernel's `%p` hashed format), preventing kernel address
+/// leaks.
+///
+/// [`fmt!`]: crate::prelude::fmt!
+pub trait Pointer {
+    /// Same as [`core::fmt::Pointer::fmt`].
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result;
+}
+
+/// A wrapper for pointers that formats them using kernel's `%p` format specifier.
+///
+/// By default, `%p` prints a hashed representation of the pointer address to prevent kernel address
+/// leaks. When the `no_hash_pointers` kernel command-line parameter is enabled, the real address is
+/// printed instead (for debugging purposes).
+pub struct HashedPtr<T: ?Sized>(pub *const T);
+
+impl<T: ?Sized> Pointer for HashedPtr<T> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        use crate::str::CStrExt as _;
+
+        let mut buf = [0u8; 32];
+
+        // Use `%#0*p` for the `0x` prefix and zero-padding; `+2` compensates for
+        // the prefix counting toward the field width.
+        let default_width = (2 * size_of::<usize>() + 2) as c_int;
+        let width = match (f.sign_aware_zero_pad(), f.width()) {
+            (true, Some(w)) if w > 0 => w.min(buf.len() - 1) as c_int,
+            _ => default_width,
+        };
+
+        // SAFETY: `buf` is a valid, writable 32-byte buffer, sufficient for
+        // all architectures (max 19 bytes for 64-bit under the default width).
+        // The format string is null-terminated; `width` (c_int) and pointer
+        // match the `%*` and `%p` specifiers.
+        let len = unsafe {
+            crate::bindings::scnprintf(
+                buf.as_mut_ptr().cast(),
+                buf.len(),
+                c"%#0*p".as_char_ptr(),
+                width,
+                self.0.cast::<c_void>(),
+            )
+        };
+
+        // SAFETY: `%#0*p` produces only ASCII, which is valid UTF-8.
+        let s = unsafe { core::str::from_utf8_unchecked(&buf[..len as usize]) };
+
+        if f.sign_aware_zero_pad() {
+            // The kernel handled the width and zero-padding already.
+            f.write_str(s)
+        } else {
+            f.pad(s)
+        }
+    }
+}
+
+// Raw pointers are formatted via `HashedPtr` (kernel `%p`: hashed by default, plain with
+// `no_hash_pointers`).
+impl<T: ?Sized> Pointer for *const T {
+    #[inline]
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        Pointer::fmt(&HashedPtr(*self), f)
+    }
+}
+
+impl<T: ?Sized> Pointer for *mut T {
+    #[inline]
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        Pointer::fmt(&HashedPtr(*self), f)
+    }
+}
+
+impl<T: ?Sized> Pointer for &T {
+    #[inline]
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        Pointer::fmt(&HashedPtr(*self), f)
+    }
+}
+
+impl<T: ?Sized> Pointer for &mut T {
+    #[inline]
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        Pointer::fmt(&HashedPtr(core::ptr::from_ref(*self)), f)
+    }
+}
+
+impl<T: ?Sized> Pointer for NonNull<T> {
+    #[inline]
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        Pointer::fmt(&HashedPtr(self.as_ptr()), f)
+    }
+}
+
+// `Adapter<&T>` bridges our `Pointer` trait to `core::fmt::Pointer`
+impl<T: Pointer> core::fmt::Pointer for Adapter<&T> {
     #[inline]
     fn fmt(&self, f: &mut Formatter<'_>) -> Result {
         Pointer::fmt(self.0, f)
@@ -112,3 +211,82 @@ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
     {<T: ?Sized>} crate::sync::Arc<T> {where crate::sync::Arc<T>: core::fmt::Display},
     {<T: ?Sized>} crate::sync::UniqueArc<T> {where crate::sync::UniqueArc<T>: core::fmt::Display},
 );
+
+#[macros::kunit_tests(rust_kernel_fmt)]
+mod tests {
+    use crate::{
+        bindings,
+        prelude::fmt,
+        str::CString, //
+    };
+
+    #[cfg(CONFIG_64BIT)]
+    mod expected {
+        pub(super) const PTR_VALUE: usize = 0xffffffffdeadbeef;
+        pub(super) const HASHED_PREFIX: &str = "0x00000000";
+        pub(super) const RAW_POINTER: &str = "0xffffffffdeadbeef";
+        pub(super) const PADDED_RIGHT: &str = "      0xffffffffdeadbeef";
+        pub(super) const ZERO_PADDED: &str = "0x000000ffffffffdeadbeef";
+        pub(super) const HASHED_PADDED_RIGHT_PREFIX: &str = "      ";
+        pub(super) const HASHED_ZERO_PADDED_PREFIX: &str = "0x00000000000000";
+        pub(super) const CLAMPED: &str = "0x0000000000000ffffffffdeadbeef";
+    }
+
+    #[cfg(not(CONFIG_64BIT))]
+    mod expected {
+        pub(super) const PTR_VALUE: usize = 0xdeadbeef;
+        pub(super) const HASHED_PREFIX: &str = "0x";
+        pub(super) const RAW_POINTER: &str = "0xdeadbeef";
+        pub(super) const PADDED_RIGHT: &str = "              0xdeadbeef";
+        pub(super) const ZERO_PADDED: &str = "0x00000000000000deadbeef";
+        pub(super) const HASHED_PADDED_RIGHT_PREFIX: &str = "              ";
+        pub(super) const HASHED_ZERO_PADDED_PREFIX: &str = "0x00000000000000";
+        pub(super) const CLAMPED: &str = "0x0000000000000000000000deadbeef";
+    }
+
+    #[test]
+    fn test_ptr_formatting() -> core::result::Result<(), crate::error::Error> {
+        let ptr = expected::PTR_VALUE as *const u8;
+
+        // SAFETY: `no_hash_pointers` is a global variable that is never concurrently modified —
+        // KUnit tests may run at boot (before `mark_readonly()`) or manually afterwards (when the
+        // variable is read-only). Reading is always safe.
+        let no_hash = unsafe { bindings::no_hash_pointers };
+
+        if no_hash {
+            let cstr = CString::try_from_fmt(fmt!("{:p}", ptr))?;
+            assert_eq!(cstr.to_str()?, expected::RAW_POINTER);
+
+            let cstr = CString::try_from_fmt(fmt!("{:>24p}", ptr))?;
+            assert_eq!(cstr.to_str()?, expected::PADDED_RIGHT);
+
+            let cstr = CString::try_from_fmt(fmt!("{:024p}", ptr))?;
+            assert_eq!(cstr.to_str()?, expected::ZERO_PADDED);
+
+            let cstr = CString::try_from_fmt(fmt!("{:01000p}", ptr))?;
+            assert_eq!(cstr.to_str()?, expected::CLAMPED);
+        } else {
+            let cstr = CString::try_from_fmt(fmt!("{:p}", ptr))?;
+            let formatted = cstr.to_str()?;
+            assert!(formatted.starts_with(expected::HASHED_PREFIX));
+            assert_ne!(formatted, expected::RAW_POINTER);
+
+            let cstr = CString::try_from_fmt(fmt!("{:>24p}", ptr))?;
+            assert!(cstr
+                .to_str()?
+                .starts_with(expected::HASHED_PADDED_RIGHT_PREFIX));
+
+            let cstr = CString::try_from_fmt(fmt!("{:024p}", ptr))?;
+            assert!(cstr
+                .to_str()?
+                .starts_with(expected::HASHED_ZERO_PADDED_PREFIX));
+
+            let cstr = CString::try_from_fmt(fmt!("{:01000p}", ptr))?;
+            let output = cstr.to_str()?;
+            assert!(output.starts_with("0x"));
+            assert!(!output[2..].chars().all(|c| c == '0'));
+        }
+
+        Ok(())
+    }
+}

-- 
2.43.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* Re: [PATCH v15 1/2] rust: fmt: fix {:p} printing stack addresses
  2026-08-10  6:35 ` [PATCH v15 1/2] rust: fmt: fix {:p} printing stack addresses Ke Sun
@ 2026-08-10 10:48   ` Gary Guo
  2026-08-10 13:17     ` Ke Sun
  0 siblings, 1 reply; 6+ messages in thread
From: Gary Guo @ 2026-08-10 10:48 UTC (permalink / raw)
  To: Ke Sun, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich
  Cc: rust-for-linux

On Mon Aug 10, 2026 at 7:35 AM BST, Ke Sun wrote:
> The `impl_fmt_adapter_forward!` macro forwards `Pointer` for
> `Adapter<T>` by destructuring `self` into a local `t`, causing `{:p}`
> to print the address of that temporary stack variable rather than the
> actual pointer.
>
> Remove `Pointer` from the macro and provide a manual impl for
> `Adapter<&T>` that passes `self.0` directly.
>
> Signed-off-by: Ke Sun <sunke@kylinos.cn>

I don't think this has changed since last version. Usually you should pick up
all tags provided for the previous series, so people know that it has been
reviewed already and don't need to spend more time correlating it with the
previous series.

(No need to resend just with tags picked up though)

Best,
Gary

> ---
>  rust/kernel/fmt.rs | 9 ++++++++-
>  1 file changed, 8 insertions(+), 1 deletion(-)
>
> diff --git a/rust/kernel/fmt.rs b/rust/kernel/fmt.rs
> index 73afbc51ba33a..cd7d9664ff5b9 100644
> --- a/rust/kernel/fmt.rs
> +++ b/rust/kernel/fmt.rs
> @@ -43,7 +43,14 @@ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
>      UpperExp,
>      UpperHex, //
>  };
> -impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, Pointer, LowerExp, UpperExp);
> +impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, LowerExp, UpperExp);
> +
> +impl<T: ?Sized + Pointer> Pointer for Adapter<&T> {
> +    #[inline]
> +    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
> +        Pointer::fmt(self.0, f)
> +    }
> +}
>  
>  /// A copy of [`core::fmt::Display`] that allows us to implement it for foreign types.
>  ///



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v15 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks
  2026-08-10  6:35 ` [PATCH v15 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks Ke Sun
@ 2026-08-10 10:59   ` Gary Guo
  0 siblings, 0 replies; 6+ messages in thread
From: Gary Guo @ 2026-08-10 10:59 UTC (permalink / raw)
  To: Ke Sun, Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich
  Cc: rust-for-linux

On Mon Aug 10, 2026 at 7:35 AM BST, Ke Sun wrote:
> Define a custom `kernel::fmt::Pointer` trait and `HashedPtr` wrapper
> so that `{:p}` formatting uses the kernel's `%p` hashed format instead
> of printing raw pointer values, preventing kernel address space leaks.
>
> Signed-off-by: Ke Sun <sunke@kylinos.cn>
> ---
>  rust/kernel/fmt.rs | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 180 insertions(+), 2 deletions(-)
>
> diff --git a/rust/kernel/fmt.rs b/rust/kernel/fmt.rs
> index cd7d9664ff5b9..6f7cb657bef7c 100644
> --- a/rust/kernel/fmt.rs
> +++ b/rust/kernel/fmt.rs
> @@ -4,6 +4,8 @@
>  //!
>  //! This module is intended to be used in place of `core::fmt` in kernel code.
>  
> +use kernel::prelude::*;
> +
>  pub use core::fmt::{
>      Arguments,
>      Debug,
> @@ -39,13 +41,110 @@ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
>      LowerExp,
>      LowerHex,
>      Octal,
> -    Pointer,
>      UpperExp,
>      UpperHex, //
>  };
> +use core::ptr::NonNull;
>  impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, LowerExp, UpperExp);
>  
> -impl<T: ?Sized + Pointer> Pointer for Adapter<&T> {
> +/// A copy of [`core::fmt::Pointer`] that allows implementing pointer formatting for foreign types.
> +///
> +/// Together with the [`Adapter`] type and [`fmt!`] macro, it enables raw pointer formatting to be
> +/// intercepted and routed to [`HashedPtr`] (kernel's `%p` hashed format), preventing kernel address
> +/// leaks.
> +///
> +/// [`fmt!`]: crate::prelude::fmt!
> +pub trait Pointer {
> +    /// Same as [`core::fmt::Pointer::fmt`].
> +    fn fmt(&self, f: &mut Formatter<'_>) -> Result;
> +}
> +
> +/// A wrapper for pointers that formats them using kernel's `%p` format specifier.
> +///
> +/// By default, `%p` prints a hashed representation of the pointer address to prevent kernel address
> +/// leaks. When the `no_hash_pointers` kernel command-line parameter is enabled, the real address is
> +/// printed instead (for debugging purposes).
> +pub struct HashedPtr<T: ?Sized>(pub *const T);
> +
> +impl<T: ?Sized> Pointer for HashedPtr<T> {
> +    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
> +        use crate::str::CStrExt as _;
> +
> +        let mut buf = [0u8; 32];
> +
> +        // Use `%#0*p` for the `0x` prefix and zero-padding; `+2` compensates for
> +        // the prefix counting toward the field width.
> +        let default_width = (2 * size_of::<usize>() + 2) as c_int;
> +        let width = match (f.sign_aware_zero_pad(), f.width()) {
> +            (true, Some(w)) if w > 0 => w.min(buf.len() - 1) as c_int,
> +            _ => default_width,
> +        };
> +
> +        // SAFETY: `buf` is a valid, writable 32-byte buffer, sufficient for
> +        // all architectures (max 19 bytes for 64-bit under the default width).
> +        // The format string is null-terminated; `width` (c_int) and pointer
> +        // match the `%*` and `%p` specifiers.
> +        let len = unsafe {
> +            crate::bindings::scnprintf(
> +                buf.as_mut_ptr().cast(),
> +                buf.len(),
> +                c"%#0*p".as_char_ptr(),
> +                width,
> +                self.0.cast::<c_void>(),
> +            )
> +        };
> +
> +        // SAFETY: `%#0*p` produces only ASCII, which is valid UTF-8.
> +        let s = unsafe { core::str::from_utf8_unchecked(&buf[..len as usize]) };
> +
> +        if f.sign_aware_zero_pad() {
> +            // The kernel handled the width and zero-padding already.

nit: this is kernel code too, so the comment here is off. should say something
like "snprintf handled the width and zero-padding".

with that,

Reviewed-by: Gary Guo <gary@garyguo.net>

for the functional part of the code, some additional nits for tests below.

> +            f.write_str(s)
> +        } else {
> +            f.pad(s)
> +        }
> +    }
> +}
> +
> +// Raw pointers are formatted via `HashedPtr` (kernel `%p`: hashed by default, plain with
> +// `no_hash_pointers`).
> +impl<T: ?Sized> Pointer for *const T {
> +    #[inline]
> +    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
> +        Pointer::fmt(&HashedPtr(*self), f)
> +    }
> +}
> +
> +impl<T: ?Sized> Pointer for *mut T {
> +    #[inline]
> +    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
> +        Pointer::fmt(&HashedPtr(*self), f)
> +    }
> +}
> +
> +impl<T: ?Sized> Pointer for &T {
> +    #[inline]
> +    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
> +        Pointer::fmt(&HashedPtr(*self), f)
> +    }
> +}
> +
> +impl<T: ?Sized> Pointer for &mut T {
> +    #[inline]
> +    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
> +        Pointer::fmt(&HashedPtr(core::ptr::from_ref(*self)), f)
> +    }
> +}
> +
> +impl<T: ?Sized> Pointer for NonNull<T> {
> +    #[inline]
> +    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
> +        Pointer::fmt(&HashedPtr(self.as_ptr()), f)
> +    }
> +}
> +
> +// `Adapter<&T>` bridges our `Pointer` trait to `core::fmt::Pointer`
> +impl<T: Pointer> core::fmt::Pointer for Adapter<&T> {
>      #[inline]
>      fn fmt(&self, f: &mut Formatter<'_>) -> Result {
>          Pointer::fmt(self.0, f)
> @@ -112,3 +211,82 @@ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
>      {<T: ?Sized>} crate::sync::Arc<T> {where crate::sync::Arc<T>: core::fmt::Display},
>      {<T: ?Sized>} crate::sync::UniqueArc<T> {where crate::sync::UniqueArc<T>: core::fmt::Display},
>  );
> +
> +#[macros::kunit_tests(rust_kernel_fmt)]
> +mod tests {
> +    use crate::{
> +        bindings,
> +        prelude::fmt,
> +        str::CString, //
> +    };
> +
> +    #[cfg(CONFIG_64BIT)]
> +    mod expected {
> +        pub(super) const PTR_VALUE: usize = 0xffffffffdeadbeef;
> +        pub(super) const HASHED_PREFIX: &str = "0x00000000";
> +        pub(super) const RAW_POINTER: &str = "0xffffffffdeadbeef";
> +        pub(super) const PADDED_RIGHT: &str = "      0xffffffffdeadbeef";
> +        pub(super) const ZERO_PADDED: &str = "0x000000ffffffffdeadbeef";
> +        pub(super) const HASHED_PADDED_RIGHT_PREFIX: &str = "      ";
> +        pub(super) const HASHED_ZERO_PADDED_PREFIX: &str = "0x00000000000000";
> +        pub(super) const CLAMPED: &str = "0x0000000000000ffffffffdeadbeef";
> +    }
> +
> +    #[cfg(not(CONFIG_64BIT))]
> +    mod expected {
> +        pub(super) const PTR_VALUE: usize = 0xdeadbeef;
> +        pub(super) const HASHED_PREFIX: &str = "0x";
> +        pub(super) const RAW_POINTER: &str = "0xdeadbeef";
> +        pub(super) const PADDED_RIGHT: &str = "              0xdeadbeef";
> +        pub(super) const ZERO_PADDED: &str = "0x00000000000000deadbeef";
> +        pub(super) const HASHED_PADDED_RIGHT_PREFIX: &str = "              ";
> +        pub(super) const HASHED_ZERO_PADDED_PREFIX: &str = "0x00000000000000";
> +        pub(super) const CLAMPED: &str = "0x0000000000000000000000deadbeef";
> +    }
> +
> +    #[test]
> +    fn test_ptr_formatting() -> core::result::Result<(), crate::error::Error> {
> +        let ptr = expected::PTR_VALUE as *const u8;

`core::ptr::without_provenance(..)`.

> +
> +        // SAFETY: `no_hash_pointers` is a global variable that is never concurrently modified —
> +        // KUnit tests may run at boot (before `mark_readonly()`) or manually afterwards (when the
> +        // variable is read-only). Reading is always safe.
> +        let no_hash = unsafe { bindings::no_hash_pointers };
> +
> +        if no_hash {

nit: not sure how much value does this test arm provides (turning hashing off
needs a kernel command line and print very loud warnings if actually being
used).

The hash arm should work for no_hash cases too, so this could probably just be removed.

> +            let cstr = CString::try_from_fmt(fmt!("{:p}", ptr))?;
> +            assert_eq!(cstr.to_str()?, expected::RAW_POINTER);
> +
> +            let cstr = CString::try_from_fmt(fmt!("{:>24p}", ptr))?;
> +            assert_eq!(cstr.to_str()?, expected::PADDED_RIGHT);
> +
> +            let cstr = CString::try_from_fmt(fmt!("{:024p}", ptr))?;
> +            assert_eq!(cstr.to_str()?, expected::ZERO_PADDED);
> +
> +            let cstr = CString::try_from_fmt(fmt!("{:01000p}", ptr))?;
> +            assert_eq!(cstr.to_str()?, expected::CLAMPED);
> +        } else {
> +            let cstr = CString::try_from_fmt(fmt!("{:p}", ptr))?;
> +            let formatted = cstr.to_str()?;
> +            assert!(formatted.starts_with(expected::HASHED_PREFIX));
> +            assert_ne!(formatted, expected::RAW_POINTER);
> +
> +            let cstr = CString::try_from_fmt(fmt!("{:>24p}", ptr))?;
> +            assert!(cstr
> +                .to_str()?
> +                .starts_with(expected::HASHED_PADDED_RIGHT_PREFIX));

In addition to checking the prefix only, you can also check if the output is
consistent with the first formatting.

> +
> +            let cstr = CString::try_from_fmt(fmt!("{:024p}", ptr))?;
> +            assert!(cstr
> +                .to_str()?
> +                .starts_with(expected::HASHED_ZERO_PADDED_PREFIX));
> +
> +            let cstr = CString::try_from_fmt(fmt!("{:01000p}", ptr))?;

Maybe pick a smaller number like 100? It's test code so perf don't matter, but
we don't gain anything by testing 1000?

Best,
Gary

> +            let output = cstr.to_str()?;
> +            assert!(output.starts_with("0x"));
> +            assert!(!output[2..].chars().all(|c| c == '0'));
> +        }
> +
> +        Ok(())
> +    }
> +}



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH v15 1/2] rust: fmt: fix {:p} printing stack addresses
  2026-08-10 10:48   ` Gary Guo
@ 2026-08-10 13:17     ` Ke Sun
  0 siblings, 0 replies; 6+ messages in thread
From: Ke Sun @ 2026-08-10 13:17 UTC (permalink / raw)
  To: Gary Guo, Miguel Ojeda, Boqun Feng, Björn Roy Baron,
	Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
	Danilo Krummrich
  Cc: rust-for-linux


On 8/10/26 18:48, Gary Guo wrote:
> On Mon Aug 10, 2026 at 7:35 AM BST, Ke Sun wrote:
>> The `impl_fmt_adapter_forward!` macro forwards `Pointer` for
>> `Adapter<T>` by destructuring `self` into a local `t`, causing `{:p}`
>> to print the address of that temporary stack variable rather than the
>> actual pointer.
>>
>> Remove `Pointer` from the macro and provide a manual impl for
>> `Adapter<&T>` that passes `self.0` directly.
>>
>> Signed-off-by: Ke Sun <sunke@kylinos.cn>
> I don't think this has changed since last version. Usually you should pick up
> all tags provided for the previous series, so people know that it has been
> reviewed already and don't need to spend more time correlating it with the
> previous series.

Thanks for pointing that out — I dropped the tags. I'll use `b4 trailer` 
to check before
sending the next version.

Best regards,
Alvin

>
> (No need to resend just with tags picked up though)
>
> Best,
> Gary
>
>> ---
>>   rust/kernel/fmt.rs | 9 ++++++++-
>>   1 file changed, 8 insertions(+), 1 deletion(-)
>>
>> diff --git a/rust/kernel/fmt.rs b/rust/kernel/fmt.rs
>> index 73afbc51ba33a..cd7d9664ff5b9 100644
>> --- a/rust/kernel/fmt.rs
>> +++ b/rust/kernel/fmt.rs
>> @@ -43,7 +43,14 @@ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
>>       UpperExp,
>>       UpperHex, //
>>   };
>> -impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, Pointer, LowerExp, UpperExp);
>> +impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, LowerExp, UpperExp);
>> +
>> +impl<T: ?Sized + Pointer> Pointer for Adapter<&T> {
>> +    #[inline]
>> +    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
>> +        Pointer::fmt(self.0, f)
>> +    }
>> +}
>>   
>>   /// A copy of [`core::fmt::Display`] that allows us to implement it for foreign types.
>>   ///
>
>

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-08-10 13:17 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10  6:35 [PATCH v15 0/2] rust: Add safe pointer formatting support Ke Sun
2026-08-10  6:35 ` [PATCH v15 1/2] rust: fmt: fix {:p} printing stack addresses Ke Sun
2026-08-10 10:48   ` Gary Guo
2026-08-10 13:17     ` Ke Sun
2026-08-10  6:35 ` [PATCH v15 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks Ke Sun
2026-08-10 10:59   ` Gary Guo

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox