Auto merge of #139632 - Darksonn:cfi-fmt, r=m-ou-se
cfi: do not transmute function pointers in formatting code Follow-up to #115954. Addresses #115199 point 2. Related to #128728. Discussion [on the LKML](https://lore.kernel.org/all/20250410115420.366349-1-panikiel@google.com/). cc `@maurer` `@rcvalle` `@RalfJung`
This commit is contained in:
commit
40dacd50b7
3 changed files with 50 additions and 25 deletions
|
@ -7,7 +7,7 @@ use crate::char::{EscapeDebugExtArgs, MAX_LEN_UTF8};
|
|||
use crate::marker::PhantomData;
|
||||
use crate::num::fmt as numfmt;
|
||||
use crate::ops::Deref;
|
||||
use crate::{iter, mem, result, str};
|
||||
use crate::{iter, result, str};
|
||||
|
||||
mod builders;
|
||||
#[cfg(not(no_fp_fmt_parse))]
|
||||
|
|
|
@ -65,61 +65,92 @@ pub struct Argument<'a> {
|
|||
ty: ArgumentType<'a>,
|
||||
}
|
||||
|
||||
#[rustc_diagnostic_item = "ArgumentMethods"]
|
||||
impl Argument<'_> {
|
||||
#[inline]
|
||||
const fn new<'a, T>(x: &'a T, f: fn(&T, &mut Formatter<'_>) -> Result) -> Argument<'a> {
|
||||
macro_rules! argument_new {
|
||||
($t:ty, $x:expr, $f:expr) => {
|
||||
Argument {
|
||||
// INVARIANT: this creates an `ArgumentType<'a>` from a `&'a T` and
|
||||
// a `fn(&T, ...)`, so the invariant is maintained.
|
||||
ty: ArgumentType::Placeholder {
|
||||
value: NonNull::from_ref(x).cast(),
|
||||
// SAFETY: function pointers always have the same layout.
|
||||
formatter: unsafe { mem::transmute(f) },
|
||||
value: NonNull::<$t>::from_ref($x).cast(),
|
||||
// The Rust ABI considers all pointers to be equivalent, so transmuting a fn(&T) to
|
||||
// fn(NonNull<()>) and calling it with a NonNull<()> that points at a T is allowed.
|
||||
// However, the CFI sanitizer does not allow this, and triggers a crash when it
|
||||
// happens.
|
||||
//
|
||||
// To avoid this crash, we use a helper function when CFI is enabled. To avoid the
|
||||
// cost of this helper function (mainly code-size) when it is not needed, we
|
||||
// transmute the function pointer otherwise.
|
||||
//
|
||||
// This is similar to what the Rust compiler does internally with vtables when KCFI
|
||||
// is enabled, where it generates trampoline functions that only serve to adjust the
|
||||
// expected type of the argument. `ArgumentType::Placeholder` is a bit like a
|
||||
// manually constructed trait object, so it is not surprising that the same approach
|
||||
// has to be applied here as well.
|
||||
//
|
||||
// It is still considered problematic (from the Rust side) that CFI rejects entirely
|
||||
// legal Rust programs, so we do not consider anything done here a stable guarantee,
|
||||
// but meanwhile we carry this work-around to keep Rust compatible with CFI and
|
||||
// KCFI.
|
||||
#[cfg(not(any(sanitize = "cfi", sanitize = "kcfi")))]
|
||||
formatter: {
|
||||
let f: fn(&$t, &mut Formatter<'_>) -> Result = $f;
|
||||
// SAFETY: This is only called with `value`, which has the right type.
|
||||
unsafe { core::mem::transmute(f) }
|
||||
},
|
||||
#[cfg(any(sanitize = "cfi", sanitize = "kcfi"))]
|
||||
formatter: |ptr: NonNull<()>, fmt: &mut Formatter<'_>| {
|
||||
let func = $f;
|
||||
// SAFETY: This is the same type as the `value` field.
|
||||
let r = unsafe { ptr.cast::<$t>().as_ref() };
|
||||
(func)(r, fmt)
|
||||
},
|
||||
_lifetime: PhantomData,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[rustc_diagnostic_item = "ArgumentMethods"]
|
||||
impl Argument<'_> {
|
||||
#[inline]
|
||||
pub fn new_display<T: Display>(x: &T) -> Argument<'_> {
|
||||
Self::new(x, Display::fmt)
|
||||
argument_new!(T, x, <T as Display>::fmt)
|
||||
}
|
||||
#[inline]
|
||||
pub fn new_debug<T: Debug>(x: &T) -> Argument<'_> {
|
||||
Self::new(x, Debug::fmt)
|
||||
argument_new!(T, x, <T as Debug>::fmt)
|
||||
}
|
||||
#[inline]
|
||||
pub fn new_debug_noop<T: Debug>(x: &T) -> Argument<'_> {
|
||||
Self::new(x, |_, _| Ok(()))
|
||||
argument_new!(T, x, |_: &T, _| Ok(()))
|
||||
}
|
||||
#[inline]
|
||||
pub fn new_octal<T: Octal>(x: &T) -> Argument<'_> {
|
||||
Self::new(x, Octal::fmt)
|
||||
argument_new!(T, x, <T as Octal>::fmt)
|
||||
}
|
||||
#[inline]
|
||||
pub fn new_lower_hex<T: LowerHex>(x: &T) -> Argument<'_> {
|
||||
Self::new(x, LowerHex::fmt)
|
||||
argument_new!(T, x, <T as LowerHex>::fmt)
|
||||
}
|
||||
#[inline]
|
||||
pub fn new_upper_hex<T: UpperHex>(x: &T) -> Argument<'_> {
|
||||
Self::new(x, UpperHex::fmt)
|
||||
argument_new!(T, x, <T as UpperHex>::fmt)
|
||||
}
|
||||
#[inline]
|
||||
pub fn new_pointer<T: Pointer>(x: &T) -> Argument<'_> {
|
||||
Self::new(x, Pointer::fmt)
|
||||
argument_new!(T, x, <T as Pointer>::fmt)
|
||||
}
|
||||
#[inline]
|
||||
pub fn new_binary<T: Binary>(x: &T) -> Argument<'_> {
|
||||
Self::new(x, Binary::fmt)
|
||||
argument_new!(T, x, <T as Binary>::fmt)
|
||||
}
|
||||
#[inline]
|
||||
pub fn new_lower_exp<T: LowerExp>(x: &T) -> Argument<'_> {
|
||||
Self::new(x, LowerExp::fmt)
|
||||
argument_new!(T, x, <T as LowerExp>::fmt)
|
||||
}
|
||||
#[inline]
|
||||
pub fn new_upper_exp<T: UpperExp>(x: &T) -> Argument<'_> {
|
||||
Self::new(x, UpperExp::fmt)
|
||||
argument_new!(T, x, <T as UpperExp>::fmt)
|
||||
}
|
||||
#[inline]
|
||||
#[track_caller]
|
||||
|
@ -135,11 +166,6 @@ impl Argument<'_> {
|
|||
/// # Safety
|
||||
///
|
||||
/// This argument must actually be a placeholder argument.
|
||||
///
|
||||
// FIXME: Transmuting formatter in new and indirectly branching to/calling
|
||||
// it here is an explicit CFI violation.
|
||||
#[allow(inline_no_sanitize)]
|
||||
#[no_sanitize(cfi, kcfi)]
|
||||
#[inline]
|
||||
pub(super) unsafe fn fmt(&self, f: &mut Formatter<'_>) -> Result {
|
||||
match self.ty {
|
||||
|
|
|
@ -169,7 +169,6 @@
|
|||
#![feature(negative_impls)]
|
||||
#![feature(never_type)]
|
||||
#![feature(no_core)]
|
||||
#![feature(no_sanitize)]
|
||||
#![feature(optimize_attribute)]
|
||||
#![feature(prelude_import)]
|
||||
#![feature(repr_simd)]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue