Skip to content

Commit

Permalink
Auto merge of rust-lang#123052 - maurer:addr-taken, r=<try>
Browse files Browse the repository at this point in the history
CFI: Support function pointers for trait methods

Adds support for both CFI and KCFI for function pointers to trait methods by attaching both concrete and abstract types to functions.

KCFI does this through generation of a `ReifyShim` on any function pointer for a method that could go into a vtable, and keeping this separate from `ReifyShim`s that are *intended* for vtable us by setting a `ReifyReason` on them.

CFI does this by setting both the concrete and abstract type on every instance.

This should land after rust-lang#123024 or a similar PR, as it diverges the implementation of CFI vs KCFI.

r? `@compiler-errors`
  • Loading branch information
bors committed Apr 1, 2024
2 parents 66de611 + 4775daf commit 0f5b741
Show file tree
Hide file tree
Showing 12 changed files with 104 additions and 29 deletions.
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/mir/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ macro_rules! make_mir_visitor {

ty::InstanceDef::Intrinsic(_def_id) |
ty::InstanceDef::VTableShim(_def_id) |
ty::InstanceDef::ReifyShim(_def_id) |
ty::InstanceDef::ReifyShim(_def_id, _) |
ty::InstanceDef::Virtual(_def_id, _) |
ty::InstanceDef::ThreadLocalShim(_def_id) |
ty::InstanceDef::ClosureOnceShim { call_once: _def_id, track_caller: _ } |
Expand Down
47 changes: 39 additions & 8 deletions compiler/rustc_middle/src/ty/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ pub struct Instance<'tcx> {
pub args: GenericArgsRef<'tcx>,
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[derive(TyEncodable, TyDecodable, HashStable)]
pub enum ReifyReason {
FnPtr,
Vtable,
}

#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[derive(TyEncodable, TyDecodable, HashStable, TypeFoldable, TypeVisitable, Lift)]
pub enum InstanceDef<'tcx> {
Expand Down Expand Up @@ -67,7 +74,13 @@ pub enum InstanceDef<'tcx> {
/// Because this is a required part of the function's ABI but can't be tracked
/// as a property of the function pointer, we use a single "caller location"
/// (the definition of the function itself).
ReifyShim(DefId),
///
/// The second field encodes *why* this shim was created. This allows distinguishing between
/// a `ReifyShim` that appears in a vtable vs one that appears as a function pointer.
///
/// This field will only be populated if we are compiling in a mode that needs these shims
/// to be separable, currently only when KCFI is enabled.
ReifyShim(DefId, Option<ReifyReason>),

/// `<fn() as FnTrait>::call_*` (generated `FnTrait` implementation for `fn()` pointers).
///
Expand Down Expand Up @@ -194,7 +207,7 @@ impl<'tcx> InstanceDef<'tcx> {
match self {
InstanceDef::Item(def_id)
| InstanceDef::VTableShim(def_id)
| InstanceDef::ReifyShim(def_id)
| InstanceDef::ReifyShim(def_id, _)
| InstanceDef::FnPtrShim(def_id, _)
| InstanceDef::Virtual(def_id, _)
| InstanceDef::Intrinsic(def_id)
Expand Down Expand Up @@ -354,7 +367,9 @@ fn fmt_instance(
match instance.def {
InstanceDef::Item(_) => Ok(()),
InstanceDef::VTableShim(_) => write!(f, " - shim(vtable)"),
InstanceDef::ReifyShim(_) => write!(f, " - shim(reify)"),
InstanceDef::ReifyShim(_, None) => write!(f, " - shim(reify)"),
InstanceDef::ReifyShim(_, Some(ReifyReason::FnPtr)) => write!(f, " - shim(reify-fnptr)"),
InstanceDef::ReifyShim(_, Some(ReifyReason::Vtable)) => write!(f, " - shim(reify-vtable)"),
InstanceDef::ThreadLocalShim(_) => write!(f, " - shim(tls)"),
InstanceDef::Intrinsic(_) => write!(f, " - intrinsic"),
InstanceDef::Virtual(_, num) => write!(f, " - virtual#{num}"),
Expand Down Expand Up @@ -476,15 +491,30 @@ impl<'tcx> Instance<'tcx> {
debug!("resolve(def_id={:?}, args={:?})", def_id, args);
// Use either `resolve_closure` or `resolve_for_vtable`
assert!(!tcx.is_closure_like(def_id), "Called `resolve_for_fn_ptr` on closure: {def_id:?}");
let reason = tcx.sess.is_sanitizer_kcfi_enabled().then_some(ReifyReason::FnPtr);
Instance::resolve(tcx, param_env, def_id, args).ok().flatten().map(|mut resolved| {
match resolved.def {
InstanceDef::Item(def) if resolved.def.requires_caller_location(tcx) => {
debug!(" => fn pointer created for function with #[track_caller]");
resolved.def = InstanceDef::ReifyShim(def);
resolved.def = InstanceDef::ReifyShim(def, reason);
}
InstanceDef::Virtual(def_id, _) => {
debug!(" => fn pointer created for virtual call");
resolved.def = InstanceDef::ReifyShim(def_id);
resolved.def = InstanceDef::ReifyShim(def_id, reason);
}
// FIXME(maurer) only shim it if it is a vtable-safe function
_ if tcx.sess.is_sanitizer_kcfi_enabled()
&& tcx.associated_item(def_id).trait_item_def_id.is_some() =>
{
// If this function could also go in a vtable, we need to `ReifyShim` it with
// KCFI because it can only attach one type per function.
resolved.def = InstanceDef::ReifyShim(resolved.def_id(), reason)
}
_ if tcx.sess.is_sanitizer_kcfi_enabled()
&& tcx.is_closure_like(resolved.def_id()) =>
{
// Reroute through a reify via the *original*
resolved = Instance { def: InstanceDef::ReifyShim(def_id, reason), args }
}
_ => {}
}
Expand All @@ -508,6 +538,7 @@ impl<'tcx> Instance<'tcx> {
debug!(" => associated item with unsizeable self: Self");
Some(Instance { def: InstanceDef::VTableShim(def_id), args })
} else {
let reason = tcx.sess.is_sanitizer_kcfi_enabled().then_some(ReifyReason::Vtable);
Instance::resolve(tcx, param_env, def_id, args).ok().flatten().map(|mut resolved| {
match resolved.def {
InstanceDef::Item(def) => {
Expand Down Expand Up @@ -544,18 +575,18 @@ impl<'tcx> Instance<'tcx> {
// Create a shim for the `FnOnce/FnMut/Fn` method we are calling
// - unlike functions, invoking a closure always goes through a
// trait.
resolved = Instance { def: InstanceDef::ReifyShim(def_id), args };
resolved = Instance { def: InstanceDef::ReifyShim(def_id, reason), args };
} else {
debug!(
" => vtable fn pointer created for function with #[track_caller]: {:?}", def
);
resolved.def = InstanceDef::ReifyShim(def);
resolved.def = InstanceDef::ReifyShim(def, reason);
}
}
}
InstanceDef::Virtual(def_id, _) => {
debug!(" => vtable fn pointer created for virtual call");
resolved.def = InstanceDef::ReifyShim(def_id);
resolved.def = InstanceDef::ReifyShim(def_id, reason)
}
_ => {}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ pub use self::context::{
tls, CtxtInterners, CurrentGcx, DeducedParamAttrs, Feed, FreeRegionInfo, GlobalCtxt, Lift,
TyCtxt, TyCtxtFeed,
};
pub use self::instance::{Instance, InstanceDef, ShortInstance, UnusedGenericParams};
pub use self::instance::{Instance, InstanceDef, ReifyReason, ShortInstance, UnusedGenericParams};
pub use self::list::List;
pub use self::parameterized::ParameterizedOverTcx;
pub use self::predicate::{
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_middle/src/ty/structural_impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,7 @@ TrivialTypeTraversalAndLiftImpls! {
crate::ty::ClosureKind,
crate::ty::ParamConst,
crate::ty::ParamTy,
crate::ty::instance::ReifyReason,
interpret::AllocId,
interpret::CtfeProvenance,
interpret::Scalar,
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir_transform/src/inline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ impl<'tcx> Inliner<'tcx> {
// do not need to catch this here, we can wait until the inliner decides to continue
// inlining a second time.
InstanceDef::VTableShim(_)
| InstanceDef::ReifyShim(_)
| InstanceDef::ReifyShim(..)
| InstanceDef::FnPtrShim(..)
| InstanceDef::ClosureOnceShim { .. }
| InstanceDef::ConstructCoroutineInClosureShim { .. }
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir_transform/src/inline/cycle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ pub(crate) fn mir_callgraph_reachable<'tcx>(
// again, a function item can end up getting inlined. Thus we'll be able to cause
// a cycle that way
InstanceDef::VTableShim(_)
| InstanceDef::ReifyShim(_)
| InstanceDef::ReifyShim(..)
| InstanceDef::FnPtrShim(..)
| InstanceDef::ClosureOnceShim { .. }
| InstanceDef::ConstructCoroutineInClosureShim { .. }
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir_transform/src/shim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ fn make_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: ty::InstanceDef<'tcx>) -> Body<'
// a virtual call, or a direct call to a function for which
// indirect calls must be codegen'd differently than direct ones
// (such as `#[track_caller]`).
ty::InstanceDef::ReifyShim(def_id) => {
ty::InstanceDef::ReifyShim(def_id, _) => {
build_call_shim(tcx, instance, None, CallKind::Direct(def_id))
}
ty::InstanceDef::ClosureOnceShim { call_once: _, track_caller: _ } => {
Expand Down
12 changes: 9 additions & 3 deletions compiler/rustc_symbol_mangling/src/legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use rustc_data_structures::stable_hasher::{Hash64, HashStable, StableHasher};
use rustc_hir::def_id::CrateNum;
use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
use rustc_middle::ty::print::{PrettyPrinter, Print, PrintError, Printer};
use rustc_middle::ty::{self, Instance, Ty, TyCtxt, TypeVisitableExt};
use rustc_middle::ty::{self, Instance, ReifyReason, Ty, TyCtxt, TypeVisitableExt};
use rustc_middle::ty::{GenericArg, GenericArgKind};

use std::fmt::{self, Write};
Expand Down Expand Up @@ -71,8 +71,14 @@ pub(super) fn mangle<'tcx>(
ty::InstanceDef::VTableShim(..) => {
printer.write_str("{{vtable-shim}}").unwrap();
}
ty::InstanceDef::ReifyShim(..) => {
printer.write_str("{{reify-shim}}").unwrap();
ty::InstanceDef::ReifyShim(_, reason) => {
printer.write_str("{{reify-shim").unwrap();
match reason {
Some(ReifyReason::FnPtr) => printer.write_str("-fnptr").unwrap(),
Some(ReifyReason::Vtable) => printer.write_str("-vtable").unwrap(),
None => (),
}
printer.write_str("}}").unwrap();
}
// FIXME(async_closures): This shouldn't be needed when we fix
// `Instance::ty`/`Instance::def_id`.
Expand Down
9 changes: 7 additions & 2 deletions compiler/rustc_symbol_mangling/src/typeid.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
/// For more information about LLVM CFI and cross-language LLVM CFI support for the Rust compiler,
/// see design document in the tracking issue #89653.
use bitflags::bitflags;
use rustc_middle::ty::{Instance, Ty, TyCtxt};
use rustc_middle::ty::{Instance, InstanceDef, ReifyReason, Ty, TyCtxt};
use rustc_target::abi::call::FnAbi;
use std::hash::Hasher;
use twox_hash::XxHash64;
Expand Down Expand Up @@ -67,8 +67,13 @@ pub fn kcfi_typeid_for_fnabi<'tcx>(
pub fn kcfi_typeid_for_instance<'tcx>(
tcx: TyCtxt<'tcx>,
instance: Instance<'tcx>,
options: TypeIdOptions,
mut options: TypeIdOptions,
) -> u32 {
// If we receive a `ReifyShim` intended to produce a function pointer, we need to remain
// concrete - abstraction is for vtables.
if matches!(instance.def, InstanceDef::ReifyShim(_, Some(ReifyReason::FnPtr))) {
options |= TypeIdOptions::NO_SELF_TYPE_ERASURE
}
// A KCFI type metadata identifier is a 32-bit constant produced by taking the lower half of the
// xxHash64 of the type metadata identifier. (See llvm/llvm-project@cff5bef.)
let mut hash: XxHash64 = Default::default();
Expand Down
8 changes: 5 additions & 3 deletions compiler/rustc_symbol_mangling/src/v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ use rustc_hir::definitions::{DefPathData, DisambiguatedDefPathData};
use rustc_middle::ty::layout::IntegerExt;
use rustc_middle::ty::print::{Print, PrintError, Printer};
use rustc_middle::ty::{
self, EarlyBinder, FloatTy, Instance, IntTy, Ty, TyCtxt, TypeVisitable, TypeVisitableExt,
UintTy,
self, EarlyBinder, FloatTy, Instance, IntTy, ReifyReason, Ty, TyCtxt, TypeVisitable,
TypeVisitableExt, UintTy,
};
use rustc_middle::ty::{GenericArg, GenericArgKind};
use rustc_span::symbol::kw;
Expand Down Expand Up @@ -44,7 +44,9 @@ pub(super) fn mangle<'tcx>(
let shim_kind = match instance.def {
ty::InstanceDef::ThreadLocalShim(_) => Some("tls"),
ty::InstanceDef::VTableShim(_) => Some("vtable"),
ty::InstanceDef::ReifyShim(_) => Some("reify"),
ty::InstanceDef::ReifyShim(_, None) => Some("reify"),
ty::InstanceDef::ReifyShim(_, Some(ReifyReason::FnPtr)) => Some("reify-fnptr"),
ty::InstanceDef::ReifyShim(_, Some(ReifyReason::Vtable)) => Some("reify-vtable"),

ty::InstanceDef::ConstructCoroutineInClosureShim { .. }
| ty::InstanceDef::CoroutineKindShim { .. } => Some("fn_once"),
Expand Down
4 changes: 0 additions & 4 deletions tests/ui/sanitizer/cfi-closures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@

#![feature(fn_traits)]
#![feature(unboxed_closures)]
#![feature(cfg_sanitize)]

fn foo<'a, T>() -> Box<dyn Fn(&'a T) -> &'a T> {
Box::new(|x| x)
Expand Down Expand Up @@ -72,9 +71,6 @@ fn use_closure<C>(call: extern "rust-call" fn(&C, ()) -> i32, f: &C) -> i32 {
}

#[test]
// FIXME after KCFI reify support is added, remove this
// It will appear to work if you test locally, set -C opt-level=0 to see it fail.
#[cfg_attr(sanitize = "kcfi", ignore)]
fn closure_addr_taken() {
let x = 3i32;
let f = || x;
Expand Down
42 changes: 38 additions & 4 deletions tests/ui/sanitizer/cfi-method-fn-ptr-cast.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,41 @@
// Verifies that casting a method to a function pointer works.
//
// FIXME(#122848): Remove only-linux when fixed.

//@ revisions: cfi kcfi
// FIXME(#122848) Remove only-linux once OSX CFI binaries work
//@ only-linux
//@ needs-sanitizer-cfi
//@ compile-flags: -Clto -Copt-level=0 -Cprefer-dynamic=off -Ctarget-feature=-crt-static -Zsanitizer=cfi
//@ [cfi] needs-sanitizer-cfi
//@ [kcfi] needs-sanitizer-kcfi
//@ compile-flags: -C target-feature=-crt-static
//@ [cfi] compile-flags: -C opt-level=0 -C codegen-units=1 -C lto
//@ [cfi] compile-flags: -C prefer-dynamic=off
//@ [cfi] compile-flags: -Z sanitizer=cfi
//@ [kcfi] compile-flags: -Z sanitizer=kcfi
//@ [kcfi] compile-flags: -C panic=abort -C prefer-dynamic=off
//@ run-pass

trait Foo {
fn foo(&self);
fn bar(&self);
}

struct S;

impl Foo for S {
fn foo(&self) {}
#[track_caller]
fn bar(&self) {}
}

struct S2 {
f: fn(&S)
}

impl S2 {
fn foo(&self, s: &S) {
(self.f)(s)
}
}

trait Trait1 {
fn foo(&self);
}
Expand All @@ -20,4 +50,8 @@ fn main() {
let type1 = Type1 {};
let f = <Type1 as Trait1>::foo;
f(&type1);
// Check again with different optimization barriers
S2 { f: <S as Foo>::foo }.foo(&S);
// Check mismatched #[track_caller]
S2 { f: <S as Foo>::bar }.foo(&S)
}

0 comments on commit 0f5b741

Please sign in to comment.