Rust for Linux List
 help / color / mirror / Atom feed
From: Ke Sun <sunke@kylinos.cn>
To: "Miguel Ojeda" <ojeda@kernel.org>,
	"Boqun Feng" <boqun@kernel.org>, "Gary Guo" <gary@garyguo.net>,
	"Björn Roy Baron" <bjorn3_gh@protonmail.com>,
	"Benno Lossin" <lossin@kernel.org>,
	"Andreas Hindborg" <a.hindborg@kernel.org>,
	"Alice Ryhl" <aliceryhl@google.com>,
	"Trevor Gross" <tmgross@umich.edu>,
	"Danilo Krummrich" <dakr@kernel.org>
Cc: rust-for-linux@vger.kernel.org, Ke Sun <sunke@kylinos.cn>
Subject: [PATCH v12 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks
Date: Tue, 12 May 2026 16:55:28 +0800	[thread overview]
Message-ID: <20260512-hashedptr-v12-2-61d5c7786889@kylinos.cn> (raw)
In-Reply-To: <20260512-hashedptr-v12-0-61d5c7786889@kylinos.cn>

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 | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 170 insertions(+), 2 deletions(-)

diff --git a/rust/kernel/fmt.rs b/rust/kernel/fmt.rs
index aba9f3dbd3175..19f422ff7f0ed 100644
--- a/rust/kernel/fmt.rs
+++ b/rust/kernel/fmt.rs
@@ -27,10 +27,95 @@ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
     };
 }
 
-use core::fmt::{Binary, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex};
+use core::fmt::{Binary, LowerExp, LowerHex, Octal, UpperExp, UpperHex};
 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];
