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 RESEND v13 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks
Date: Mon, 06 Jul 2026 13:18:44 +0800 [thread overview]
Message-ID: <20260706-hashedptr-v13-2-377a07f2f78d@kylinos.cn> (raw)
In-Reply-To: <20260706-hashedptr-v13-0-377a07f2f78d@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 | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 164 insertions(+), 2 deletions(-)
diff --git a/rust/kernel/fmt.rs b/rust/kernel/fmt.rs
index cd7d9664ff5b9..3d154dad06f64 100644
--- a/rust/kernel/fmt.rs
+++ b/rust/kernel/fmt.rs
@@ -39,13 +39,106 @@ 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];
+
+ // SAFETY: `buf` is a valid, writable buffer of 32 bytes, sufficient for all architectures
+ // (max 19 bytes for 64-bit). The format string `c"0x%p"` is null-terminated and `%p`
+ // matches the pointer argument.
+ let len = unsafe {
+ crate::bindings::scnprintf(
+ buf.as_mut_ptr().cast(),
+ buf.len(),
+ // Rust's `{:p}` includes a "0x" prefix, the kernel's `%p` does not.
+ c"0x%p".as_char_ptr(),
+ self.0.cast::<core::ffi::c_void>(),
+ )
+ };
+
+ // SAFETY: "0x%p" produces only ASCII, which is valid UTF-8.
+ let hashed_str = unsafe { core::str::from_utf8_unchecked(&buf[..len as usize]) };
+
+ // Handle `{:0width$p}`: insert zeros after "0x" prefix.
+ if f.sign_aware_zero_pad() {
+ if let Some(width) = f.width() {
+ if hashed_str.len() < width && hashed_str.starts_with("0x") {
+ return write!(f, "0x{:0>width$}", &hashed_str[2..], width = width - 2);
+ }
+ }
+ }
+
+ // 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 {
+ #[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 {
+ <*const T as Pointer>::fmt(&(*self).cast_const(), f)
+ }
+}
+
+impl<T: ?Sized> Pointer for &T {
+ #[inline]
+ 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 {
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+ <*const T as Pointer>::fmt(&core::ptr::from_ref(*self), f)
+ }
+}
+
+impl<T: ?Sized> Pointer for NonNull<T> {
+ #[inline]
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
+ <*const T as Pointer>::fmt(&self.as_ptr().cast_const(), 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 +205,72 @@ 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";
+ }
+
+ #[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";
+ }
+
+ #[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);
+ } 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));
+ }
+
+ Ok(())
+ }
+}
--
2.43.0
prev parent reply other threads:[~2026-07-06 5:19 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-06 5:18 [PATCH RESEND v13 0/2] rust: Add safe pointer formatting support Ke Sun
2026-07-06 5:18 ` [PATCH RESEND v13 1/2] rust: fmt: fix {:p} printing stack addresses Ke Sun
2026-07-06 5:18 ` Ke Sun [this message]
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=20260706-hashedptr-v13-2-377a07f2f78d@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