|
4 | 4 | //! |
5 | 5 | //! This module is intended to be used in place of `core::fmt` in kernel code. |
6 | 6 |
|
| 7 | +use kernel::prelude::*; |
| 8 | + |
7 | 9 | pub use core::fmt::{ |
8 | 10 | Arguments, |
9 | 11 | Debug, |
@@ -39,13 +41,110 @@ use core::fmt::{ |
39 | 41 | LowerExp, |
40 | 42 | LowerHex, |
41 | 43 | Octal, |
42 | | - Pointer, |
43 | 44 | UpperExp, |
44 | 45 | UpperHex, // |
45 | 46 | }; |
| 47 | +use core::ptr::NonNull; |
46 | 48 | impl_fmt_adapter_forward!(Debug, LowerHex, UpperHex, Octal, Binary, LowerExp, UpperExp); |
47 | 49 |
|
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> { |
49 | 148 | #[inline] |
50 | 149 | fn fmt(&self, f: &mut Formatter<'_>) -> Result { |
51 | 150 | Pointer::fmt(self.0, f) |
@@ -112,3 +211,88 @@ impl_display_forward!( |
112 | 211 | {<T: ?Sized>} crate::sync::Arc<T> {where crate::sync::Arc<T>: core::fmt::Display}, |
113 | 212 | {<T: ?Sized>} crate::sync::UniqueArc<T> {where crate::sync::UniqueArc<T>: core::fmt::Display}, |
114 | 213 | ); |
| 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