rust: Add safe pointer formatting support
v13 cover rust: Add safe pointer formatting support
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 v14:
- EDITME: describe what is new in this series revision.
- EDITME: use bulletpoints and terse descriptions.
- Link to v13: https://lore.kernel.org/r/[email protected]
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/[email protected]
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/[email protected]
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/[email protected]
Changes in v10:
- Merge all patches into a single patch
- Improve kernel::fmt::Pointer trait implementation
Link to v9: https://lore.kernel.org/r/[email protected]
Changes in v9: https://lore.kernel.org/r/[email protected]
- 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/[email protected]
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/[email protected]
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/[email protected]
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/[email protected]
Changes in v5: https://lore.kernel.org/r/[email protected]
- 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/[email protected]
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/[email protected]
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/[email protected]
Changes in v2:
- Disabled {:p} raw pointer printing by default to prevent accidental information leaks
- Link to v1: https://lore.kernel.org/r/[email protected]
---
Ke Sun (2):
rust: fmt: fix {:p} printing stack addresses
rust: fmt: route {:p} through HashedPtr to prevent address leaks
rust/kernel/fmt.rs | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 171 insertions(+), 2 deletions(-)
---
base-commit: 728e68a889bcf257b1e67298b12c360e5c3a13e0
change-id: 20260512-hashedptr-22469b930113
Best regards,
Signature · 1 line
Ke Sun <[email protected]>
PATCH RESEND v13 1/2 rust: fmt: fix {:p} printing stack addresses
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.
--- rust/kernel/fmt.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-)
rust/kernel/fmt.rs +8 -1
@@ -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. ///
Signature · 1 line
2.43.0
Re: [PATCH RESEND v13 1/2] rust: fmt: fix {:p} printing stack addresses
Quoted 9 lines · Ke Sun
The
impl_fmt_adapter_forward!macro forwardsPointerforAdapter<T>by destructuringselfinto a localt, causing{:p}to print the address of that temporary stack variable rather than the actual pointer.Remove
Pointerfrom the macro and provide a manual impl forAdapter<&T>that passesself.0directly.Signed-off-by Ke Sun <[email protected]>
Re: [PATCH RESEND v13 1/2] rust: fmt: fix {:p} printing stack addresses
In reply to Ke SunQuoted 9 lines · Ke Sun
The
impl_fmt_adapter_forward!macro forwardsPointerforAdapter<T>by destructuringselfinto a localt, causing{:p}to print the address of that temporary stack variable rather than the actual pointer.Remove
Pointerfrom the macro and provide a manual impl forAdapter<&T>that passesself.0directly.Signed-off-by Ke Sun <[email protected]>
Quoted 3 lines
--- rust/kernel/fmt.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-)
PATCH RESEND v13 2/2 rust: fmt: route {:p} through HashedPtr to prevent address leaks
In reply to Ke SunDefine 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.
--- rust/kernel/fmt.rs | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 164 insertions(+), 2 deletions(-)
rust/kernel/fmt.rs +164 -2
@@ -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[..lenas 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 (()) + } +}
Signature · 1 line
2.43.0
Alice Ryhl
reviewerRe: [PATCH RESEND v13 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks
Quoted 5 lines · Ke Sun
Define a custom
kernel::fmt::Pointertrait andHashedPtrwrapper so that{:p}formatting uses the kernel's%phashed format instead of printing raw pointer values, preventing kernel address space leaks.Signed-off-by Ke Sun <[email protected]>
Overall looks good to me, but one thing:
Quoted 8 lines
rust/kernel/fmt.rs +8
-0+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`
rust/kernel/fmt.rs fn fmt(&self, f: &mut Formatter<'_>) -> Result { · line 84
Quoted 10 lines
rust/kernel/fmt.rs +10
-0+// 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 >(), + ) + };
When given a null pointer, this will print 0x(null), which seems a bit weird. It may also print 0x(ptrval) or 0x(____ptrval____) during early boot.
It seems like it'd be nice to special-case these to provide better output in those cases.
Alice
Link Mauve
Tested-byRe: [PATCH RESEND v13 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks
Quoted 23 lines · Alice Ryhl
Quoted 5 lines · Ke Sun
Define a custom
kernel::fmt::Pointertrait andHashedPtrwrapper so that{:p}formatting uses the kernel's%phashed format instead of printing raw pointer values, preventing kernel address space leaks.Signed-off-by Ke Sun <[email protected]>Overall looks good to me, but one thing:
Quoted 14 lines
rust/kernel/fmt.rs +14
-0+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.
Quoted 8 lines
rust/kernel/fmt.rs fn fmt(&self, f: &mut Formatter<'_>) -> Result { · line 84
Quoted 4 lines
rust/kernel/fmt.rs +4
-0+ c"0x%p".as_char_ptr(), + self.0.cast::<core::ffi::c_void>(), + ) + };When given a null pointer, this will print 0x(null), which seems a bit weird. It may also print 0x(ptrval) or 0x(____ptrval____) during early boot.
I’ve also seen a bunch of 0x(ptrval) during testing.
This series resolves a mystery where I thought I was crazy since addresses of pointers and references were never what I thought they were, and instead were always on the stack, thanks a lot for resolving this!
Quoted 6 lines
It seems like it'd be nice to special-case these to provide better output in those cases.
Alice
Signature · 1 line
Link Mauve
Re: [PATCH RESEND v13 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks
Quoted 30 lines · Link Mauve
Quoted 21 lines · Alice Ryhl
Quoted 5 lines · Ke Sun
Define a custom
kernel::fmt::Pointertrait andHashedPtrwrapper so that{:p}formatting uses the kernel's%phashed format instead of printing raw pointer values, preventing kernel address space leaks.Signed-off-by Ke Sun <[email protected]>Overall looks good to me, but one thing:
Quoted 13 lines
rust/kernel/fmt.rs +13
-0+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(),Quoted 8 lines
rust/kernel/fmt.rs fn fmt(&self, f: &mut Formatter<'_>) -> Result { · line 84
Quoted 5 lines
rust/kernel/fmt.rs +5
-0+// 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>(), + ) + };When given a null pointer, this will print 0x(null), which seems a bit weird. It may also print 0x(ptrval) or 0x(____ptrval____) during early boot.
Quoted 1 line
I’ve also seen a bunch of 0x(ptrval) during testing.
Same 0x(ptrval) issue — also addressed in v14.
Quoted 5 lines
This series resolves a mystery where I thought I was crazy since addresses of pointers and references were never what I thought they were, and instead were always on the stack, thanks a lot for resolving this!
I was just as confused — spent quite a while debugging before realizing {:p} was the culprit. I'll send v14 shortly, would be great if you could give it another spin.
Quoted 2 lines
Tested-by Link Mauve <[email protected]>
Thanks for testing!
Best regards,
Alvin
Quoted 6 lines
Quoted 6 lines
It seems like it'd be nice to special-case these to provide better output in those cases.
Alice
Re: [PATCH RESEND v13 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks
In reply to Alice RyhlQuoted 21 lines · Alice Ryhl
Quoted 5 lines · Ke Sun
Define a custom
kernel::fmt::Pointertrait andHashedPtrwrapper so that{:p}formatting uses the kernel's%phashed format instead of printing raw pointer values, preventing kernel address space leaks.Signed-off-by Ke Sun <[email protected]>Overall looks good to me, but one thing:
Quoted 13 lines
rust/kernel/fmt.rs +13
-0+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(),
Quoted 8 lines
rust/kernel/fmt.rs fn fmt(&self, f: &mut Formatter<'_>) -> Result { · line 84
Quoted 5 lines
rust/kernel/fmt.rs +5
-0+// 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>(), + ) + };When given a null pointer, this will print 0x(null), which seems a bit weird. It may also print 0x(ptrval) or 0x(____ptrval____) during early boot.
Thanks to Gary's suggestion, %#p in v14 adds the "0x" prefix for normal pointers but leaves placeholder tokens like "(____ptrval____)" untouched.
Best regards,
Alvin
Quoted 6 lines
It seems like it'd be nice to special-case these to provide better output in those cases.
Alice
Gary Guo
reviewerRe: [PATCH RESEND v13 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks
In reply to Ke SunQuoted 48 lines · Ke Sun
Define a custom
kernel::fmt::Pointertrait andHashedPtrwrapper so that{:p}formatting uses the kernel's%phashed format instead of printing raw pointer values, preventing kernel address space leaks.Signed-off-by Ke Sun <[email protected]>--- rust/kernel/fmt.rs | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 164 insertions(+), 2 deletions(-)rust/kernel/fmt.rs +24
-2@@ -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 _; +
rust/kernel/fmt.rs fn fmt(&self, f: &mut Formatter<'_>) -> Result { · line 80
Quoted 10 lines
rust/kernel/fmt.rs +10
-0+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.
%#p should do the trick?
rust/kernel/fmt.rs fn fmt(&self, f: &mut Formatter<'_>) -> Result { · line 90
Quoted 10 lines
rust/kernel/fmt.rs +10
-0+ 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[..lenas usize ]) }; + +// Handle `{:0width$p}`: insert zeros after "0x" prefix. +if f.sign_aware_zero_pad () {
zero pad can be implemented by %0*p.
Quoted 15 lines
rust/kernel/fmt.rs +15
-0+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 {
rust/kernel/fmt.rs fn fmt(&self, f: &mut Formatter<'_>) -> Result { · line 115
Quoted 10 lines
rust/kernel/fmt.rs +10
-0+#[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)
This could just be
Pointer::fmt(&HashedPtr(*self), f)
by making use of *mut T -> *const T coercion. This would avoid doing multiple indirection. Same for all other impls below.
Best,
Gary
Quoted 34 lines
rust/kernel/fmt.rs +26
-0+ } +} + +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}, );
Re: [PATCH RESEND v13 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks
Quoted 61 lines · Gary Guo
Quoted 48 lines · Ke Sun
Define a custom
kernel::fmt::Pointertrait andHashedPtrwrapper so that{:p}formatting uses the kernel's%phashed format instead of printing raw pointer values, preventing kernel address space leaks.Signed-off-by Ke Sun <[email protected]>--- rust/kernel/fmt.rs | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 164 insertions(+), 2 deletions(-)rust/kernel/fmt.rs +24
-2@@ -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 _; +rust/kernel/fmt.rs fn fmt(&self, f: &mut Formatter<'_>) -> Result { · line 80
Quoted 10 lines
rust/kernel/fmt.rs +10
-0+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.
%#pshould do the trick?
Quoted 11 lines
rust/kernel/fmt.rs fn fmt(&self, f: &mut Formatter<'_>) -> Result { · line 90
Quoted 10 lines
rust/kernel/fmt.rs +10
-0+ 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[..lenas usize ]) }; + +// Handle `{:0width$p}`: insert zeros after "0x" prefix. +if f.sign_aware_zero_pad () {zero pad can be implemented by
%0*p.
%#0*p handles both the "0x" prefix and zero-padding in one pass, and also fixes the "0x(____ptrval____)" issue Alice reported.
Quoted 28 lines
Quoted 16 lines
rust/kernel/fmt.rs +15
-0+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 {rust/kernel/fmt.rs fn fmt(&self, f: &mut Formatter<'_>) -> Result { · line 115
Quoted 10 lines
rust/kernel/fmt.rs +10
-0+#[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)This could just be
Quoted 1 line
Pointer::fmt(&HashedPtr(*self), f)
All the impls are #[inline], so the compiler will eliminate the intermediate calls anyway — but the direct style is cleaner. I'll switch the other impls to the HashedPtr(...) form in v14.
Best regards,
Alvin
Quoted 41 lines
by making use of
*mut T->*const Tcoercion. This would avoid doing multiple indirection. Same for all other impls below.Best,
GaryQuoted 34 lines
rust/kernel/fmt.rs +26
-0+ } +} + +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}, );
Alice Ryhl
reviewerQuoted 6 lines · Ke Sun
This series fixes two issues with {:p} pointer formatting:
- Theimpl_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
I got:rust/kernel.o: error: objtool: _RNvXs4_NtCskVLJmUDtH0q_6kernel3fmtINtB5_7AdapterRPhENtNtCsfr3MPOfBGpN_4core3fmt7Pointer3fmtB7_() falls through to next function _RNvXs4_NtCskVLJmUDtH0q_6kernel3strShINtNtCsfr3MPOfBGpN_4core7convert5AsRefNtB5_4BStrE6as_ref()
when I built this on x86_64. Miguel, are we missing a function in the list of noreturn functions?
Alice
Quoted 6 lines · Alice Ryhl
I got:
rust/kernel.o: error: objtool:_RNvXs4_NtCskVLJmUDtH0q_6kernel3fmtINtB5_7AdapterRPhENtNtCsfr3MPOfBGpN_4core3fmt7Pointer3fmtB7_()falls through to next function_RNvXs4_NtCskVLJmUDtH0q_6kernel3strShINtNtCsfr3MPOfBGpN_4core7convert5AsRefNtB5_4BStrE6as_ref()when I built this on
x86_64. Miguel, are we missing a function in the list of noreturn functions?
Yeah, I can reproduce it and yeah, it is one more we need.
I will send a patch to be applied with this series since we only add the ones we need.
Cheers,
Miguel
Quoted 3 lines · Miguel Ojeda
I will send a patch to be applied with this series since we only add the ones we need.
https://lore.kernel.org/rust-for-linux/[email protected]/
Cheers,
Miguel