+
+        // SAFETY:
+        // - `buf` is a valid writable buffer. 32 bytes is sufficient for all architectures:
+        //   64-bit: "0x" + 16 hex digits (zero-padded by `pointer_string`) = 18 bytes;
+        //   32-bit: "0x" + 8 hex digits (zero-padded by `pointer_string`) = 10 bytes.
+        // - The format string is valid and `%p` expects a pointer argument
+        // - `scnprintf` is safe to call with proper arguments
+        //
+        // Note: "0x" is added because Rust's `{:p}` includes a "0x" prefix,
+        // but the kernel's `%p` does not.
+        let len = unsafe {
+            crate::bindings::scnprintf(
+                buf.as_mut_ptr().cast(),
+                buf.len(),
+                c"0x%p".as_char_ptr(),
+                self.0.cast::<core::ffi::c_void>(),
+            )
+        };
+
+        // SAFETY:
+        // - `buf` is a valid buffer (see above for size justification)
+        // - `scnprintf` returns the number of characters written (excluding null terminator),
+        //   which is always non-negative (see `vsnprintf` and `vscnprintf` in lib/vsprintf.c)
+        // - `len` is bounded by `scnprintf` to at most `buf.len() - 1`
+        // - The format string "0x%p" produces ASCII hex digits and "0x" prefix,
+        //   which are valid UTF-8 (ASCII is a strict subset of UTF-8)
+        let hashed_str = unsafe { core::str::from_utf8_unchecked(&buf[..len as usize]) };
+
+        // Use `f.pad` to handle width/alignment formatting.
+        f.pad(hashed_str)
+    }
+}
+
+// Raw pointers are formatted via `HashedPtr` (kernel `%p`: hashed by default, plain with
+// `no_hash_pointers`).
+impl<T: ?Sized> Pointer for *const T {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        Pointer::fmt(&HashedPtr(*self), f)
+    }
+}
+
+impl<T: ?Sized> Pointer for *mut T {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        <*const T as Pointer>::fmt(&(*self).cast_const(), f)
+    }
+}
+
+impl<T: ?Sized> Pointer for &T {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        <*const T as Pointer>::fmt(&core::ptr::from_ref(*self), f)
+    }
+}
+
+impl<T: ?Sized> Pointer for &mut T {
+    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+        <*const T as Pointer>::fmt(&core::ptr::from_ref(*self), f)
+    }
+}
+
+// `Adapter<&T>` bridges our `Pointer` trait to `core::fmt::Pointer`
+impl<T: Pointer> core::fmt::Pointer for Adapter<&T> {
     fn fmt(&self, f: &mut Formatter<'_>) -> Result {
         Pointer::fmt(self.0, f)
     }
@@ -96,3 +181,86 @@ 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, //
+    };
+
+    /// A RAII guard that temporarily sets `no_hash_pointers` and restores it on drop.
+    ///
+    /// # Safety
+    ///
+    /// `no_hash_pointers` is a `__ro_after_init` global variable. KUnit tests run during
+    /// `kernel_init_freeable()` (init/main.c), before `mark_readonly()` makes the
+    /// `.data..ro_after_init` section read-only. At this point there are no concurrent
+    /// readers or writers, so it is safe to modify.
+    struct NoHashPointersGuard {
+        original: bool,
+    }
+
+    impl NoHashPointersGuard {
+        /// Sets `no_hash_pointers` to `value` and returns a guard that will restore
+        /// the original value on drop.
+        ///
+        /// # Safety
+        ///
+        /// See struct-level documentation.
+        unsafe fn new(value: bool) -> Self {
+            // SAFETY: See `NoHashPointersGuard` safety documentation.
+            let original = unsafe { bindings::no_hash_pointers };
+            // SAFETY: See `NoHashPointersGuard` safety documentation.
+            unsafe { bindings::no_hash_pointers = value };
+            Self { original }
+        }
+    }
+
+    impl Drop for NoHashPointersGuard {
+        fn drop(&mut self) {
+            // SAFETY: See `NoHashPointersGuard` safety documentation.
+            unsafe { bindings::no_hash_pointers = self.original };
+        }
+    }
+
+    #[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_LEFT: &str = "0xffffffffdeadbeef      ";
+    }
+
+    #[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_LEFT: &str = "0xdeadbeef              ";
+    }
+
+    #[test]
+    fn test_ptr_formatting() -> core::result::Result<(), crate::error::Error> {
+        let ptr = expected::PTR_VALUE as *const u8;
+
+        // SAFETY: See `NoHashPointersGuard` safety documentation.
+        let _hashed = unsafe { NoHashPointersGuard::new(false) };
+        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);
+        drop(_hashed);
+
+        // SAFETY: See `NoHashPointersGuard` safety documentation.
+        let _guard = unsafe { NoHashPointersGuard::new(true) };
+        let cstr = CString::try_from_fmt(fmt!("{:p}", ptr))?;
+        assert_eq!(cstr.to_str()?, expected::RAW_POINTER);
+
+        let cstr = CString::try_from_fmt(fmt!("{:024p}", ptr))?;
+        assert_eq!(cstr.to_str()?, expected::PADDED_LEFT);
+
+        Ok(())
+    }
+}

-- 
2.43.0


  parent reply	other threads:[~2026-05-12  8:55 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-12  8:55 [PATCH v12 0/2] rust: Add safe pointer formatting support Ke Sun
2026-05-12  8:55 ` [PATCH v12 1/2] rust: fmt: fix {:p} printing stack addresses Ke Sun
2026-05-12  8:55 ` Ke Sun [this message]
2026-05-12  9:26   ` [PATCH v12 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks Onur Özkan

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260512-hashedptr-v12-2-61d5c7786889@kylinos.cn \
    --to=sunke@kylinos.cn \
    --cc=a.hindborg@kernel.org \
    --cc=aliceryhl@google.com \
    --cc=bjorn3_gh@protonmail.com \
    --cc=boqun@kernel.org \
    --cc=dakr@kernel.org \
    --cc=gary@garyguo.net \
    --cc=lossin@kernel.org \
    --cc=ojeda@kernel.org \
    --cc=rust-for-linux@vger.kernel.org \
    --cc=tmgross@umich.edu \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox