From 20d3d576d574bc38c7ea38caf821f923abe31cc9 Mon Sep 17 00:00:00 2001 From: Ramon de C Valle Date: Mon, 4 Aug 2025 18:58:31 -0700 Subject: [PATCH] CFI: Rewrite `FnPtrShim` when generalizing When looking for instances which could either be dynamically called through a vtable or through a concrete trait method, we missed `FnPtrShim`, instead only looking at `Item` and closure-likes. --- .../cfi/typeid/itanium_cxx_abi/transform.rs | 34 ++++++++++++++++--- tests/ui/sanitizer/cfi/fn-trait-objects.rs | 32 +++++++++++++++++ tests/ui/sanitizer/kcfi/fn-trait-objects.rs | 32 +++++++++++++++++ 3 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 tests/ui/sanitizer/cfi/fn-trait-objects.rs create mode 100644 tests/ui/sanitizer/kcfi/fn-trait-objects.rs diff --git a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs index 021b206e1e2ef..28481f3dcdb3d 100644 --- a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs +++ b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs @@ -10,9 +10,8 @@ use rustc_hir as hir; use rustc_hir::LangItem; use rustc_middle::bug; use rustc_middle::ty::{ - self, AssocContainer, ExistentialPredicateStableCmpExt as _, Instance, InstanceKind, IntTy, - List, TraitRef, Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, - UintTy, + self, AssocContainer, ExistentialPredicateStableCmpExt as _, Instance, IntTy, List, TraitRef, + Ty, TyCtxt, TypeFoldable, TypeFolder, TypeSuperFoldable, TypeVisitableExt, UintTy, }; use rustc_span::def_id::DefId; use rustc_span::{DUMMY_SP, sym}; @@ -459,6 +458,30 @@ pub(crate) fn transform_instance<'tcx>( instance } +fn default_or_shim<'tcx>(tcx: TyCtxt<'tcx>, instance: Instance<'tcx>) -> Option { + match instance.def { + ty::InstanceKind::Item(def_id) | ty::InstanceKind::FnPtrShim(def_id, _) => { + tcx.opt_associated_item(def_id).map(|item| item.def_id) + } + _ => None, + } +} + +/// Determines if an instance represents a trait method implementation and returns the necessary +/// information for type erasure. +/// +/// This function handles two main cases: +/// +/// * **Implementation in an `impl` block**: When the instance represents a concrete implementation +/// of a trait method in an `impl` block, it extracts the trait reference, method ID, and trait +/// ID from the implementation. The method ID is obtained from the `trait_item_def_id` field of +/// the associated item, which points to the original trait method definition. +/// +/// * **Provided method in a `trait` block or synthetic `shim`**: When the instance represents a +/// default implementation provided in the trait definition itself or a synthetic shim, it uses +/// the instance's own `def_id` as the method ID and determines the trait ID from the associated +/// item. +/// fn implemented_method<'tcx>( tcx: TyCtxt<'tcx>, instance: Instance<'tcx>, @@ -476,10 +499,11 @@ fn implemented_method<'tcx>( trait_id = trait_ref.skip_binder().def_id; impl_id } else if let AssocContainer::Trait = assoc.container - && let InstanceKind::Item(def_id) = instance.def + && let Some(trait_method_def_id) = default_or_shim(tcx, instance) { + // Provided method in a `trait` block or a synthetic `shim` trait_method = assoc; - method_id = def_id; + method_id = trait_method_def_id; trait_id = tcx.parent(method_id); trait_ref = ty::EarlyBinder::bind(TraitRef::from_assoc(tcx, trait_id, instance.args)); trait_id diff --git a/tests/ui/sanitizer/cfi/fn-trait-objects.rs b/tests/ui/sanitizer/cfi/fn-trait-objects.rs new file mode 100644 index 0000000000000..977d4124fff0c --- /dev/null +++ b/tests/ui/sanitizer/cfi/fn-trait-objects.rs @@ -0,0 +1,32 @@ +// Verifies that types that implement the Fn, FnMut, or FnOnce traits can be +// called through their trait methods. +// +//@ needs-sanitizer-cfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Ccodegen-units=1 -Clto -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=cfi -Cunsafe-allow-abi-mismatch=sanitizer --test +//@ run-pass + +#![feature(fn_traits)] +#![feature(unboxed_closures)] + +fn foo(_a: u32) {} + +#[test] +fn test_fn_trait() { + let f: Box = Box::new(foo); + Fn::call(&f, (0,)); +} + +#[test] +fn test_fnmut_trait() { + let mut a = 0; + let mut f: Box = Box::new(|x| a += x); + FnMut::call_mut(&mut f, (1,)); +} + +#[test] +fn test_fnonce_trait() { + let f: Box = Box::new(foo); + FnOnce::call_once(f, (2,)); +} diff --git a/tests/ui/sanitizer/kcfi/fn-trait-objects.rs b/tests/ui/sanitizer/kcfi/fn-trait-objects.rs new file mode 100644 index 0000000000000..3f6b78545a0a1 --- /dev/null +++ b/tests/ui/sanitizer/kcfi/fn-trait-objects.rs @@ -0,0 +1,32 @@ +// Verifies that types that implement the Fn, FnMut, or FnOnce traits can be +// called through their trait methods. +// +//@ needs-sanitizer-kcfi +//@ only-linux +//@ ignore-backends: gcc +//@ compile-flags: -Ctarget-feature=-crt-static -Zpanic_abort_tests -Cpanic=abort -Cprefer-dynamic=off -Copt-level=0 -Zsanitizer=kcfi -Cunsafe-allow-abi-mismatch=sanitizer --test +//@ run-pass + +#![feature(fn_traits)] +#![feature(unboxed_closures)] + +fn foo(_a: u32) {} + +#[test] +fn test_fn_trait() { + let f: Box = Box::new(foo); + Fn::call(&f, (0,)); +} + +#[test] +fn test_fnmut_trait() { + let mut a = 0; + let mut f: Box = Box::new(|x| a += x); + FnMut::call_mut(&mut f, (1,)); +} + +#[test] +fn test_fnonce_trait() { + let f: Box = Box::new(foo); + FnOnce::call_once(f, (2,)); +}