mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-28 02:53:16 -04:00
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>
299 lines
9.6 KiB
Rust
299 lines
9.6 KiB
Rust
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
//! Formatting utilities.
|
|
//!
|
|
//! This module is intended to be used in place of `core::fmt` in kernel code.
|
|
|
|
use kernel::prelude::*;
|
|
|
|
pub use core::fmt::{
|
|
Arguments,
|
|
Debug,
|
|
Error,
|
|
Formatter,
|
|
Result,
|
|
Write, //
|
|
};
|
|
|
|
/// Internal adapter used to route and allow implementations of formatting traits for foreign types.
|
|
///
|
|
/// It is inserted automatically by the [`fmt!`] macro and is not meant to be used directly.
|
|
///
|
|
/// [`fmt!`]: crate::prelude::fmt!
|
|
#[doc(hidden)]
|
|
pub struct Adapter<T>(pub T);
|
|
|
|
macro_rules! impl_fmt_adapter_forward {
|
|
($($trait:ident),* $(,)?) => {
|
|
$(
|
|
impl<T: $trait> $trait for Adapter<T> {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
|
let Self(t) = self;
|
|
$trait::fmt(t, f)
|
|
}
|
|
}
|
|
)*
|
|
};
|
|
}
|
|
|
|
use core::fmt::{
|
|
Binary,
|
|
LowerExp,
|
|
LowerHex,
|
|
Octal,
|
|
UpperExp,
|
|
UpperHex, //
|
|
};
|
|
use core::ptr::NonNull;
|
|
impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, LowerExp, UpperExp);
|
|
|
|
/// 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];
|
|
|
|
// 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;
|
|
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).
|
|
// 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(),
|
|
width,
|
|
self.0.cast::<c_void>(),
|
|
)
|
|
};
|
|
|
|
// 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`.
|
|
f.write_str(s)
|
|
} else {
|
|
f.pad(s)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
Pointer::fmt(&HashedPtr(*self), f)
|
|
}
|
|
}
|
|
|
|
impl<T: ?Sized> Pointer for &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 {
|
|
Pointer::fmt(&HashedPtr(core::ptr::from_ref(*self)), f)
|
|
}
|
|
}
|
|
|
|
impl<T: ?Sized> Pointer for NonNull<T> {
|
|
#[inline]
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
|
Pointer::fmt(&HashedPtr(self.as_ptr()), 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)
|
|
}
|
|
}
|
|
|
|
/// A copy of [`core::fmt::Display`] that allows us to implement it for foreign types.
|
|
///
|
|
/// Types should implement this trait rather than [`core::fmt::Display`]. Together with the
|
|
/// [`Adapter`] type and [`fmt!`] macro, it allows for formatting foreign types (e.g. types from
|
|
/// core) which do not implement [`core::fmt::Display`] directly.
|
|
///
|
|
/// [`fmt!`]: crate::prelude::fmt!
|
|
pub trait Display {
|
|
/// Same as [`core::fmt::Display::fmt`].
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
|
|
}
|
|
|
|
impl<T: ?Sized + Display> Display for &T {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
|
Display::fmt(*self, f)
|
|
}
|
|
}
|
|
|
|
impl<T: ?Sized + Display> core::fmt::Display for Adapter<&T> {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
|
let Self(t) = self;
|
|
Display::fmt(t, f)
|
|
}
|
|
}
|
|
|
|
macro_rules! impl_display_forward {
|
|
($(
|
|
$( { $($generics:tt)* } )? $ty:ty $( { where $($where:tt)* } )?
|
|
),* $(,)?) => {
|
|
$(
|
|
impl$($($generics)*)? Display for $ty $(where $($where)*)? {
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
|
core::fmt::Display::fmt(self, f)
|
|
}
|
|
}
|
|
)*
|
|
};
|
|
}
|
|
|
|
impl_display_forward!(
|
|
bool,
|
|
char,
|
|
core::panic::PanicInfo<'_>,
|
|
Arguments<'_>,
|
|
i128,
|
|
i16,
|
|
i32,
|
|
i64,
|
|
i8,
|
|
isize,
|
|
str,
|
|
u128,
|
|
u16,
|
|
u32,
|
|
u64,
|
|
u8,
|
|
usize,
|
|
{<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 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";
|
|
}
|
|
|
|
#[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";
|
|
}
|
|
|
|
#[test]
|
|
fn test_ptr_formatting() -> core::result::Result<(), crate::error::Error> {
|
|
let ptr: *const u8 = core::ptr::without_provenance(expected::PTR_VALUE);
|
|
|
|
// 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);
|
|
|
|
let cstr = CString::try_from_fmt(fmt!("{:0100p}", ptr))?;
|
|
assert_eq!(cstr.to_str()?, expected::CLAMPED);
|
|
} else {
|
|
let cstr = CString::try_from_fmt(fmt!("{:p}", ptr))?;
|
|
let formatted = cstr.to_str()?;
|
|
// If the RNG is not yet ready, `%p` falls back to a placeholder.
|
|
if formatted == expected::PTR_VAL_NO_CRNG {
|
|
return Ok(());
|
|
}
|
|
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));
|
|
|
|
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'));
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
}
|