* [PATCH] rust: fmt: drop the "0x" prefix from {:p} to match %p
@ 2026-10-06 18:38 Carlos Llamas
2026-10-06 21:09 ` Gary Guo
0 siblings, 1 reply; 3+ messages in thread
From: Carlos Llamas @ 2026-10-06 18:38 UTC (permalink / raw)
To: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Ke Sun, Carlos Llamas,
Dirk Behme
Cc: kernel-team, linux-kernel, stable, open list:RUST
Commit 643a7c306b8c ("rust: fmt: route {:p} through HashedPtr to prevent
address leaks") made {:p} go through the kernel's %p so that pointers
are hashed by default. However, it formats them with %#0*p, where the
'#' flag prepends a '0x' prefix that the plain %p does not produce in C.
Thus the same pointer is printed differently from C and from Rust:
C %p 00000000e8efa736
Rust {:p} 0x00000000e8efa736
The prefix is a leftover from 'core::fmt::Pointer', which always adds
one. However, doing so deviates from the %p format documented in
Documentation/core-api/printk-formats.rst and makes it harder to
correlate the same pointer across C and Rust messages.
Format the pointer with '%0*p' instead and adjust the default field
width accordingly, so that {:p} produces exactly the same output as
plain %p which is zero-padded to the width of a pointer and without any
prefix, both for hashed pointers and for real addresses under
'no_hash_pointers'. Update the KUnit test expectations to match.
Note this patch focuses only on fixing the '{:p}' prefix to match %p.
The '#' flag remains ignored as before, so the '{:#p}' equivalent to
%#p should be sent as a follow up patch.
Fixes: 643a7c306b8c ("rust: fmt: route {:p} through HashedPtr to prevent address leaks")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Carlos Llamas <cmllamas@google.com>
---
rust/kernel/fmt.rs | 48 ++++++++++++++++++++++++----------------------
1 file changed, 25 insertions(+), 23 deletions(-)
diff --git a/rust/kernel/fmt.rs b/rust/kernel/fmt.rs
index 29582b053ab1..b35858a28d38 100644
--- a/rust/kernel/fmt.rs
+++ b/rust/kernel/fmt.rs
@@ -64,6 +64,9 @@ pub trait Pointer {
/// 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).
+///
+/// The output matches that of `%p` in C: zero-padded to the width of a pointer and, unlike
+/// [`core::fmt::Pointer`], without a `0x` prefix.
pub struct HashedPtr<T: ?Sized>(pub *const T);
impl<T: ?Sized> Pointer for HashedPtr<T> {
@@ -72,33 +75,32 @@ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
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;
+ // Like `%p`, zero-pad to the width of a pointer and do not add a `0x` prefix.
+ let default_width = (2 * size_of::<usize>()) 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).
+ // all architectures (max 17 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(),
+ c"%0*p".as_char_ptr(),
width,
self.0.cast::<c_void>(),
)
};
- // SAFETY: `%#0*p` produces only ASCII, which is valid UTF-8.
+ // 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() {
- // `scnprintf` already applied the width and zero-padding via `%#0*p`.
+ // `scnprintf` already applied the width and zero-padding via `%0*p`.
f.write_str(s)
} else {
f.pad(s)
@@ -224,26 +226,26 @@ mod tests {
mod expected {
pub(super) const PTR_VALUE: usize = 0xffffffffdeadbeef;
pub(super) const PTR_VAL_NO_CRNG: &str = "(____ptrval____)";
- 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";
+ pub(super) const HASHED_PREFIX: &str = "00000000";
+ pub(super) const RAW_POINTER: &str = "ffffffffdeadbeef";
+ pub(super) const PADDED_RIGHT: &str = " ffffffffdeadbeef";
+ pub(super) const ZERO_PADDED: &str = "00000000ffffffffdeadbeef";
+ pub(super) const HASHED_PADDED_RIGHT_PREFIX: &str = " ";
+ pub(super) const HASHED_ZERO_PADDED_PREFIX: &str = "0000000000000000";
+ pub(super) const CLAMPED: &str = "000000000000000ffffffffdeadbeef";
}
#[cfg(not(CONFIG_64BIT))]
mod expected {
pub(super) const PTR_VALUE: usize = 0xdeadbeef;
pub(super) const PTR_VAL_NO_CRNG: &str = "(ptrval)";
- 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";
+ pub(super) const HASHED_PREFIX: &str = "";
+ pub(super) const RAW_POINTER: &str = "deadbeef";
+ pub(super) const PADDED_RIGHT: &str = " deadbeef";
+ pub(super) const ZERO_PADDED: &str = "0000000000000000deadbeef";
+ pub(super) const HASHED_PADDED_RIGHT_PREFIX: &str = " ";
+ pub(super) const HASHED_ZERO_PADDED_PREFIX: &str = "0000000000000000";
+ pub(super) const CLAMPED: &str = "00000000000000000000000deadbeef";
}
#[test]
@@ -289,8 +291,8 @@ fn test_ptr_formatting() -> core::result::Result<(), crate::error::Error> {
let cstr = CString::try_from_fmt(fmt!("{:0100p}", ptr))?;
let output = cstr.to_str()?;
- assert!(output.starts_with("0x"));
- assert!(!output[2..].chars().all(|c| c == '0'));
+ assert_eq!(output.len(), expected::CLAMPED.len());
+ assert!(!output.chars().all(|c| c == '0'));
}
Ok(())
--
2.56.0.385.gd3acb90ef8-goog
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] rust: fmt: drop the "0x" prefix from {:p} to match %p
2026-10-06 18:38 [PATCH] rust: fmt: drop the "0x" prefix from {:p} to match %p Carlos Llamas
@ 2026-10-06 21:09 ` Gary Guo
2026-10-06 21:44 ` Carlos Llamas
0 siblings, 1 reply; 3+ messages in thread
From: Gary Guo @ 2026-10-06 21:09 UTC (permalink / raw)
To: Carlos Llamas, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Ke Sun, Dirk Behme
Cc: kernel-team, linux-kernel, stable, open list:RUST
On Tue Oct 6, 2026 at 8:38 PM CEST, Carlos Llamas wrote:
> Commit 643a7c306b8c ("rust: fmt: route {:p} through HashedPtr to prevent
> address leaks") made {:p} go through the kernel's %p so that pointers
> are hashed by default. However, it formats them with %#0*p, where the
> '#' flag prepends a '0x' prefix that the plain %p does not produce in C.
> Thus the same pointer is printed differently from C and from Rust:
>
> C %p 00000000e8efa736
> Rust {:p} 0x00000000e8efa736
>
> The prefix is a leftover from 'core::fmt::Pointer', which always adds
> one. However, doing so deviates from the %p format documented in
> Documentation/core-api/printk-formats.rst and makes it harder to
> correlate the same pointer across C and Rust messages.
>
> Format the pointer with '%0*p' instead and adjust the default field
> width accordingly, so that {:p} produces exactly the same output as
> plain %p which is zero-padded to the width of a pointer and without any
> prefix, both for hashed pointers and for real addresses under
> 'no_hash_pointers'. Update the KUnit test expectations to match.
>
> Note this patch focuses only on fixing the '{:p}' prefix to match %p.
> The '#' flag remains ignored as before, so the '{:#p}' equivalent to
> %#p should be sent as a follow up patch.
>
> Fixes: 643a7c306b8c ("rust: fmt: route {:p} through HashedPtr to prevent address leaks")
Rust libcore's `:p` also adds the 0x, so I don't consider this a fix.
This will be an intentional behaviour divergence from Rust format specifier, so
this fact need to be mentioned explicitly.
Best,
Gary
> Cc: stable@vger.kernel.org
> Assisted-by: LLM
> Signed-off-by: Carlos Llamas <cmllamas@google.com>
> ---
> rust/kernel/fmt.rs | 48 ++++++++++++++++++++++++----------------------
> 1 file changed, 25 insertions(+), 23 deletions(-)
>
> diff --git a/rust/kernel/fmt.rs b/rust/kernel/fmt.rs
> index 29582b053ab1..b35858a28d38 100644
> --- a/rust/kernel/fmt.rs
> +++ b/rust/kernel/fmt.rs
> @@ -64,6 +64,9 @@ pub trait Pointer {
> /// 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).
> +///
> +/// The output matches that of `%p` in C: zero-padded to the width of a pointer and, unlike
> +/// [`core::fmt::Pointer`], without a `0x` prefix.
> pub struct HashedPtr<T: ?Sized>(pub *const T);
>
> impl<T: ?Sized> Pointer for HashedPtr<T> {
> @@ -72,33 +75,32 @@ fn fmt(&self, f: &mut Formatter<'_>) -> Result {
>
> 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;
> + // Like `%p`, zero-pad to the width of a pointer and do not add a `0x` prefix.
> + let default_width = (2 * size_of::<usize>()) 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).
> + // all architectures (max 17 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(),
> + c"%0*p".as_char_ptr(),
> width,
> self.0.cast::<c_void>(),
> )
> };
>
> - // SAFETY: `%#0*p` produces only ASCII, which is valid UTF-8.
> + // 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() {
> - // `scnprintf` already applied the width and zero-padding via `%#0*p`.
> + // `scnprintf` already applied the width and zero-padding via `%0*p`.
> f.write_str(s)
> } else {
> f.pad(s)
> @@ -224,26 +226,26 @@ mod tests {
> mod expected {
> pub(super) const PTR_VALUE: usize = 0xffffffffdeadbeef;
> pub(super) const PTR_VAL_NO_CRNG: &str = "(____ptrval____)";
> - 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";
> + pub(super) const HASHED_PREFIX: &str = "00000000";
> + pub(super) const RAW_POINTER: &str = "ffffffffdeadbeef";
> + pub(super) const PADDED_RIGHT: &str = " ffffffffdeadbeef";
> + pub(super) const ZERO_PADDED: &str = "00000000ffffffffdeadbeef";
> + pub(super) const HASHED_PADDED_RIGHT_PREFIX: &str = " ";
> + pub(super) const HASHED_ZERO_PADDED_PREFIX: &str = "0000000000000000";
> + pub(super) const CLAMPED: &str = "000000000000000ffffffffdeadbeef";
> }
>
> #[cfg(not(CONFIG_64BIT))]
> mod expected {
> pub(super) const PTR_VALUE: usize = 0xdeadbeef;
> pub(super) const PTR_VAL_NO_CRNG: &str = "(ptrval)";
> - 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";
> + pub(super) const HASHED_PREFIX: &str = "";
> + pub(super) const RAW_POINTER: &str = "deadbeef";
> + pub(super) const PADDED_RIGHT: &str = " deadbeef";
> + pub(super) const ZERO_PADDED: &str = "0000000000000000deadbeef";
> + pub(super) const HASHED_PADDED_RIGHT_PREFIX: &str = " ";
> + pub(super) const HASHED_ZERO_PADDED_PREFIX: &str = "0000000000000000";
> + pub(super) const CLAMPED: &str = "00000000000000000000000deadbeef";
> }
>
> #[test]
> @@ -289,8 +291,8 @@ fn test_ptr_formatting() -> core::result::Result<(), crate::error::Error> {
>
> let cstr = CString::try_from_fmt(fmt!("{:0100p}", ptr))?;
> let output = cstr.to_str()?;
> - assert!(output.starts_with("0x"));
> - assert!(!output[2..].chars().all(|c| c == '0'));
> + assert_eq!(output.len(), expected::CLAMPED.len());
> + assert!(!output.chars().all(|c| c == '0'));
> }
>
> Ok(())
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH] rust: fmt: drop the "0x" prefix from {:p} to match %p
2026-10-06 21:09 ` Gary Guo
@ 2026-10-06 21:44 ` Carlos Llamas
0 siblings, 0 replies; 3+ messages in thread
From: Carlos Llamas @ 2026-10-06 21:44 UTC (permalink / raw)
To: Gary Guo
Cc: Miguel Ojeda, Boqun Feng, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Alice Ryhl, Trevor Gross, Danilo Krummrich,
Daniel Almeida, Tamir Duberstein, Alexandre Courbot,
Onur Özkan, Ke Sun, Dirk Behme, kernel-team, linux-kernel,
stable, open list:RUST
On Tue, Oct 06, 2026 at 11:09:09PM +0200, Gary Guo wrote:
> On Tue Oct 6, 2026 at 8:38 PM CEST, Carlos Llamas wrote:
> > Commit 643a7c306b8c ("rust: fmt: route {:p} through HashedPtr to prevent
> > address leaks") made {:p} go through the kernel's %p so that pointers
> > are hashed by default. However, it formats them with %#0*p, where the
> > '#' flag prepends a '0x' prefix that the plain %p does not produce in C.
> > Thus the same pointer is printed differently from C and from Rust:
> >
> > C %p 00000000e8efa736
> > Rust {:p} 0x00000000e8efa736
> >
> > The prefix is a leftover from 'core::fmt::Pointer', which always adds
> > one. However, doing so deviates from the %p format documented in
> > Documentation/core-api/printk-formats.rst and makes it harder to
> > correlate the same pointer across C and Rust messages.
> >
> > Format the pointer with '%0*p' instead and adjust the default field
> > width accordingly, so that {:p} produces exactly the same output as
> > plain %p which is zero-padded to the width of a pointer and without any
> > prefix, both for hashed pointers and for real addresses under
> > 'no_hash_pointers'. Update the KUnit test expectations to match.
> >
> > Note this patch focuses only on fixing the '{:p}' prefix to match %p.
> > The '#' flag remains ignored as before, so the '{:#p}' equivalent to
> > %#p should be sent as a follow up patch.
> >
> > Fixes: 643a7c306b8c ("rust: fmt: route {:p} through HashedPtr to prevent address leaks")
>
> Rust libcore's `:p` also adds the 0x, so I don't consider this a fix.
> This will be an intentional behaviour divergence from Rust format specifier, so
> this fact need to be mentioned explicitly.
I see. I was afraid this was going to be the case. Then, I don't know if
:p in the kernel should follow %p or std:fmt. It diverges either way.
For context, I ran into this issue because log parsing broke in binder
when I started using :p due to the unexpected prefix.
I suppose one can always open code a %p equivalent as needed too but
perhaps matching the %p behavior make more sense? I dunno, wdyt?
--
Carlos Llamas
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-06 21:44 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-06 18:38 [PATCH] rust: fmt: drop the "0x" prefix from {:p} to match %p Carlos Llamas
2026-10-06 21:09 ` Gary Guo
2026-10-06 21:44 ` Carlos Llamas
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®