Re: [PATCH RESEND v13 2/2] rust: fmt: route {:p} through HashedPtr to prevent address leaks
Ke Sun <[email protected]>
| Newsgroups | org.kernel.vger.rust-for-linux |
|---|---|
| Message-ID | <[email protected]> |
On 8/6/26 22:41, Gary Guo wrote:
> On Mon Jul 6, 2026 at 6:18 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 <[email protected]>
>> ---
>> 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.
> `%#p` should do the trick?
>
>> + 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() {
> 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.
>
>> + 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)
> This could just be
>
> 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
>
> by making use of `*mut T` -> `*const T` coercion. This would avoid doing
> multiple indirection. Same for all other impls below.
>
> Best,
> Gary
>
>> + }
>> +}
>> +
>> +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},
>> );
>>