Skip to content

Commit 643a7c3

Browse files
alvinsun039ojeda
authored andcommitted
rust: fmt: route {:p} through HashedPtr to prevent address leaks
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> Reviewed-by: Gary Guo <gary@garyguo.net> Link: https://patch.msgid.link/20260810-hashedptr-v15-2-eafd27d36476@kylinos.cn [ Fixed KUnit failure when the CRNG is not ready. Then, as suggested, replaced the `scnprintf` comment (with v16's), changed width to 100, replaced cast with `without_provenance`. - Miguel ] Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
1 parent fb7d645 commit 643a7c3

1 file changed

Lines changed: 186 additions & 2 deletions

File tree

‎rust/kernel/fmt.rs‎

Lines changed: 186 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
//!
55
//! This module is intended to be used in place of `core::fmt` in kernel code.
66
7+
use kernel::prelude::*;
8+
79
pub use core::fmt::{
810
Arguments,
911
Debug,
@@ -39,13 +41,110 @@ use core::fmt::{
3941
LowerExp,
4042
LowerHex,
4143
Octal,
42-
Pointer,
4344
UpperExp,
4445
UpperHex, //
4546
};
47+
use core::ptr::NonNull;
4648
impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, LowerExp, UpperExp);
4749

48-
impl<T: ?Sized + Pointer> Pointer for Adapter<&T> {
50+
/// A copy of [`core::fmt::Pointer`] that allows implementing pointer formatting for foreign types.
51+
///
52+
/// Together with the [`Adapter`] type and [`fmt!`] macro, it enables raw pointer formatting to be
53+
/// intercepted and routed to [`HashedPtr`] (kernel's `%p` hashed format), preventing kernel address
54+
/// leaks.
55+
///
56+
/// [`fmt!`]: crate::prelude::fmt!
57+
pub trait Pointer {
58+
/// Same as [`core::fmt::Pointer::fmt`].
59+
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
60+
}
61+
62+
/// A wrapper for pointers that formats them using kernel's `%p` format specifier.
63+
///
64+
/// By default, `%p` prints a hashed representation of the pointer address to prevent kernel address
65+
/// leaks. When the `no_hash_pointers` kernel command-line parameter is enabled, the real address is
66+
/// printed instead (for debugging purposes).
67+
pub struct HashedPtr<T: ?Sized>(pub *const T);
68+
69+
impl<T: ?Sized> Pointer for HashedPtr<T> {
70+
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
71+
use crate::str::CStrExt as _;
72+
73+
let mut buf = [0u8; 32];
74+
75+
// Use `%#0*p` for the `0x` prefix and zero-padding; `+2` compensates for
76+
// the prefix counting toward the field width.
77+
let default_width = (2 * size_of::<usize>() + 2) as c_int;
78+
let width = match (f.sign_aware_zero_pad(), f.width()) {
79+
(true, Some(w)) if w > 0 => w.min(buf.len() - 1) as c_int,
80+
_ => default_width,
81+
};
82+
83+
// SAFETY: `buf` is a valid, writable 32-byte buffer, sufficient for
84+
// all architectures (max 19 bytes for 64-bit under the default width).
85+
// The format string is null-terminated; `width` (c_int) and pointer
86+
// match the `%*` and `%p` specifiers.
87+
let len = unsafe {
88+
crate::bindings::scnprintf(
89+
buf.as_mut_ptr().cast(),
90+
buf.len(),
91+
c"%#0*p".as_char_ptr(),
92+
width,
93+
self.0.cast::<c_void>(),
94+
)
95+
};
96+
97+
// SAFETY: `%#0*p` produces only ASCII, which is valid UTF-8.
98+
let s = unsafe { core::str::from_utf8_unchecked(&buf[..len as usize]) };
99+
100+
if f.sign_aware_zero_pad() {
101+
// `scnprintf` already applied the width and zero-padding via `%#0*p`.
102+
f.write_str(s)
103+
} else {
104+
f.pad(s)
105+
}
106+
}
107+
}
108+
109+
// Raw pointers are formatted via `HashedPtr` (kernel `%p`: hashed by default, plain with
110+
// `no_hash_pointers`).
111+
impl<T: ?Sized> Pointer for *const T {
112+
#[inline]
113+
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
114+
Pointer::fmt(&HashedPtr(*self), f)
115+
}
116+
}
117+
118+
impl<T: ?Sized> Pointer for *mut T {
119+
#[inline]
120+
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
121+
Pointer::fmt(&HashedPtr(*self), f)
122+
}
123+
}
124+
125+
impl<T: ?Sized> Pointer for &T {
126+
#[inline]
127+
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
128+
Pointer::fmt(&HashedPtr(*self), f)
129+
}
130+
}
131+
132+
impl<T: ?Sized> Pointer for &mut T {
133+
#[inline]
134+
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
135+
Pointer::fmt(&HashedPtr(core::ptr::from_ref(*self)), f)
136+
}
137+
}
138+
139+
impl<T: ?Sized> Pointer for NonNull<T> {
140+
#[inline]
141+
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
142+
Pointer::fmt(&HashedPtr(self.as_ptr()), f)
143+
}
144+
}
145+
146+
// `Adapter<&T>` bridges our `Pointer` trait to `core::fmt::Pointer`
147+
impl<T: Pointer> core::fmt::Pointer for Adapter<&T> {
49148
#[inline]
50149
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
51150
Pointer::fmt(self.0, f)
@@ -112,3 +211,88 @@ impl_display_forward!(
112211
{<T: ?Sized>} crate::sync::Arc<T> {where crate::sync::Arc<T>: core::fmt::Display},
113212
{<T: ?Sized>} crate::sync::UniqueArc<T> {where crate::sync::UniqueArc<T>: core::fmt::Display},
114213
);
214+
215+
#[macros::kunit_tests(rust_kernel_fmt)]
216+
mod tests {
217+
use crate::{
218+
bindings,
219+
prelude::fmt,
220+
str::CString, //
221+
};
222+
223+
#[cfg(CONFIG_64BIT)]
224+
mod expected {
225+
pub(super) const PTR_VALUE: usize = 0xffffffffdeadbeef;
226+
pub(super) const PTR_VAL_NO_CRNG: &str = "(____ptrval____)";
227+
pub(super) const HASHED_PREFIX: &str = "0x00000000";
228+
pub(super) const RAW_POINTER: &str = "0xffffffffdeadbeef";
229+
pub(super) const PADDED_RIGHT: &str = " 0xffffffffdeadbeef";
230+
pub(super) const ZERO_PADDED: &str = "0x000000ffffffffdeadbeef";
231+
pub(super) const HASHED_PADDED_RIGHT_PREFIX: &str = " ";
232+
pub(super) const HASHED_ZERO_PADDED_PREFIX: &str = "0x00000000000000";
233+
pub(super) const CLAMPED: &str = "0x0000000000000ffffffffdeadbeef";
234+
}
235+
236+
#[cfg(not(CONFIG_64BIT))]
237+
mod expected {
238+
pub(super) const PTR_VALUE: usize = 0xdeadbeef;
239+
pub(super) const PTR_VAL_NO_CRNG: &str = "(ptrval)";
240+
pub(super) const HASHED_PREFIX: &str = "0x";
241+
pub(super) const RAW_POINTER: &str = "0xdeadbeef";
242+
pub(super) const PADDED_RIGHT: &str = " 0xdeadbeef";
243+
pub(super) const ZERO_PADDED: &str = "0x00000000000000deadbeef";
244+
pub(super) const HASHED_PADDED_RIGHT_PREFIX: &str = " ";
245+
pub(super) const HASHED_ZERO_PADDED_PREFIX: &str = "0x00000000000000";
246+
pub(super) const CLAMPED: &str = "0x0000000000000000000000deadbeef";
247+
}
248+
249+
#[test]
250+
fn test_ptr_formatting() -> core::result::Result<(), crate::error::Error> {
251+
let ptr: *const u8 = core::ptr::without_provenance(expected::PTR_VALUE);
252+
253+
// SAFETY: `no_hash_pointers` is a global variable that is never concurrently modified —
254+
// KUnit tests may run at boot (before `mark_readonly()`) or manually afterwards (when the
255+
// variable is read-only). Reading is always safe.
256+
let no_hash = unsafe { bindings::no_hash_pointers };
257+
258+
if no_hash {
259+
let cstr = CString::try_from_fmt(fmt!("{:p}", ptr))?;
260+
assert_eq!(cstr.to_str()?, expected::RAW_POINTER);
261+
262+
let cstr = CString::try_from_fmt(fmt!("{:>24p}", ptr))?;
263+
assert_eq!(cstr.to_str()?, expected::PADDED_RIGHT);
264+
265+
let cstr = CString::try_from_fmt(fmt!("{:024p}", ptr))?;
266+
assert_eq!(cstr.to_str()?, expected::ZERO_PADDED);
267+
268+
let cstr = CString::try_from_fmt(fmt!("{:0100p}", ptr))?;
269+
assert_eq!(cstr.to_str()?, expected::CLAMPED);
270+
} else {
271+
let cstr = CString::try_from_fmt(fmt!("{:p}", ptr))?;
272+
let formatted = cstr.to_str()?;
273+
// If the RNG is not yet ready, `%p` falls back to a placeholder.
274+
if formatted == expected::PTR_VAL_NO_CRNG {
275+
return Ok(());
276+
}
277+
assert!(formatted.starts_with(expected::HASHED_PREFIX));
278+
assert_ne!(formatted, expected::RAW_POINTER);
279+
280+
let cstr = CString::try_from_fmt(fmt!("{:>24p}", ptr))?;
281+
assert!(cstr
282+
.to_str()?
283+
.starts_with(expected::HASHED_PADDED_RIGHT_PREFIX));
284+
285+
let cstr = CString::try_from_fmt(fmt!("{:024p}", ptr))?;
286+
assert!(cstr
287+
.to_str()?
288+
.starts_with(expected::HASHED_ZERO_PADDED_PREFIX));
289+
290+
let cstr = CString::try_from_fmt(fmt!("{:0100p}", ptr))?;
291+
let output = cstr.to_str()?;
292+
assert!(output.starts_with("0x"));
293+
assert!(!output[2..].chars().all(|c| c == '0'));
294+
}
295+
296+
Ok(())
297+
}
298+
}

0 commit comments

Comments
 (0)