diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs index da17f05bb8a32..c28e1c1b86d64 100644 --- a/compiler/rustc_codegen_gcc/src/builder.rs +++ b/compiler/rustc_codegen_gcc/src/builder.rs @@ -1843,6 +1843,17 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> { fn fptosi_sat(&mut self, val: RValue<'gcc>, dest_ty: Type<'gcc>) -> RValue<'gcc> { self.fptoint_sat(true, val, dest_ty) } + + fn ptrauth_resign( + &mut self, + _value: Self::Value, + _old_key: u32, + _old_discriminator: u64, + _new_key: u32, + _new_discriminator: u64, + ) -> Self::Value { + bug!("Resigning of pointers not implemented"); + } } impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index e73b8aab54d73..edbd0ef7ad1c0 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -247,7 +247,7 @@ impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> { cv: Scalar, layout: abi::Scalar, ty: Type<'gcc>, - _schema: Option<&PointerAuthSchema>, + _schema: Option, ) -> RValue<'gcc> { let bitsize = if layout.is_bool() { 1 } else { layout.size(self).bits() }; match cv { diff --git a/compiler/rustc_codegen_gcc/src/context.rs b/compiler/rustc_codegen_gcc/src/context.rs index 0e3fa72fbcfb3..4194f7de4609f 100644 --- a/compiler/rustc_codegen_gcc/src/context.rs +++ b/compiler/rustc_codegen_gcc/src/context.rs @@ -401,7 +401,7 @@ impl<'gcc, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { fn get_fn_addr( &self, instance: Instance<'tcx>, - _pointer_auth_schema: Option<&PointerAuthSchema>, + _pointer_auth_schema: Option, ) -> RValue<'gcc> { let func_name = self.tcx.symbol_name(instance).name; diff --git a/compiler/rustc_codegen_gcc/src/int.rs b/compiler/rustc_codegen_gcc/src/int.rs index dfae4eceebe44..8e391ce1dd0e8 100644 --- a/compiler/rustc_codegen_gcc/src/int.rs +++ b/compiler/rustc_codegen_gcc/src/int.rs @@ -375,6 +375,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { fixed_count: 3, conv: CanonAbi::C, can_unwind: false, + ptrauth_type_discriminator: 0, }; fn_abi.adjust_for_foreign_abi(self.cx, ExternAbi::C { unwind: false }); diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index af6c24018028a..636519cff35ec 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -1542,6 +1542,30 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { let cold_inline = llvm::AttributeKind::Cold.create_attr(self.llcx); attributes::apply_to_callsite(llret, llvm::AttributePlace::Function, &[cold_inline]); } + + fn ptrauth_resign( + &mut self, + value: &'ll Value, + old_key: u32, + old_discriminator: u64, + new_key: u32, + new_discriminator: u64, + ) -> &'ll Value { + let ptr_as_int = self.ptrtoint(value, self.type_i64()); + let resigned_int = self.call_intrinsic( + "llvm.ptrauth.resign", + &[], + &[ + ptr_as_int, + self.const_i32(old_key as i32), + self.const_i64(old_discriminator as i64), + self.const_i32(new_key as i32), + self.const_i64(new_discriminator as i64), + ], + ); + + self.inttoptr(resigned_int, self.val_ty(value)) + } } impl<'ll> StaticBuilderMethods for Builder<'_, 'll, '_> { @@ -2059,8 +2083,14 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { // bundles. // Once this is resolved, we should analyze each call and skip direct calls. See the // discussion in the rust-lang issue: - let key: u32 = 0; - let discriminator: u64 = 0; + + let key: u32 = self.sess().pointer_authentication_fn_ptr_key().unwrap() as u32; + let discriminator = if self.sess().pointer_authentication_fn_ptr_type_discrimination() { + fn_abi?.ptrauth_type_discriminator + } else { + 0 + }; + Some(llvm::OperandBundleBox::new( "ptrauth", &[self.const_u32(key), self.const_u64(discriminator)], diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs index 205e2e9a14701..af9846e281038 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -30,11 +30,9 @@ pub(crate) fn maybe_sign_fn_ptr<'ll, 'tcx>( cx: &CodegenCx<'ll, '_>, instance: Instance<'tcx>, llfn: &'ll llvm::Value, - schema: &PointerAuthSchema, + schema: PointerAuthSchema, ) -> &'ll llvm::Value { - if cx.tcx.sess.pointer_authentication_functions().is_none() { - return llfn; - } + assert!(cx.tcx.sess.pointer_authentication_functions().is_some()); // Only free functions or methods let def_id = instance.def_id(); @@ -317,7 +315,7 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { cv: Scalar, layout: abi::Scalar, llty: &'ll Type, - schema: Option<&PointerAuthSchema>, + schema: Option, ) -> &'ll Value { let bitsize = if layout.is_bool() { 1 } else { layout.size(self).bits() }; match cv { @@ -352,6 +350,7 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { alloc.inner(), IsStatic::No, IsInitOrFini::No, + None, ); let alloc = alloc.inner(); let value = match alloc.mutability { @@ -389,6 +388,7 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { alloc.inner(), IsStatic::No, IsInitOrFini::No, + None, ); self.static_addr_of_impl(init, alloc.inner().align, None) } diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs index 8f87acaf675a4..9e515296515ee 100644 --- a/compiler/rustc_codegen_llvm/src/consts.rs +++ b/compiler/rustc_codegen_llvm/src/consts.rs @@ -3,6 +3,7 @@ use std::ops::Range; use rustc_abi::{Align, ExternAbi, HasDataLayout, Primitive, Scalar, Size, WrappingRange}; use rustc_codegen_ssa::common; use rustc_codegen_ssa::traits::*; +use rustc_data_structures::fx::FxHashMap; use rustc_hir::LangItem; use rustc_hir::attrs::Linkage; use rustc_hir::def::DefKind; @@ -13,8 +14,9 @@ use rustc_middle::mir::interpret::{ read_target_uint, }; use rustc_middle::mono::MonoItem; +use rustc_middle::ptrauth::compute_fn_ptr_type_discriminator_for; use rustc_middle::ty::layout::{HasTypingEnv, LayoutOf}; -use rustc_middle::ty::{self, Instance}; +use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; use rustc_middle::{bug, span_bug}; use rustc_span::Symbol; use rustc_target::spec::Arch; @@ -37,11 +39,152 @@ pub(crate) enum IsInitOrFini { Yes, No, } + +/// Maps offsets within a static allocation to the function pointer +/// discriminator that should be applied when authenticating the relocation +/// emitted at that offset. +/// +/// Offsets are relative to the beginning of the allocation. +pub(crate) struct FnPtrDiscriminatorAtOffset { + map: FxHashMap, +} + +/// Recursively walks a type layout and records the offsets of all extern "C" +/// function pointer fields together with their computed type discriminators. +/// +/// Traversal currently supports: +/// - direct function pointers +/// - transparent wrappers +/// - structs +/// - tuples +/// - arrays +/// +/// Offsets are accumulated relative to the containing object. +fn collect_fn_ptr_discriminators<'tcx>( + tcx: TyCtxt<'tcx>, + typing_env: ty::TypingEnv<'tcx>, + ty: Ty<'tcx>, +) -> FnPtrDiscriminatorAtOffset { + let mut map = FxHashMap::default(); + + collect_fn_ptr_discriminators_inner(tcx, typing_env, ty, Size::ZERO, &mut map); + + FnPtrDiscriminatorAtOffset { map } +} + +fn collect_fn_ptr_discriminators_inner<'tcx>( + tcx: TyCtxt<'tcx>, + typing_env: ty::TypingEnv<'tcx>, + ty: Ty<'tcx>, + base_offset: Size, + map: &mut FxHashMap, +) { + // Direct function pointer. + if let Some(disc) = compute_fn_ptr_type_discriminator_for(tcx, ty) { + map.insert(base_offset, disc.into()); + + return; + } + + match ty.kind() { + ty::Adt(def, args) if def.repr().transparent() => { + let variant = def.non_enum_variant(); + + let Some((_, field)) = variant.fields.iter_enumerated().next() else { + return; + }; + + let field_ty = tcx.normalize_erasing_regions(typing_env, field.ty(tcx, args)); + + collect_fn_ptr_discriminators_inner(tcx, typing_env, field_ty, base_offset, map); + } + ty::Adt(def, args) if def.is_struct() => { + let Ok(layout) = tcx.layout_of(typing_env.as_query_input(ty)) else { + return; + }; + + let variant = def.non_enum_variant(); + + for (idx, field_def) in variant.fields.iter_enumerated() { + let field_ty = tcx.normalize_erasing_regions(typing_env, field_def.ty(tcx, args)); + + let field_offset = layout.fields.offset(idx.into()); + + collect_fn_ptr_discriminators_inner( + tcx, + typing_env, + field_ty, + base_offset + field_offset, + map, + ); + } + } + ty::Tuple(fields) => { + let Ok(layout) = tcx.layout_of(typing_env.as_query_input(ty)) else { + return; + }; + + for (idx, field_ty) in fields.iter().enumerate() { + let field_offset = layout.fields.offset(idx); + + collect_fn_ptr_discriminators_inner( + tcx, + typing_env, + field_ty, + base_offset + field_offset, + map, + ); + } + } + ty::Array(elem_ty, len) => { + let count = match len.try_to_target_usize(tcx) { + Some(v) => v, + None => return, + }; + + let Ok(elem_layout) = tcx.layout_of(typing_env.as_query_input(*elem_ty)) else { + return; + }; + + let stride = elem_layout.size; + + // Collect discriminator of one element, so we don't have to recompute it for all the + // elements in the array. + let mut elem_map = FxHashMap::default(); + + collect_fn_ptr_discriminators_inner( + tcx, + typing_env, + *elem_ty, + Size::ZERO, + &mut elem_map, + ); + + // SAFETY: We immediately collect into a Vec and sort by offset. + // The HashMap iteration order is irrelevant and must not affect determinism. + #[allow(rustc::potential_query_instability)] + let mut entries: Vec<(Size, u64)> = elem_map.into_iter().collect(); + entries.sort_unstable_by_key(|(offset, _)| *offset); + + // Replicate for every array slot. + for i in 0..count { + let elem_base = base_offset + stride * i; + + for (inner_offset, discr) in entries.iter().copied() { + map.insert(elem_base + inner_offset, discr); + } + } + } + _ => {} + } +} + pub(crate) fn const_alloc_to_llvm<'ll>( cx: &CodegenCx<'ll, '_>, alloc: &Allocation, is_static: IsStatic, is_init_fini: IsInitOrFini, + fn_ptr_discriminators: Option<&FnPtrDiscriminatorAtOffset>, ) -> &'ll Value { // We expect that callers of const_alloc_to_llvm will instead directly codegen a pointer or // integer for any &ZST where the ZST is a constant (i.e. not a static). We should never be @@ -121,7 +264,7 @@ pub(crate) fn const_alloc_to_llvm<'ll>( as u64; let address_space = cx.tcx.global_alloc(prov.alloc_id()).address_space(cx); - let schema = if cx.sess().pointer_authentication() { + let mut schema = if cx.sess().pointer_authentication() { match is_init_fini { IsInitOrFini::Yes => cx.sess().pointer_authentication_init_fini(), IsInitOrFini::No => cx.sess().pointer_authentication_functions(), @@ -129,6 +272,16 @@ pub(crate) fn const_alloc_to_llvm<'ll>( } else { None }; + let discr = fn_ptr_discriminators + .as_ref() + .and_then(|m| m.map.get(&Size::from_bytes(offset as u64))); + + // Init/fini entries must not participate in function pointer type discrimination. + if let (Some(schema), Some(discr)) = (schema.as_mut(), discr) + && is_init_fini == IsInitOrFini::No + { + schema.constant_discriminator = *discr as u16; + } llvals.push(cx.scalar_to_backend_with_pac( InterpScalar::from_pointer(Pointer::new(prov, Size::from_bytes(ptr_offset)), &cx.tcx), Scalar::Initialized { @@ -160,6 +313,15 @@ fn codegen_static_initializer<'ll, 'tcx>( cx: &CodegenCx<'ll, 'tcx>, def_id: DefId, ) -> Result<(&'ll Value, ConstAllocation<'tcx>), ErrorHandled> { + let fn_ptr_discriminators = if cx.sess().pointer_authentication_fn_ptr_type_discrimination() { + let instance = Instance::mono(cx.tcx, def_id); + let ty = instance.ty(cx.tcx, cx.typing_env()); + + Some(collect_fn_ptr_discriminators(cx.tcx, cx.typing_env(), ty)) + } else { + None + }; + let alloc = cx.tcx.eval_static_initializer(def_id)?; let attrs = cx.tcx.codegen_fn_attrs(def_id); // FIXME(jchlanda) Decide if this could be better served by `ctor` crate. See the discussion @@ -175,7 +337,16 @@ fn codegen_static_initializer<'ll, 'tcx>( } }) .unwrap_or(IsInitOrFini::No); - Ok((const_alloc_to_llvm(cx, alloc.inner(), IsStatic::Yes, is_in_init_fini), alloc)) + Ok(( + const_alloc_to_llvm( + cx, + alloc.inner(), + IsStatic::Yes, + is_in_init_fini, + fn_ptr_discriminators.as_ref(), + ), + alloc, + )) } fn set_global_alignment<'ll>(cx: &CodegenCx<'ll, '_>, gv: &'ll Value, mut align: Align) { @@ -837,7 +1008,7 @@ impl<'ll> StaticCodegenMethods for CodegenCx<'ll, '_> { fn static_addr_of(&self, alloc: ConstAllocation<'_>, kind: Option<&str>) -> &'ll Value { // FIXME: should we cache `const_alloc_to_llvm` to avoid repeating this for the // same `ConstAllocation`? - let cv = const_alloc_to_llvm(self, alloc.inner(), IsStatic::No, IsInitOrFini::No); + let cv = const_alloc_to_llvm(self, alloc.inner(), IsStatic::No, IsInitOrFini::No, None); let gv = self.static_addr_of_impl(cv, alloc.inner().align, kind); // static_addr_of_impl returns the bare global variable, which might not be in the default diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 7ea30a5b4db6d..f77cc011ae45d 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -913,7 +913,7 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { fn get_fn_addr( &self, instance: Instance<'tcx>, - pointer_auth_schema: Option<&PointerAuthSchema>, + pointer_auth_schema: Option, ) -> &'ll Value { // When pointer authentication metadata is provided, `get_fn_addr` will // attempt to sign the pointer using LLVM's `ConstPtrAuth` constant @@ -973,6 +973,16 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { let tcx = self.tcx; let llfn = match tcx.lang_items().eh_personality() { + // We intentionally do NOT apply pointer authentication (and/or function type + // discriminators to the EH personality function). + // + // Although `get_fn_addr` normally produces a signed function pointer for + // externally-callable functions, the EH personality is not an indirect call + // target in the SSA sense. Instead, it is a compile-time constant attached to + // the Function object (via LLVM's `setPersonalityFn`) and consumed only by + // exception handling metadata generation (landing pads / unwind tables). + // LLVM never loads or invokes the personality via a function pointer value; + // it is not part of the program's call graph or data flow. Some(def_id) if name.is_none() => self.get_fn_addr( ty::Instance::expect_resolve( tcx, @@ -981,7 +991,7 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { ty::List::empty(), DUMMY_SP, ), - tcx.sess.pointer_authentication_functions(), + None, ), _ => { let name = name.unwrap_or("rust_eh_personality"); diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index 43d5e312c6b40..932ceef3e8fa8 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -493,8 +493,11 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( // We want to create the wrapper only when the codegen unit is the primary one return None; } - - let main_llfn = cx.get_fn_addr(instance, cx.sess().pointer_authentication_functions()); + // No function pointer signing / type discriminator is needed here. Although `get_fn_addr` is + // used to obtain function pointers, both the user's `main` and `LangItem::Start` use the Rust + // ABI (currently pointer authentication is only supported for C/System ABI). The same applies + // to the logic in `create_entry_fn` further below. + let main_llfn = cx.get_fn_addr(instance, None); let entry_fn = create_entry_fn::(cx, main_llfn, main_def_id, entry_type); return Some(entry_fn); @@ -555,8 +558,8 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( cx.tcx().mk_args(&[main_ret_ty.into()]), DUMMY_SP, ); - let start_fn = - cx.get_fn_addr(start_instance, cx.sess().pointer_authentication_functions()); + + let start_fn = cx.get_fn_addr(start_instance, None); let i8_ty = cx.type_i8(); let arg_sigpipe = bx.const_u8(sigpipe); diff --git a/compiler/rustc_codegen_ssa/src/common.rs b/compiler/rustc_codegen_ssa/src/common.rs index ae72258a87c86..512ac56eeac51 100644 --- a/compiler/rustc_codegen_ssa/src/common.rs +++ b/compiler/rustc_codegen_ssa/src/common.rs @@ -2,6 +2,7 @@ use rustc_hir::LangItem; use rustc_hir::attrs::PeImportNameType; +use rustc_middle::ptrauth::clone_discriminated_ptrauth_schema_for; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::ty::{self, Instance, TyCtxt}; use rustc_middle::{bug, mir, span_bug}; @@ -117,11 +118,20 @@ pub(crate) fn build_langcall<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( let tcx = bx.tcx(); let def_id = tcx.require_lang_item(li, span); let instance = ty::Instance::mono(tcx, def_id); - ( - bx.fn_abi_of_instance(instance, ty::List::empty()), - bx.get_fn_addr(instance, tcx.sess.pointer_authentication_functions()), - instance, - ) + + let schema = if bx.sess().pointer_authentication_fn_ptr_type_discrimination() { + // It is unlikely that any of LangItem will follow the extern C/System ABI, but it future + // proofs the implementation. + clone_discriminated_ptrauth_schema_for( + bx.tcx(), + bx.sess().pointer_authentication_functions(), + instance, + ) + } else { + bx.sess().pointer_authentication_functions().clone() + }; + + (bx.fn_abi_of_instance(instance, ty::List::empty()), bx.get_fn_addr(instance, schema), instance) } pub(crate) fn shift_mask_val<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>( diff --git a/compiler/rustc_codegen_ssa/src/mir/block.rs b/compiler/rustc_codegen_ssa/src/mir/block.rs index fba0cff0e6e14..92299636ed585 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -11,6 +11,7 @@ use rustc_hir::attrs::AttributeKind; use rustc_hir::lang_items::LangItem; use rustc_lint_defs::builtin::TAIL_CALL_TRACK_CALLER; use rustc_middle::mir::{self, AssertKind, InlineAsmMacro, SwitchTargets, UnwindTerminateReason}; +use rustc_middle::ptrauth::clone_discriminated_ptrauth_schema_for; use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, ValidityRequirement}; use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths}; use rustc_middle::ty::{self, Instance, Ty, TypeVisitableExt}; @@ -684,12 +685,23 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { virtual_drop, ) } - _ => ( - false, - bx.get_fn_addr(drop_fn, bx.sess().pointer_authentication_functions()), - bx.fn_abi_of_instance(drop_fn, ty::List::empty()), - drop_fn, - ), + _ => { + let schema = if bx.sess().pointer_authentication_fn_ptr_type_discrimination() { + clone_discriminated_ptrauth_schema_for( + bx.tcx(), + bx.sess().pointer_authentication_functions(), + drop_fn, + ) + } else { + bx.sess().pointer_authentication_functions().clone() + }; + ( + false, + bx.get_fn_addr(drop_fn, schema), + bx.fn_abi_of_instance(drop_fn, ty::List::empty()), + drop_fn, + ) + } }; // We generate a null check for the drop_fn. This saves a bunch of relocations being @@ -1101,14 +1113,18 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { generic_args.no_bound_vars().unwrap(), ) .unwrap(); + let schema = + if bx.sess().pointer_authentication_fn_ptr_type_discrimination() { + clone_discriminated_ptrauth_schema_for( + bx.tcx(), + bx.sess().pointer_authentication_functions(), + instance, + ) + } else { + bx.sess().pointer_authentication_functions().clone() + }; - ( - None, - Some(bx.get_fn_addr( - instance, - bx.sess().pointer_authentication_functions(), - )), - ) + (None, Some(bx.get_fn_addr(instance, schema))) } _ => (Some(instance), None), } @@ -1422,7 +1438,17 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let fn_ptr = match (instance, llfn) { (Some(instance), None) => { - bx.get_fn_addr(instance, bx.sess().pointer_authentication_functions()) + let schema = if bx.sess().pointer_authentication_fn_ptr_type_discrimination() { + clone_discriminated_ptrauth_schema_for( + bx.tcx(), + bx.sess().pointer_authentication_functions(), + instance, + ) + } else { + bx.sess().pointer_authentication_functions().clone() + }; + + bx.get_fn_addr(instance, schema) } (_, Some(llfn)) => llfn, _ => span_bug!(fn_span, "no instance or llfn for call"), diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 077d724997441..13565d3c88572 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -1,6 +1,9 @@ use itertools::Itertools as _; -use rustc_abi::{self as abi, BackendRepr, FIRST_VARIANT}; +use rustc_abi::{self as abi, BackendRepr, ExternAbi, FIRST_VARIANT}; use rustc_index::IndexVec; +use rustc_middle::ptrauth::{ + clone_discriminated_ptrauth_schema_for, compute_fn_ptr_type_discriminator_for, +}; use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::layout::{HasTyCtxt, HasTypingEnv, LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, Instance, Mutability, Ty, TyCtxt}; @@ -15,6 +18,13 @@ use crate::common::{IntPredicate, TypeKind}; use crate::traits::*; use crate::{MemFlags, base}; +/// Type metadata used when applying pointer authentication semantics during +/// transmute lowering. +struct TransmuteInfo<'tcx> { + src_ty: Ty<'tcx>, + dst_ty: Ty<'tcx>, +} + impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { fn try_codegen_const_aggregate_as_immediate( &mut self, @@ -89,6 +99,125 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { true } + /// Applies pointer-authentication-aware semantic transmute, that is + /// ensuring that when a function pointer is transmuted between two types + /// that map to different authentication domains (discriminators), the + /// resulting pointer is re-signed appropriately. + /// + /// Only SSA `OperandValue::Immediate` values are eligible for this path. + fn codegen_semantic_transmute_operand( + &mut self, + bx: &mut Bx, + operand: OperandRef<'tcx, Bx::Value>, + cast: TyAndLayout<'tcx>, + ) -> OperandValue { + let val = self.codegen_transmute_operand(bx, operand, cast); + + let OperandValue::Immediate(ptr) = val else { + return val; + }; + + let info = TransmuteInfo { src_ty: operand.layout.ty, dst_ty: cast.ty }; + + OperandValue::Immediate(self.resign_transmuted_fn_ptr(bx, ptr, info)) + } + + /// Applies pointer-authentication domain correction for a function pointer + /// value being transmuted between two types. + /// + /// The "domain" is defined by the function pointer type discriminator. If + /// the source and destination types map to different discriminator values, + /// the pointer must be re-signed using `llvm.ptrauth.resign` intrinsic. + /// + /// A discriminator value of `0` is used to represent non-function-pointer + /// or "raw pointer domain" values. + /// ```text + // static mut CPTR: *const u8 = 0 as *const u8; + // ... = mem::transmute::<*const u8, unsafe extern "C" fn()>(CPTR); + /// ``` + /// where the source has no authentication domain. + fn resign_transmuted_fn_ptr( + &mut self, + bx: &mut Bx, + val: Bx::Value, + info: TransmuteInfo<'tcx>, + ) -> Bx::Value { + let tcx = bx.tcx(); + + let src_disc = compute_fn_ptr_type_discriminator_for(tcx, info.src_ty).unwrap_or(0); + let dst_disc = compute_fn_ptr_type_discriminator_for(tcx, info.dst_ty).unwrap_or(0); + + if src_disc == dst_disc { + return val; + } + + debug!("resign_transmuted_fn_ptr\t{:#x} -> {:#x}", src_disc, dst_disc); + + let key = self.cx.tcx().sess.pointer_authentication_fn_ptr_key().unwrap() as u32; + bx.ptrauth_resign(val, key, src_disc.into(), key, dst_disc.into()) + } + + /// Walks through `#[repr(transparent)]` wrappers to find an underlying + /// function pointer or function definition. + /// + /// Returns the corresponding layout if one is found, otherwise `None`. + fn transparent_fn_ptr_layout( + &self, + mut layout: TyAndLayout<'tcx>, + ) -> Option> { + loop { + match layout.ty.kind() { + ty::FnPtr(..) | ty::FnDef(..) => return Some(layout), + + ty::Adt(def, _) if def.repr().transparent() => { + layout = layout.field(self.cx, 0); + } + + _ => return None, + } + } + } + + /// Applies pointer-authentication domain change during a transmute into a + /// memory-backed place. + /// + /// Unlike the operand version, this path handles values stored in memory + /// and therefore must unwrap #[repr(transparent)] wrapper types so that pointer + /// authentication is based on the underlying function pointer type. + /// + /// Only immediate values are subject to ptrauth adjustment; other + /// representations are passed through unchanged. + fn codegen_semantic_transmute_place( + &mut self, + bx: &mut Bx, + src: OperandRef<'tcx, Bx::Value>, + dst: PlaceRef<'tcx, Bx::Value>, + ) { + debug!( + "codegen_semantic_transmute_place\tsrc={:?}, dst={:?}", + src.layout.ty.kind(), + dst.layout.ty.kind() + ); + + let info = TransmuteInfo { + src_ty: self.transparent_fn_ptr_layout(src.layout).map_or(src.layout.ty, |l| l.ty), + dst_ty: self.transparent_fn_ptr_layout(dst.layout).map_or(dst.layout.ty, |l| l.ty), + }; + + let dest = dst.val.with_type(src.layout); + + let val = match src.val { + OperandValue::Immediate(v) => { + let v = self.resign_transmuted_fn_ptr(bx, v, info); + OperandValue::Immediate(v) + } + other => other, + }; + + OperandRef { val, layout: src.layout, move_annotation: None } + .store_with_annotation(bx, dest); + } + #[instrument(level = "trace", skip(self, bx))] pub(crate) fn codegen_rvalue( &mut self, @@ -180,8 +309,25 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { mir::Rvalue::Cast( mir::CastKind::Transmute | mir::CastKind::Subtype, ref operand, - _ty, + ty, ) => { + if self.cx.tcx().sess.pointer_authentication_fn_ptr_type_discrimination() { + let src_ty = operand.ty(self.mir, self.cx.tcx()); + let dst_ty = self.monomorphize(ty); + + if src_ty.is_fn_ptr() || dst_ty.is_fn_ptr() { + let op = self.codegen_operand(bx, operand); + let cast = bx.cx().layout_of(dst_ty); + + let val = self.codegen_semantic_transmute_operand(bx, op, cast); + + OperandRef { val, layout: cast, move_annotation: None } + .store_with_annotation(bx, dest); + + return; + } + } + let src = self.codegen_operand(bx, operand); self.codegen_transmute(bx, src, dest); } @@ -316,7 +462,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { // Since in this path we have a place anyway, we can store or copy to it, // making sure we use the destination place's alignment even if the // source would normally have a higher one. - src.store_with_annotation(bx, dst.val.with_type(src.layout)); + + if self.cx.tcx().sess.pointer_authentication_fn_ptr_type_discrimination() { + self.codegen_semantic_transmute_place(bx, src, dst); + } else { + src.store_with_annotation(bx, dst.val.with_type(src.layout)); + } } } @@ -330,6 +481,16 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { operand: OperandRef<'tcx, Bx::Value>, cast: TyAndLayout<'tcx>, ) -> OperandValue { + debug!( + "codegen_transmute_operand\t + from_ty={:?} to_ty={:?} from_layout={:?} to_layout={:?} is fnptr=({}, {})", + operand.layout.ty, + cast.ty, + operand.layout.backend_repr, + cast.backend_repr, + operand.layout.ty.is_fn_ptr(), + cast.ty.is_fn_ptr() + ); if let abi::BackendRepr::Memory { .. } = cast.backend_repr && !cast.is_zst() { @@ -515,12 +676,18 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { args.no_bound_vars().unwrap(), ) .unwrap(); - OperandValue::Immediate( - bx.get_fn_addr( - instance, - bx.sess().pointer_authentication_functions(), - ), + + let schema = if bx.sess().pointer_authentication_fn_ptr_type_discrimination() { + clone_discriminated_ptrauth_schema_for( + bx.tcx(), + bx.sess().pointer_authentication_functions(), + operand.layout.ty, ) + } else { + bx.sess().pointer_authentication_functions().clone() + }; + + OperandValue::Immediate(bx.get_fn_addr(instance, schema)) } _ => bug!("{} cannot be reified to a fn ptr", operand.layout.ty), } @@ -534,10 +701,20 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { args, ty::ClosureKind::FnOnce, ); + assert!( + !matches!( + bx.cx().tcx().fn_sig(instance.def_id()).skip_binder().abi(), + ExternAbi::C { .. } | ExternAbi::System { .. } + ) + ); OperandValue::Immediate( + // A closure coerced to a function pointer retains the Rust + // ABI. Pointer authentication only applies to extern + // "C"/System ABI function pointer, hence pass None to + // `get_fn_addr`. bx.cx().get_fn_addr( instance, - bx.sess().pointer_authentication_functions(), + None, ), ) } @@ -614,7 +791,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { }) } mir::CastKind::Transmute | mir::CastKind::Subtype => { - self.codegen_transmute_operand(bx, operand, cast) + if self.cx.tcx().sess.pointer_authentication_fn_ptr_type_discrimination() { + self.codegen_semantic_transmute_operand(bx, operand, cast) + } else { + self.codegen_transmute_operand(bx, operand, cast) + } } }; OperandRef { val, layout: cast, move_annotation: None } @@ -759,8 +940,10 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { def: ty::InstanceKind::Shim(ty::ShimKind::ThreadLocal(def_id)), args: ty::GenericArgs::empty(), }; - let fn_ptr = - bx.get_fn_addr(instance, bx.sess().pointer_authentication_functions()); + // This is the address of a compiler-generated TLS shim function. It is not an + // externally visible function pointer and does not require function pointer + // authentication signing. + let fn_ptr = bx.get_fn_addr(instance, None); let fn_abi = bx.fn_abi_of_instance(instance, ty::List::empty()); let fn_ty = bx.fn_decl_backend_type(fn_abi); let fn_attrs = if bx.tcx().def_kind(instance.def_id()).has_codegen_attrs() { diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs index 0d27ff90991b3..1f455221507be 100644 --- a/compiler/rustc_codegen_ssa/src/traits/builder.rs +++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs @@ -667,4 +667,13 @@ pub trait BuilderMethods<'a, 'tcx>: fn zext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value; fn apply_attrs_to_cleanup_callsite(&mut self, llret: Self::Value); + + fn ptrauth_resign( + &mut self, + value: Self::Value, + old_key: u32, + old_discriminator: u64, + new_key: u32, + new_discriminator: u64, + ) -> Self::Value; } diff --git a/compiler/rustc_codegen_ssa/src/traits/consts.rs b/compiler/rustc_codegen_ssa/src/traits/consts.rs index b4eba38d39c19..ad5c4443f2dbc 100644 --- a/compiler/rustc_codegen_ssa/src/traits/consts.rs +++ b/compiler/rustc_codegen_ssa/src/traits/consts.rs @@ -47,7 +47,7 @@ pub trait ConstCodegenMethods: BackendTypes { cv: Scalar, layout: abi::Scalar, llty: Self::Type, - schema: Option<&PointerAuthSchema>, + schema: Option, ) -> Self::Value; fn const_ptr_byte_offset(&self, val: Self::Value, offset: abi::Size) -> Self::Value; diff --git a/compiler/rustc_codegen_ssa/src/traits/misc.rs b/compiler/rustc_codegen_ssa/src/traits/misc.rs index add7128a2974b..6589cd219885b 100644 --- a/compiler/rustc_codegen_ssa/src/traits/misc.rs +++ b/compiler/rustc_codegen_ssa/src/traits/misc.rs @@ -22,7 +22,7 @@ pub trait MiscCodegenMethods<'tcx>: BackendTypes { fn get_fn_addr( &self, instance: Instance<'tcx>, - pointer_auth_schema: Option<&PointerAuthSchema>, + pointer_auth_schema: Option, ) -> Self::Value; fn eh_personality(&self) -> Self::Function; fn sess(&self) -> &Session; diff --git a/compiler/rustc_middle/src/lib.rs b/compiler/rustc_middle/src/lib.rs index 69c2e099080c9..9798130e69ef0 100644 --- a/compiler/rustc_middle/src/lib.rs +++ b/compiler/rustc_middle/src/lib.rs @@ -80,6 +80,7 @@ pub mod metadata; pub mod middle; pub mod mir; pub mod mono; +pub mod ptrauth; pub mod queries; pub mod query; pub mod thir; diff --git a/compiler/rustc_middle/src/ptrauth/discriminator.rs b/compiler/rustc_middle/src/ptrauth/discriminator.rs new file mode 100644 index 0000000000000..76d7fe9b1f0ea --- /dev/null +++ b/compiler/rustc_middle/src/ptrauth/discriminator.rs @@ -0,0 +1,493 @@ +/*! +Function pointer type discrimination for pointer authentication. + +This module implements Rust's equivalent of Clang's function pointer type +discriminator computation used in pointer authentication. + +Compatibility with Clang is a primary goal. The discriminator produced for a +given external "C" function type must match the value computed by Clang so that +function pointers can be exchanged safely between Rust and C code while +preserving pointer authentication semantics. + +The implementation mirrors Clang's behavior in +`ASTContext::encodeTypeForFunctionPointerAuth`, ensuring that identical +C-compatible function types produce identical discriminators. See: +. + +## Overview + +The computation is structured into three conceptual stages: + +### 1. Type normalization and lowering + Rust types are converted into a language-independent representation + (`ClangDiscTy`) that mirrors the type categories used by Clang when computing + function pointer discriminators. This includes canonicalization such as + treating all pointer-like types uniformly and mapping Rust constructs onto + their closest C equivalents. + One notable exception is C `_Complex`. Rust has no corresponding native type, + so there is no canonical Rust representation to map onto Clang's `_Complex` + type category. Rather than infer one (for example, by treating `(f32, f32)` + or `(f64, f64)` as complex numbers), this implementation leaves such + representation choices to users and does not provide dedicated `_Complex` + encoding. + +### 2. Type encoding + The lowered representation is serialized into a byte stream using rules + intended to match Clang's implementation in: + `encodeTypeForFunctionPointerAuth`. The resulting encoding describes the + function signature in a target-independent form suitable for hashing. + +### 3. Discriminator hashing + The encoded byte stream is hashed using LLVM's stable SipHash-2-4 based + discriminator algorithm. The implementation here is a direct translation + of LLVM/Clang's logic and must remain bit-for-bit compatible. See: + . + Defined in `llvm_siphash.rs`. + +## Module structure + +- High-level API + - `FnPtrDiscriminatorSource` + - `compute_fn_ptr_type_discriminator_for` + - `clone_discriminated_ptrauth_schema_for` + +- Low-level API + - `FnPtrTypeDiscriminatorInput` + - `compute_fn_ptr_type_discriminator` + +- Signature extraction + - `extract_fn_ptr_type` + +- Clang-compatible type model + - `ClangDiscTy` + - `canonicalize_c_type` + - `to_clang_disc_ty` + +- Encoding + - `PtrauthEncoder` + - `encode_ty` + +## Compatibility requirements + +Any changes to the encoding or hashing logic should be validated against Clang's +discriminator computation. Divergence from Clang will result in incompatible +pointer authentication values across language boundaries. + +This implementation intentionally approximates Clang's behavior for extern "C" +function types only. It does NOT attempt to model full type system rules. +*/ + +use rustc_abi::ExternAbi; +use rustc_middle::ty::{self, Instance, Ty, TyCtxt, Unnormalized}; +use rustc_session::PointerAuthSchema; +use rustc_span::sym; + +use crate::ptrauth::llvm_siphash::llvm_pointer_auth_stable_siphash; + +/// Types that can serve as a source for function pointer type discrimination. +/// +/// This trait abstracts over the different compiler representations from which +/// a function signature can be obtained. Implementations construct the +/// canonical `FnPtrTypeDiscriminatorInput` consumed by the discriminator +/// computation. +/// +/// This is intended primarily for ergonomic use at call sites, allowing code +/// to compute discriminators directly from an `Instance`, `Ty`, or `FnSig` +/// without manually constructing the intermediate representation. +pub trait FnPtrDiscriminatorSource<'tcx>: Sized { + fn discriminator_input(self, tcx: TyCtxt<'tcx>) -> Option>; +} + +/// Enables discriminator computation directly from Rust function types. +/// +/// Accepts both: +/// - `FnPtr`: actual function pointer types +/// - `FnDef`: function items +/// +/// FnDef is only accepted for convenience; the discriminator is still computed +/// from the instantiated function signature. +impl<'tcx> FnPtrDiscriminatorSource<'tcx> for Ty<'tcx> { + fn discriminator_input(self, tcx: TyCtxt<'tcx>) -> Option> { + let ty = extract_fn_ptr_type(tcx, self)?; + + match ty.kind() { + ty::FnPtr(sig, header) => { + let sig = sig.skip_binder(); + Some(FnPtrTypeDiscriminatorInput::from_sig_tys(sig, header)) + } + + ty::FnDef(def_id, args) => { + let sig = tcx.fn_sig(*def_id).instantiate(tcx, args.skip_binder()).skip_binder(); + + Some(FnPtrTypeDiscriminatorInput::from_sig(sig)) + } + + _ => None, + } + } +} +/// Enables discriminator computation directly from monomorphized function +/// instances. +/// +/// The instance's signature is instantiated using its generic arguments and +/// normalized before constructing the canonical discriminator input. +impl<'tcx> FnPtrDiscriminatorSource<'tcx> for Instance<'tcx> { + fn discriminator_input(self, tcx: TyCtxt<'tcx>) -> Option> { + let sig = tcx + .instantiate_and_normalize_erasing_regions( + self.args, + ty::TypingEnv::fully_monomorphized(), + tcx.fn_sig(self.def_id()), + ) + .skip_binder(); + + Some(FnPtrTypeDiscriminatorInput::from_sig(sig)) + } +} +/// Enables discriminator computation directly from instantiated function +/// signatures. +/// +/// The signature is assumed to already be instantiated and normalized. +impl<'tcx> FnPtrDiscriminatorSource<'tcx> for ty::FnSig<'tcx> { + fn discriminator_input(self, _: TyCtxt<'tcx>) -> Option> { + Some(FnPtrTypeDiscriminatorInput::from_sig(self)) + } +} + +/// Computes the function pointer type discriminator directly from a supported +/// source. +/// +/// This is a convenience wrapper around +/// `FnPtrDiscriminatorSource::discriminator_input` and +/// `compute_fn_ptr_type_discriminator`. +/// +/// Returns `None` if the supplied source does not represent a function pointer +/// type (for example, a non-function `Ty`). +pub fn compute_fn_ptr_type_discriminator_for<'tcx, S>(tcx: TyCtxt<'tcx>, source: S) -> Option +where + S: FnPtrDiscriminatorSource<'tcx>, +{ + let input = source.discriminator_input(tcx)?; + Some(compute_fn_ptr_type_discriminator(tcx, &input)) +} + +/// Clones a pointer authentication schema and updates its constant +/// discriminator. +/// +/// If `schema` is `Some`, the function computes a function pointer type +/// discriminator from `source` and stores it in the cloned schema's +/// `constant_discriminator` field. +/// +/// If no discriminator can be computed (for example, because `source` does not +/// represent a function pointer type), the schema is returned unchanged. +/// +/// This is intended as a convenience helper for code generation sites that need +/// to attach function pointer type discrimination to a generic schema before +/// calling `get_fn_addr`. +pub fn clone_discriminated_ptrauth_schema_for<'tcx, S>( + tcx: TyCtxt<'tcx>, + mut schema: Option, + source: S, +) -> Option +where + S: FnPtrDiscriminatorSource<'tcx>, +{ + if let Some(ref mut s) = schema { + if let Some(disc) = compute_fn_ptr_type_discriminator_for(tcx, source) { + s.constant_discriminator = disc; + } + } + + schema +} + +/// Canonical representation of a function signature used for pointer +/// authentication discriminator generation. +#[derive(Debug)] +pub struct FnPtrTypeDiscriminatorInput<'tcx> { + inputs: &'tcx [Ty<'tcx>], + output: Ty<'tcx>, + abi: ExternAbi, + c_variadic: bool, +} + +impl<'tcx> FnPtrTypeDiscriminatorInput<'tcx> { + fn from_sig(sig: ty::FnSig<'tcx>) -> Self { + FnPtrTypeDiscriminatorInput { + inputs: sig.inputs(), + output: sig.output(), + abi: sig.abi(), + c_variadic: sig.c_variadic(), + } + } + + fn from_sig_tys(sig: ty::FnSigTys>, header: &ty::FnHeader>) -> Self { + FnPtrTypeDiscriminatorInput { + inputs: sig.inputs(), + output: sig.output(), + abi: header.abi(), + c_variadic: header.c_variadic(), + } + } +} + +/// Unwraps optional function pointers and normalizes the type. +/// +/// Only `Option` is supported for nullability modeling, matching C ABI +/// null pointer conventions. +fn extract_fn_ptr_type<'tcx>(tcx: TyCtxt<'tcx>, mut ty: Ty<'tcx>) -> Option> { + ty = tcx.normalize_erasing_regions(ty::TypingEnv::fully_monomorphized(), Unnormalized::new(ty)); + + loop { + match ty.kind() { + ty::Adt(def, args) if tcx.is_diagnostic_item(sym::Option, def.did()) => { + ty = args.type_at(0); + continue; + } + + ty::FnPtr(..) | ty::FnDef(..) => { + return Some(ty); + } + + _ => return None, + } + } +} + +/// Computes the Clang-compatible function pointer type discriminator. +/// +/// This is the low-level discriminator computation routine operating on an +/// already constructed `FnPtrTypeDiscriminatorInput`. +fn compute_fn_ptr_type_discriminator<'tcx>( + tcx: TyCtxt<'tcx>, + input: &FnPtrTypeDiscriminatorInput<'tcx>, +) -> u16 { + if !matches!(input.abi, ExternAbi::C { .. } | ExternAbi::System { .. }) { + return 0; + } + + let mut enc = PtrauthEncoder::new(); + enc.push(b'F'); + + encode_ty(&mut enc, tcx, input.output); + + for &arg in input.inputs { + encode_ty(&mut enc, tcx, arg); + } + + if input.c_variadic { + enc.push(b'z'); + } + + enc.push(b'E'); + + let hash = enc.finish(); + + hash.into() +} + +// Clang disc type. +#[derive(Debug)] +enum ClangDiscTy<'tcx> { + Int, + Float(&'tcx ty::FloatTy), + Bool, + Char, + + // Pointer-like types in the C ABI sense. + // This includes: + // - raw pointers (`*const T`, `*mut T`) + // - Rust references (`&T`, `&mut T`) + // - function pointers + // All collapse to a single Clang-compatible 'P' node. + Pointer, + + Array { elem: Ty<'tcx> }, + + // FIXME(jchlands) Decide if to support Complex types in future. Clang has + // dedicated node for this `Type::Complex`, Rust does not. So we could match + // against a Tuple(FP_TYPE, FP_TYPE). + // Complex(Ty<'tcx>), + Vector { bytes: u64 }, + + EnumLikeInt, + AdtName(String), + Opaque, + Void, +} + +// Canonicalize Option-wrapped pointer types used to model C nullable pointers. +// +// Rust and Clang should compute identical discriminators for equivalent C APIs. +// Clang does not distinguish nullable from non-nullable pointer types when +// computing function pointer authentication discriminators, so +// `Option` and `Option<*mut T>` are encoded identically to their +// underlying pointer types. +// +// Although `Option<*mut T>` is not considered FFI-safe by Rust and triggers the +// `improper_ctypes`/`improper_ctypes_definitions` lints, this is a warning +// rather than a hard error. Canonicalizing it here preserves Clang-compatible +// discriminator computation. +// +// Please see the following tests for sample use cases: +// pauth-fn-ptr-type-discrimination-option-callback.rs, +// pauth-fn-ptr-type-discrimination-option-return.rs and pauth-fn-ptr-type-discrimination-option.rs +fn canonicalize_c_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { + if let ty::Adt(def, args) = ty.kind() + && tcx.is_diagnostic_item(sym::Option, def.did()) + { + let inner = args.type_at(0); + + match inner.kind() { + ty::FnPtr(..) | ty::RawPtr(..) => return inner, + _ => {} + } + } + + ty +} + +/// Lowers a Rust type into a Clang-compatible discriminator type. +/// +/// This is not a full semantic translation of Rust types. It is a lossy mapping +/// that intentionally matches Clang's function pointer authentication encoding +/// rules. +/// This is not a full semantic translation of Rust types. It is a lossy mapping +/// that intentionally matches Clang's function pointer authentication encoding +/// rules where Rust has a direct language-level equivalent. +/// +/// In particular C `_Complex`, without a canonical Rust equivalent, is not +/// recognized. This avoids introducing heuristics for user-defined +/// representations that may vary across codebases. +/// +/// Important invariants: +/// - All pointer-like types (Rust refs, raw pointers, fn pointers) collapse to +/// `Pointer`. +/// - Struct/union types are encoded using name only, not layout. +/// - Enums are treated as integers. +/// - SIMD types are encoded only by total byte size (no lane semantics). +/// - No attempt is made to recognize user-defined representations of C +/// `_Complex` types. +/// This must remain in sync with Clang's `encodeTypeForFunctionPointerAuth`. +fn to_clang_disc_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ClangDiscTy<'tcx> { + let ty = canonicalize_c_type(tcx, ty); + match ty.kind() { + // C void / Rust () + ty::Tuple(list) if list.is_empty() => ClangDiscTy::Void, + + // scalars + ty::Bool => ClangDiscTy::Bool, + ty::Char => ClangDiscTy::Char, + + ty::Int(_) | ty::Uint(_) => ClangDiscTy::Int, + ty::Float(f) => ClangDiscTy::Float(f), + + // everything pointer-like collapses + ty::RawPtr(..) | ty::Ref(..) | ty::FnPtr(..) | ty::Dynamic(..) | ty::Slice(_) | ty::Str => { + ClangDiscTy::Pointer + } + + // arrays ignore size + ty::Array(elem, _) => ClangDiscTy::Array { elem: *elem }, + + // enums to integer collapse + ty::Adt(def, _) if def.is_enum() => ClangDiscTy::EnumLikeInt, + // simd vectors + ty::Adt(def, args) if def.repr().simd() => { + // Clang encodes SIMD vectors by their total size + let input = ty::PseudoCanonicalInput { + typing_env: ty::TypingEnv::fully_monomorphized(), + value: ty, + }; + + let Ok(layout) = tcx.layout_of(input) else { + tcx.dcx().delayed_bug("could not compute SIMD layout"); + return ClangDiscTy::Opaque; + }; + + let bytes = layout.size.bytes(); + + ClangDiscTy::Vector { bytes } + } + // structs/unions to name-based identity + ty::Adt(def, _) => { + let name = tcx.item_name(def.did()).to_string(); + ClangDiscTy::AdtName(name) + } + + ty::Foreign(_) => ClangDiscTy::Opaque, + + _ => ClangDiscTy::Opaque, + } +} + +// Encoder +struct PtrauthEncoder { + buf: Vec, +} + +impl PtrauthEncoder { + fn new() -> Self { + Self { buf: Vec::new() } + } + + fn push(&mut self, b: u8) { + self.buf.push(b); + } + + fn push_str(&mut self, s: &str) { + self.buf.extend_from_slice(s.as_bytes()); + } + + fn finish(&self) -> u16 { + llvm_pointer_auth_stable_siphash(&self.buf) + } +} + +/// Encodes a ClangDiscTy into the discriminator byte stream. +/// +/// This format is intended to be bit-for-bit compatible with Clang's +/// `encodeTypeForFunctionPointerAuth`. +fn encode_ty<'tcx>(enc: &mut PtrauthEncoder, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) { + let cty = to_clang_disc_ty(tcx, ty); + + match cty { + // scalars + ClangDiscTy::Bool | ClangDiscTy::Char | ClangDiscTy::Int => enc.push(b'i'), + + ClangDiscTy::Float(f) => match f.bit_width() { + 16 => enc.push_str("Dh"), + 32 => enc.push(b'f'), + 64 => enc.push(b'd'), + 128 => enc.push(b'g'), + _ => enc.push(b'?'), + }, + + ClangDiscTy::Void => enc.push(b'v'), + + // pointer boundary (NO RECURSION) + ClangDiscTy::Pointer => enc.push(b'P'), + + // arrays ignore size + ClangDiscTy::Array { elem } => { + enc.push(b'A'); + encode_ty(enc, tcx, elem); + } + + // enums collapse + ClangDiscTy::EnumLikeInt => enc.push(b'i'), + + // ADT identity + ClangDiscTy::AdtName(name) => { + enc.push_str(&name.len().to_string()); + enc.push_str(&name); + } + + ClangDiscTy::Opaque => enc.push(b'?'), + + ClangDiscTy::Vector { bytes } => { + enc.push_str("Dv"); + enc.push_str(&bytes.to_string()); + } + } +} diff --git a/compiler/rustc_middle/src/ptrauth/llvm_siphash.rs b/compiler/rustc_middle/src/ptrauth/llvm_siphash.rs new file mode 100644 index 0000000000000..68aeac11a0429 --- /dev/null +++ b/compiler/rustc_middle/src/ptrauth/llvm_siphash.rs @@ -0,0 +1,142 @@ +// LLVM SipHash-2-4 +pub fn llvm_pointer_auth_stable_siphash(data: &[u8]) -> u16 { + let raw = llvm_siphash_2_4_64(data); + + ((raw % 0xFFFF) + 1) as u16 +} + +const SIPHASH_KEY: [u8; 16] = [ + 0xb5, 0xd4, 0xc9, 0xeb, 0x79, 0x10, 0x4a, 0x79, 0x6f, 0xec, 0x8b, 0x1b, 0x42, 0x87, 0x81, 0xd4, +]; + +#[inline(always)] +fn rotl(x: u64, b: u32) -> u64 { + (x << b) | (x >> (64 - b)) +} + +#[inline(always)] +fn sipround(v0: &mut u64, v1: &mut u64, v2: &mut u64, v3: &mut u64) { + *v0 = v0.wrapping_add(*v1); + *v1 = rotl(*v1, 13); + *v1 ^= *v0; + *v0 = rotl(*v0, 32); + + *v2 = v2.wrapping_add(*v3); + *v3 = rotl(*v3, 16); + *v3 ^= *v2; + + *v0 = v0.wrapping_add(*v3); + *v3 = rotl(*v3, 21); + *v3 ^= *v0; + + *v2 = v2.wrapping_add(*v1); + *v1 = rotl(*v1, 17); + *v1 ^= *v2; + *v2 = rotl(*v2, 32); +} + +fn u64_from_le(bytes: &[u8]) -> u64 { + u64::from_le_bytes(bytes.try_into().unwrap()) +} + +fn load_u64_partial(bytes: &[u8]) -> u64 { + let mut b = 0u64; + + match bytes.len() { + 7 => { + b |= (bytes[6] as u64) << 48; + b |= (bytes[5] as u64) << 40; + b |= (bytes[4] as u64) << 32; + b |= (bytes[3] as u64) << 24; + b |= (bytes[2] as u64) << 16; + b |= (bytes[1] as u64) << 8; + b |= bytes[0] as u64; + } + 6 => { + b |= (bytes[5] as u64) << 40; + b |= (bytes[4] as u64) << 32; + b |= (bytes[3] as u64) << 24; + b |= (bytes[2] as u64) << 16; + b |= (bytes[1] as u64) << 8; + b |= bytes[0] as u64; + } + 5 => { + b |= (bytes[4] as u64) << 32; + b |= (bytes[3] as u64) << 24; + b |= (bytes[2] as u64) << 16; + b |= (bytes[1] as u64) << 8; + b |= bytes[0] as u64; + } + 4 => { + b |= (bytes[3] as u64) << 24; + b |= (bytes[2] as u64) << 16; + b |= (bytes[1] as u64) << 8; + b |= bytes[0] as u64; + } + 3 => { + b |= (bytes[2] as u64) << 16; + b |= (bytes[1] as u64) << 8; + b |= bytes[0] as u64; + } + 2 => { + b |= (bytes[1] as u64) << 8; + b |= bytes[0] as u64; + } + 1 => { + b |= bytes[0] as u64; + } + _ => {} + } + + b +} + +// LLVM siphash<2,4> 64-bit output +fn llvm_siphash_2_4_64(data: &[u8]) -> u64 { + let k0 = u64_from_le(&SIPHASH_KEY[0..8]); + let k1 = u64_from_le(&SIPHASH_KEY[8..16]); + + let mut v0: u64 = 0x736f6d6570736575 ^ k0; + let mut v1: u64 = 0x646f72616e646f6d ^ k1; + let mut v2: u64 = 0x6c7967656e657261 ^ k0; + let mut v3: u64 = 0x7465646279746573 ^ k1; + + let mut b: u64 = (data.len() as u64) << 56; + let mut i = 0; + + // compression + while i + 8 <= data.len() { + let m = u64_from_le(&data[i..i + 8]); + i += 8; + + v3 ^= m; + + for _ in 0..2 { + sipround(&mut v0, &mut v1, &mut v2, &mut v3); + } + + v0 ^= m; + } + + // tail + let tail = &data[i..]; + + b |= load_u64_partial(tail); + + v3 ^= b; + + for _ in 0..2 { + sipround(&mut v0, &mut v1, &mut v2, &mut v3); + } + + v0 ^= b; + + // finalization + v2 ^= 0xff; + + for _ in 0..4 { + sipround(&mut v0, &mut v1, &mut v2, &mut v3); + } + + v0 ^ v1 ^ v2 ^ v3 +} diff --git a/compiler/rustc_middle/src/ptrauth/mod.rs b/compiler/rustc_middle/src/ptrauth/mod.rs new file mode 100644 index 0000000000000..ec874c3ef9015 --- /dev/null +++ b/compiler/rustc_middle/src/ptrauth/mod.rs @@ -0,0 +1,7 @@ +pub mod discriminator; +pub mod llvm_siphash; + +pub use discriminator::{ + FnPtrDiscriminatorSource, FnPtrTypeDiscriminatorInput, clone_discriminated_ptrauth_schema_for, + compute_fn_ptr_type_discriminator_for, +}; diff --git a/compiler/rustc_session/src/diagnostics.rs b/compiler/rustc_session/src/diagnostics.rs index d1989609cefe2..20e7cb288ad5b 100644 --- a/compiler/rustc_session/src/diagnostics.rs +++ b/compiler/rustc_session/src/diagnostics.rs @@ -381,12 +381,6 @@ pub(crate) struct StackProtectorNotSupportedForTarget<'a> { pub(crate) target_triple: &'a TargetTuple, } -#[derive(Diagnostic)] -#[diag("function pointer type discrimination is not supported")] -pub(crate) struct PointerAuthenticationTypeDiscriminationNotSupportedForTarget<'a> { - pub(crate) target_triple: &'a TargetTuple, -} - #[derive(Diagnostic)] #[diag( "`-Z pointer-authentication` is not supported for target {$target_triple} and will be ignored" diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 733470a7e0471..5045370cdb67c 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -96,6 +96,7 @@ pub enum PointerAuthARM8_3Key { } /// Forms of extra discrimination. +#[derive(Clone, Debug, PartialEq)] pub enum PointerAuthDiscrimination { /// No additional discrimination. None, @@ -108,6 +109,7 @@ pub enum PointerAuthDiscrimination { } /// Types of address discrimination. +#[derive(Clone, Debug)] pub enum PointerAuthAddressDiscriminator { /// Enable/disable hardware address discrimination. HardwareAddress(bool), @@ -116,6 +118,7 @@ pub enum PointerAuthAddressDiscriminator { Synthetic(u64), } +#[derive(Clone, Debug)] pub struct PointerAuthSchema { pub is_address_discriminated: PointerAuthAddressDiscriminator, pub discrimination_kind: PointerAuthDiscrimination, @@ -206,8 +209,7 @@ impl PointerAuthConfig { const GOT: u32 = 8; const GOTOS: u32 = 9; const TYPEINFO_VT_PTR_DISCR: u32 = 10; - // FIXME(jchlanda) We don't yet support function pointer type discrimination. - // const FPTR_TYPE_DISCR: u32 = 11; + const FPTR_TYPE_DISCR: u32 = 11; let pauth_abi_version: u32 = (u32::from(self.intrinsics) << INTRINSICS) | (u32::from(self.function_pointers.is_some()) << CALLS) @@ -225,7 +227,10 @@ impl PointerAuthConfig { })) << INIT_FINI_ADDR_DISC) | (u32::from(self.elf_got) << GOT) | (u32::from(self.indirect_gotos) << GOTOS) - | (u32::from(self.typeinfo_vt_ptr_discrimination) << TYPEINFO_VT_PTR_DISCR); + | (u32::from(self.typeinfo_vt_ptr_discrimination) << TYPEINFO_VT_PTR_DISCR) + | (u32::from(self.function_pointers.as_ref().is_some_and(|schema| { + matches!(schema.discrimination_kind, PointerAuthDiscrimination::Type) + })) << FPTR_TYPE_DISCR); pauth_abi_version } @@ -1193,12 +1198,26 @@ impl Session { self.pointer_auth_config.is_some() } - pub fn pointer_authentication_functions(&self) -> Option<&PointerAuthSchema> { - self.pointer_auth_config.as_ref().and_then(|cfg| cfg.function_pointers.as_ref()) + pub fn pointer_authentication_functions(&self) -> Option { + self.pointer_auth_config.as_ref().and_then(|cfg| cfg.function_pointers.clone()) } - pub fn pointer_authentication_init_fini(&self) -> Option<&PointerAuthSchema> { - self.pointer_auth_config.as_ref().and_then(|cfg| cfg.init_fini.as_ref()) + pub fn pointer_authentication_init_fini(&self) -> Option { + self.pointer_auth_config.as_ref().and_then(|cfg| cfg.init_fini.clone()) + } + + pub fn pointer_authentication_fn_ptr_type_discrimination(&self) -> bool { + self.pointer_auth_config + .as_ref() + .and_then(|cfg| cfg.function_pointers.as_ref()) + .is_some_and(|schema| schema.discrimination_kind == PointerAuthDiscrimination::Type) + } + + pub fn pointer_authentication_fn_ptr_key(&self) -> Option { + self.pointer_auth_config + .as_ref() + .and_then(|cfg| cfg.function_pointers.as_ref()) + .map(|schema| schema.key) } } @@ -1429,19 +1448,6 @@ fn validate_commandline_args_with_session_available(sess: &Session) { sess.dcx().emit_err(diagnostics::LinkerPluginToWindowsNotSupported); } - if sess - .pointer_auth_config - .as_ref() - .and_then(|cfg| cfg.function_pointers.as_ref()) - .is_some_and(|schema| matches!(schema.discrimination_kind, PointerAuthDiscrimination::Type)) - { - sess.dcx().emit_err( - diagnostics::PointerAuthenticationTypeDiscriminationNotSupportedForTarget { - target_triple: &sess.opts.target_triple, - }, - ); - } - if sess.target.cfg_abi != CfgAbi::Pauthtest && !sess.opts.unstable_opts.pointer_authentication.is_empty() { diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs index 54f4ff77627b9..329ecfde0a11b 100644 --- a/compiler/rustc_target/src/callconv/mod.rs +++ b/compiler/rustc_target/src/callconv/mod.rs @@ -618,12 +618,22 @@ pub struct FnAbi<'a, Ty> { pub conv: CanonAbi, /// Indicates if an unwind may happen across a call to this function. pub can_unwind: bool, + /// Computed type discriminator for pointer authentication purpose. + pub ptrauth_type_discriminator: u64, } // Needs to be a custom impl because of the bounds on the `TyAndLayout` debug impl. impl<'a, Ty: fmt::Display> fmt::Debug for FnAbi<'a, Ty> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let FnAbi { args, ret, c_variadic, fixed_count, conv, can_unwind } = self; + let FnAbi { + args, + ret, + c_variadic, + fixed_count, + conv, + can_unwind, + ptrauth_type_discriminator, + } = self; f.debug_struct("FnAbi") .field("args", args) .field("ret", ret) @@ -631,6 +641,7 @@ impl<'a, Ty: fmt::Display> fmt::Debug for FnAbi<'a, Ty> { .field("fixed_count", fixed_count) .field("conv", conv) .field("can_unwind", can_unwind) + .field("ptrauth_type_discriminator", ptrauth_type_discriminator) .finish() } } @@ -930,6 +941,6 @@ mod size_asserts { use super::*; // tidy-alphabetical-start static_assert_size!(ArgAbi<'_, usize>, 56); - static_assert_size!(FnAbi<'_, usize>, 80); + static_assert_size!(FnAbi<'_, usize>, 88); // tidy-alphabetical-end } diff --git a/compiler/rustc_ty_utils/src/abi.rs b/compiler/rustc_ty_utils/src/abi.rs index baf7d95741cac..1b063d55c75ff 100644 --- a/compiler/rustc_ty_utils/src/abi.rs +++ b/compiler/rustc_ty_utils/src/abi.rs @@ -6,6 +6,7 @@ use rustc_hir::lang_items::LangItem; use rustc_hir::{self as hir, find_attr}; use rustc_middle::bug; use rustc_middle::middle::deduced_param_attrs::DeducedParamAttrs; +use rustc_middle::ptrauth::compute_fn_ptr_type_discriminator_for; use rustc_middle::query::Providers; use rustc_middle::ty::layout::{ FnAbiError, HasTyCtxt, HasTypingEnv, LayoutCx, LayoutOf, TyAndLayout, fn_can_unwind, @@ -632,6 +633,12 @@ fn fn_abi_new_uncached<'tcx>( determined_fn_def_id, sig.abi(), ), + ptrauth_type_discriminator: if tcx.sess.pointer_authentication_fn_ptr_type_discrimination() + { + compute_fn_ptr_type_discriminator_for(tcx, sig).unwrap_or(0).into() + } else { + 0 + }, }; fn_abi_adjust_for_abi(cx, &mut fn_abi, sig.abi()); debug!("fn_abi_new_uncached = {:?}", fn_abi); diff --git a/src/doc/rustc/src/platform-support/aarch64-unknown-linux-pauthtest.md b/src/doc/rustc/src/platform-support/aarch64-unknown-linux-pauthtest.md index e0f11c7800dcd..d4cb6ed97acb5 100644 --- a/src/doc/rustc/src/platform-support/aarch64-unknown-linux-pauthtest.md +++ b/src/doc/rustc/src/platform-support/aarch64-unknown-linux-pauthtest.md @@ -109,29 +109,18 @@ Clang-based toolchain. In this case, no wrapper script is required, Introduction of `aarch64-unknown-linux-pauthtest` target needs to be propagated to various crates/repos, so that they can correctly recognise and handle it. -Specifically: +At the time of writing this document the following requires patching: * `cc-rs`: https://github.com/jchlanda/cc-rs/tree/jakub/cc-v1.2.28-pauthtest -* `libc`: https://github.com/jchlanda/libc/tree/jakub/0.2.183-pauthtest * `backtrace`: https://github.com/jchlanda/backtrace-rs/tree/jakub/backtrace-v0.3.76-pauthtest -The patched versions of `cc-rs` and `libc` will have to be registered through -`[patch.crates-io]` section of `Cargo.toml` files both in: -`/src/bootstrap/` and `/library/`. Check out `cc-rs` and -`libc` to `/patches` and update config files. See attached diff for -details: +The patched versions of `cc-rs` will have to be registered through +`[patch.crates-io]` section of `Cargo.toml` file in: +`/src/bootstrap/`. Check out `cc-rs` to `/patches` and +update config file. See attached diff for details:
```diff -diff --git a/library/Cargo.toml b/library/Cargo.toml -index e30e6240942..fb5a12f0065 100644 ---- a/library/Cargo.toml -+++ b/library/Cargo.toml -@@ -59,3 +59,4 @@ rustflags = ["-Cpanic=abort"] - rustc-std-workspace-core = { path = 'rustc-std-workspace-core' } - rustc-std-workspace-alloc = { path = 'rustc-std-workspace-alloc' } - rustc-std-workspace-std = { path = 'rustc-std-workspace-std' } -+libc = { path = '/patches/libc' } diff --git a/src/bootstrap/Cargo.toml b/src/bootstrap/Cargo.toml index e1725db60cf..46763cdf9a4 100644 --- a/src/bootstrap/Cargo.toml @@ -147,7 +136,7 @@ index e1725db60cf..46763cdf9a4 100644
-In contrast to `cc-rs` and `libc`, which are external crates resolved from +In contrast to `cc-rs`, which is an external crate resolved from [crates.io](https://crates.io/) and can be overridden using `[patch.crates-io]`, `backtrace` is included in the Rust repository as a git submodule under `/library/backtrace`. At the time of writing, the necessary change @@ -464,6 +453,18 @@ The following categories are supported (all present in tree): * pauth-extern-weak-global.rs * pauth-init-fini.rs * pauth-attr-special-funcs.rs + * pauth-fn-ptr-type-discrimination-deeply-nested.rs + * pauth-fn-ptr-type-discrimination-encoder.rs + * pauth-fn-ptr-type-discrimination-fn-ptr-return-type.rs + * pauth-fn-ptr-type-discrimination-option-callback.rs + * pauth-fn-ptr-type-discrimination-option-return.rs + * pauth-fn-ptr-type-discrimination-option.rs + * pauth-fn-ptr-type-discrimination-running-test.rs + * pauth-fn-ptr-type-discrimination-rust-array.rs + * pauth-fn-ptr-type-discrimination-simd.rs + * pauth-fn-ptr-type-discrimination-struct-members.rs + * pauth-fn-ptr-type-discrimination-struct-name.rs + * pauth-drop-terminator (implemented in run-make) * End-to-end execution tests * Rust-driven quicksort (pauth-quicksort-rust-driver) * C-driven quicksort (pauth-quicksort-c-driver) @@ -472,7 +473,6 @@ The following categories are supported (all present in tree): * pauth-static-link-warning * enable_pointer_authentication_validation.rs * invalid_target_pointer_authentication.rs - * type_discrimination_not_supported_pointer_authentication.rs * incompatible_pauth.rs All tests from `assembly-llvm`, `codegen-llvm`, `codegen-units`, `coverage`, @@ -493,13 +493,24 @@ x.py test --target aarch64-unknown-linux-pauthtest --force-rerun assembly-llvm \ tests/codegen-llvm/pauth/pauth-extern-c-direct-indirect-call.rs \ tests/codegen-llvm/pauth/pauth-extern-weak-global.rs \ tests/codegen-llvm/pauth/pauth-init-fini.rs \ + tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-deeply-nested.rs \ + tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-encoder.rs \ + tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-fn-ptr-return-type.rs \ + tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-callback.rs \ + tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-return.rs \ + tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option.rs \ + tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-running-test.rs \ + tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-rust-array.rs \ + tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-simd.rs \ + tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-struct-members.rs \ + tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-struct-name.rs \ tests/run-make/pauth-quicksort-rust-driver \ tests/run-make/pauth-quicksort-c-driver \ tests/run-make/pauth-static-link-warning \ + tests/run-make/pauth-drop-terminator \ tests/ui/statics/crt-static-pauthtest.rs \ tests/ui/pointer_authentication/enable_pointer_authentication_validation.rs \ tests/ui/pointer_authentication/invalid_target_pointer_authentication.rs \ - tests/ui/pointer_authentication/type_discrimination_not_supported_pointer_authentication.rs \ tests/ui/target_modifiers/incompatible_pauth.rs ``` diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index d63d48e56903d..70366142dbf9e 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -112,6 +112,7 @@ impl Copy for [T; N] {} pub struct PhantomData; impl Copy for PhantomData {} +#[rustc_diagnostic_item = "Option"] pub enum Option { None, Some(T), @@ -249,11 +250,18 @@ pub trait Add { fn add(self, _: Rhs) -> Self::Output; } +// Avoid needing to add all of the overflow handling and panic language items impl Add for isize { type Output = isize; fn add(self, other: isize) -> isize { - 7 // avoid needing to add all of the overflow handling and panic language items + 7 + } +} +impl Add for i32 { + type Output = i32; + fn add(self, rhs: i32) -> i32 { + 7 } } @@ -300,18 +308,30 @@ impl_marker_trait!( ); impl Sync for () {} - impl Sync for [T; N] {} +impl Sync for Option {} +impl Sync for &T {} + // Function pointers are treated as `Sync` to match real `core` behavior. // // Minicore provides only the minimal set of impls required by tests. Rather // than exhaustively covering all possible function pointer signatures, -// additional impls should be added as needed. -impl Sync for fn() -> R {} -impl Sync for extern "C" fn() -> R {} -impl Sync for unsafe extern "C" fn() -> R {} -impl Sync for extern "C" fn(A) -> R {} -impl Sync for unsafe extern "C" fn(A) -> R {} +// additional arities should be added as needed. +macro_rules! impl_sync_for_fn_ptrs { + ($(($($T:ident),*)),* $(,)?) => { + $( + impl<$($T,)* R> Sync for fn($($T),*) -> R {} + + impl<$($T,)* R> Sync for extern "C" fn($($T),*) -> R {} + impl<$($T,)* R> Sync for unsafe extern "C" fn($($T),*) -> R {} + + impl<$($T,)* R> Sync for extern "C" fn($($T,)* ...) -> R {} + impl<$($T,)* R> Sync for unsafe extern "C" fn($($T,)* ...) -> R {} + )* + }; +} + +impl_sync_for_fn_ptrs!((), (A), (A, B), (A, B, C), (A, B, C, D),); #[lang = "drop_glue"] fn drop_glue(_: &mut T) {} @@ -348,7 +368,7 @@ pub trait CoerceUnsized {} impl<'a, 'b: 'a, T: PointeeSized + Unsize, U: PointeeSized> CoerceUnsized<&'a U> for &'b T {} #[lang = "drop"] -trait Drop { +pub trait Drop { fn drop(&mut self); } @@ -359,7 +379,7 @@ pub const unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: us pub mod mem { #[rustc_nounwind] #[rustc_intrinsic] - pub unsafe fn transmute(src: Src) -> Dst; + pub const unsafe fn transmute(src: Src) -> Dst; #[rustc_nounwind] #[rustc_intrinsic] @@ -378,6 +398,15 @@ pub mod ptr { unsafe { volatile_store(dst, src) }; } + + #[inline] + #[rustc_diagnostic_item = "ptr_read_volatile"] + pub unsafe fn read_volatile(src: *const T) -> T { + #[rustc_intrinsic] + pub unsafe fn volatile_load(src: *const T) -> T; + + unsafe { volatile_load(src) } + } } pub mod hint { @@ -392,9 +421,10 @@ pub mod hint { #[lang = "c_void"] #[repr(u8)] +#[allow(non_camel_case_types)] pub enum c_void { - __variant1, - __variant2, + Variant1, + Variant2, } #[rustc_builtin_macro(pattern_type)] diff --git a/tests/codegen-llvm/pauth/pauth-attr-special-funcs.rs b/tests/codegen-llvm/pauth/pauth-attr-special-funcs.rs index 2751494b9de7a..c704a86394d73 100644 --- a/tests/codegen-llvm/pauth/pauth-attr-special-funcs.rs +++ b/tests/codegen-llvm/pauth/pauth-attr-special-funcs.rs @@ -8,7 +8,10 @@ use std::panic; -// CHECK: define {{.*}} @__rust_try{{.*}} [[ATTR_TRY:#[0-9]+]] +// Make sure that `rust_eh_personality` is not signed. +// CHECK: define internal i32 @{{.*}}lang_start{{.*}}pauth_attr_special_funcs(ptr %{{.*}}) unnamed_addr #[[#]] personality ptr @rust_eh_personality + +// CHECK: define {{.*}} @__rust_try{{.*}} [[ATTR_TRY:#[0-9]+]] personality ptr @rust_eh_personality { // CHECK: define {{.*}} @main{{.*}} [[ATTR_MAIN:#[0-9]+]] // CHECK: attributes [[ATTR_TRY]] = { {{.*}}"aarch64-jump-table-hardening" diff --git a/tests/codegen-llvm/pauth/pauth-extern-c-direct-indirect-call.rs b/tests/codegen-llvm/pauth/pauth-extern-c-direct-indirect-call.rs index 643b428339b73..31ec8e9febc6f 100644 --- a/tests/codegen-llvm/pauth/pauth-extern-c-direct-indirect-call.rs +++ b/tests/codegen-llvm/pauth/pauth-extern-c-direct-indirect-call.rs @@ -1,5 +1,5 @@ +// ignore-tidy-file-linelength //@ add-minicore -// ignore-tidy-linelength //@ only-pauthtest //@ revisions: O0_PAUTH O3_PAUTH diff --git a/tests/codegen-llvm/pauth/pauth-extern-weak-global.rs b/tests/codegen-llvm/pauth/pauth-extern-weak-global.rs index a83298dd5725c..3adb5e0e27f2d 100644 --- a/tests/codegen-llvm/pauth/pauth-extern-weak-global.rs +++ b/tests/codegen-llvm/pauth/pauth-extern-weak-global.rs @@ -1,4 +1,4 @@ -// ignore-tidy-linelength +// ignore-tidy-file-linelength //@ only-pauthtest //@ revisions: O0_PAUTH O3_PAUTH O0_NO_PAUTH O3_NO_PAUTH //@ add-minicore diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-deeply-nested.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-deeply-nested.rs new file mode 100644 index 0000000000000..baba0249fded3 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-deeply-nested.rs @@ -0,0 +1,150 @@ +// ignore-tidy-file-linelength +//@ add-minicore +//@ only-pauthtest +// Run it at O0, so that the compiler doesn't optimise the calls away. +//@ revisions: DISC NO_DISC + +//@ [DISC] needs-llvm-components: aarch64 +//@ [DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=+function-pointer-type-discrimination -C opt-level=0 +//@ [NO_DISC] needs-llvm-components: aarch64 +//@ [NO_DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=-function-pointer-type-discrimination -C opt-level=0 + +// Test generation of function-pointer type discriminators. The discriminator values were obtained +// from Clang by compiling equivalent C code (included). Both compilers must generate identical +// values. +// +// Make sure that the compiler can see through chains of nested structs, both when used as globals, +// arguments and returns. +// +// Equivalent C: +// +// #include +// +// typedef int (*L0)(int); +// typedef int (*L1)(L0); +// typedef int (*L2)(L1); +// typedef int (*L3)(L2); +// typedef int (*L4)(L3); +// typedef L0 (*DeepRet)(void); +// +// int callback_i32(int x) { return x + 1; } +// +// int dummy_l1(L0 cb) { return cb(5); } +// int dummy_l2(L1 cb) { return cb(callback_i32); } +// int dummy_l3(L2 cb) { return cb(dummy_l1); } +// int dummy_l4(L3 cb) { return cb(dummy_l2); } +// +// L0 returned_fn(void) { return callback_i32; } +// +// DeepRet f_deep(L4 cb) { +// cb(dummy_l3); +// return returned_fn; +// } +// +// DeepRet (*T_DEEP)(L4) = f_deep; +// +// int main(void) { +// DeepRet ret_fn; +// L0 inner_fn; +// int result; +// +// ret_fn = T_DEEP(dummy_l4); +// inner_fn = ret_fn(); +// result = inner_fn(42); +// +// printf("result = %d\n", result); +// +// return 0; +// } + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] +extern crate minicore; +use minicore::hint::black_box; + +// Nested fn ptr chain. +type L0 = extern "C" fn(i32) -> i32; +type L1 = extern "C" fn(L0) -> i32; +type L2 = extern "C" fn(L1) -> i32; +type L3 = extern "C" fn(L2) -> i32; +type L4 = extern "C" fn(L3) -> i32; +// Function returning fn ptr. +type DeepRet = extern "C" fn() -> L0; + +#[used] +// DISC: @{{.*}}T_DEEP = constant ptr ptrauth (ptr @{{.*}}f_deep, i32 0, i64 1059), align 8 +// NO_DISC: @{{.*}}T_DEEP = constant ptr ptrauth (ptr @{{.*}}f_deep, i32 0), align 8 +static T_DEEP: unsafe extern "C" fn(L4) -> DeepRet = f_deep; + +// Leaf callback. +// CHECK-LABEL: callback_i32 +pub extern "C" fn callback_i32(x: i32) -> i32 { + x +} + +// Dummy chain impl. +// CHECK-LABEL: dummy_l1 +// CHECK: (ptr [[CB:%.*]]) +pub extern "C" fn dummy_l1(cb: L0) -> i32 { + // DISC: call i32 [[CB]](i32 5) {{.*}} [ "ptrauth"(i32 0, i64 2981) ] + // NO_DISC: call i32 [[CB]](i32 5) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + cb(5) +} +// CHECK-LABEL: dummy_l2 +// CHECK: (ptr [[CB:%.*]]) +pub extern "C" fn dummy_l2(cb: L1) -> i32 { + // DISC: call i32 [[CB]](ptr ptrauth (ptr @{{.*}}callback_i32, i32 0, i64 2981)) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 [[CB]](ptr ptrauth (ptr @{{.*}}callback_i32, i32 0)) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + cb(callback_i32) +} +// CHECK-LABEL: dummy_l3 +// CHECK: (ptr [[CB:%.*]]) +pub extern "C" fn dummy_l3(cb: L2) -> i32 { + // DISC: call i32 [[CB]](ptr ptrauth (ptr @{{.*}}dummy_l1, i32 0, i64 12410)) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 [[CB]](ptr ptrauth (ptr @{{.*}}dummy_l1, i32 0)) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + cb(dummy_l1) +} +// CHECK-LABEL: dummy_l4 +// CHECK: (ptr [[CB:%.*]]) +pub extern "C" fn dummy_l4(cb: L3) -> i32 { + // DISC: call i32 [[CB]](ptr ptrauth (ptr @{{.*}}dummy_l2, i32 0, i64 12410)) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 [[CB]](ptr ptrauth (ptr @{{.*}}dummy_l2, i32 0)) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + cb(dummy_l2) +} +// Return fn impl. +// CHECK-LABEL: returned_fn +pub extern "C" fn returned_fn() -> L0 { + // DISC: ret ptr ptrauth (ptr @{{.*}}callback_i32, i32 0, i64 2981) + // NO_DISC: ret ptr ptrauth (ptr @{{.*}}callback_i32, i32 0) + return callback_i32; +} +// Entry point to the chain, takes L4 and returns function returning fn ptr. +// CHECK-LABEL: f_deep +// CHECK: (ptr [[CB:%.*]]) +pub extern "C" fn f_deep(cb: L4) -> DeepRet { + // DISC: call i32 [[CB]](ptr ptrauth (ptr @{{.*}}dummy_l3, i32 0, i64 12410)) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 [[CB]](ptr ptrauth (ptr @{{.*}}dummy_l3, i32 0)) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + cb(dummy_l3); + // DISC: ret ptr ptrauth (ptr @{{.*}}returned_fn, i32 0, i64 34128) + // NO_DISC: ret ptr ptrauth (ptr @{{.*}}returned_fn, i32 0) + return returned_fn; +} + +// CHECK-LABEL: main +pub fn main() { + unsafe { + // DISC: [[RET_FN:%.*]] = call ptr ptrauth (ptr @{{.*}}f_deep, i32 0, i64 1059)(ptr ptrauth (ptr @{{.*}}dummy_l4, i32 0, i64 12410)) {{.*}} [ "ptrauth"(i32 0, i64 1059) ] + // NO_DISC: [[RET_FN:%.*]] = call ptr ptrauth (ptr @{{.*}}f_deep, i32 0)(ptr ptrauth (ptr @{{.*}}dummy_l4, i32 0)) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let ret_fn: DeepRet = T_DEEP(dummy_l4); + // DISC: [[INNER_FN:%.*]] = call ptr [[RET_FN]]() {{.*}} [ "ptrauth"(i32 0, i64 34128) ] + // NO_DISC: [[INNER_FN:%.*]] = call ptr [[RET_FN]]() {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let inner_fn: L0 = ret_fn(); + // DISC: call i32 [[INNER_FN]](i32 42) {{.*}} [ "ptrauth"(i32 0, i64 2981) ] + // NO_DISC: call i32 [[INNER_FN]](i32 42) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let result = inner_fn(42); + + black_box(result); + } +} diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-encoder.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-encoder.rs new file mode 100644 index 0000000000000..7466b373fc80f --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-encoder.rs @@ -0,0 +1,531 @@ +// ignore-tidy-file-linelength +//@ add-minicore +//@ only-pauthtest +// Run it at O0, so that the compiler doesn't optimise the calls away. +//@ revisions: DISC NO_DISC +//@ [DISC] needs-llvm-components: aarch64 +//@ [DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=+function-pointer-type-discrimination -C opt-level=0 +//@ [NO_DISC] needs-llvm-components: aarch64 +//@ [NO_DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=-function-pointer-type-discrimination -C opt-level=0 + +// Test generation of function-pointer type discriminators. The discriminator values were obtained +// from Clang by compiling equivalent C code (included). Both compilers must generate identical +// values. +// +// The `encode_ty` function in is responsible +// for converting types to the literal values that are then used as the basis for hashing. Its +// implementation is a faithful translation of Clang's `encodeTypeForFunctionPointerAuth`. + +#![feature(repr_simd)] +#![feature(simd_ffi)] +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] + +extern crate minicore; +use minicore::{c_void, mem}; + +// Builtin types. +extern "C" { + fn f_i32(x: i32) -> i32; + fn f_f(x: f32) -> f32; + fn f_d(x: f64) -> f64; + fn f_2d(x: f64, y: f64) -> f64; + fn f_ld(x: f64) -> f64; + fn f_v() -> (); +} +type fn_i32 = unsafe extern "C" fn(i32) -> i32; +type fn_f = unsafe extern "C" fn(f32) -> f32; +type fn_d = unsafe extern "C" fn(f64) -> f64; +type fn_2d = unsafe extern "C" fn(f64, f64) -> f64; +type fn_v = unsafe extern "C" fn() -> (); +// discriminator: 2981 (0x0BA5), encoding: FiiE +// DISC: @{{.*}}T_I32 = constant ptr ptrauth (ptr @f_i32, i32 0, i64 2981), align 8 +// NO_DISC: @{{.*}}T_I32 = constant ptr ptrauth (ptr @f_i32, i32 0), align 8 +#[used] +static T_I32: fn_i32 = f_i32; +// discriminator: 28450 (0x6F22), encoding: FffE +// DISC: @{{.*}}T_F = constant ptr ptrauth (ptr @f_f, i32 0, i64 28450), align 8 +// NO_DISC: @{{.*}}T_F = constant ptr ptrauth (ptr @f_f, i32 0), align 8 +#[used] +static T_F: fn_f = f_f; +// discriminator: 43115 (0xA86B), encoding: FddE +// DISC: @{{.*}}T_D = constant ptr ptrauth (ptr @f_d, i32 0, i64 43115), align 8 +// NO_DISC: @{{.*}}T_D = constant ptr ptrauth (ptr @f_d, i32 0), align 8 +#[used] +static T_D: fn_d = f_d; +// discriminator: 38695 (0x9727), encoding: FdddE +// DISC: @{{.*}}T_2D = constant ptr ptrauth (ptr @f_2d, i32 0, i64 38695), align 8 +// NO_DISC: @{{.*}}T_2D = constant ptr ptrauth (ptr @f_2d, i32 0), align 8 +#[used] +static T_2D: fn_2d = f_2d; +// discriminator: 18983 (0x4A27), encoding: FvE +// DISC: @{{.*}}T_V = constant ptr ptrauth (ptr @f_v, i32 0, i64 18983), align 8 +// NO_DISC: @{{.*}}T_V = constant ptr ptrauth (ptr @f_v, i32 0), align 8 +#[used] +static T_V: fn_v = f_v; + +// Pointer types. +extern "C" { + fn f_ptr(x: *mut i32) -> i32; +} +type fn_ptr = unsafe extern "C" fn(*mut i32) -> i32; +// discriminator: 12410 (0x307A), encoding: FiPE +// DISC: @{{.*}}T_PTR = constant ptr ptrauth (ptr @f_ptr, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}T_PTR = constant ptr ptrauth (ptr @f_ptr, i32 0), align 8 +#[used] +static T_PTR: fn_ptr = f_ptr; + +// Array types. +extern "C" { + fn f_arr_2(x: *mut i32) -> i32; + fn f_arr_4(x: *mut i32) -> i32; + fn f_arr2(x: *mut i32) -> i32; +} +type fn_arr_2 = unsafe extern "C" fn(*mut i32) -> i32; +type fn_arr_4 = unsafe extern "C" fn(*mut i32) -> i32; +type fn_arr2 = unsafe extern "C" fn(*mut i32) -> i32; +// discriminator: 12410 (0x307A), encoding: FiPE +// DISC: @{{.*}}T_ARR_2 = constant ptr ptrauth (ptr @f_arr_2, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}T_ARR_2 = constant ptr ptrauth (ptr @f_arr_2, i32 0), align 8 +#[used] +static T_ARR_2: fn_arr_2 = f_arr_2; +// discriminator: 12410 (0x307A), encoding: FiPE +// DISC: @{{.*}}T_ARR_4 = constant ptr ptrauth (ptr @f_arr_4, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}T_ARR_4 = constant ptr ptrauth (ptr @f_arr_4, i32 0), align 8 +#[used] +static T_ARR_4: fn_arr_4 = f_arr_4; +// discriminator: 12410 (0x307A), encoding: FiPE +// DISC: @{{.*}}T_ARR2 = constant ptr ptrauth (ptr @f_arr2, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}T_ARR2 = constant ptr ptrauth (ptr @f_arr2, i32 0), align 8 +#[used] +static T_ARR2: fn_arr2 = f_arr2; + +// Function types. +extern "C" { + fn f_nested(g: extern "C" fn(i32) -> i32, x: i32) -> i32; +} +type fn_i32_i32 = extern "C" fn(i32) -> i32; +type fn_nested = unsafe extern "C" fn(fn_i32_i32, i32) -> i32; +// discriminator: 20679 (0x50C7), encoding: FiPiE +// DISC: @{{.*}}T_NESTED = constant ptr ptrauth (ptr @f_nested, i32 0, i64 20679), align 8 +// NO_DISC: @{{.*}}T_NESTED = constant ptr ptrauth (ptr @f_nested, i32 0), align 8 +#[used] +static T_NESTED: fn_nested = f_nested; + +// Variadic function. +extern "C" { + fn f_var(x: i32, ...) -> i32; +} +type fn_var = unsafe extern "C" fn(i32, ...) -> i32; +// discriminator: 7476 (0x1D34), encoding: FiizE +// DISC: @{{.*}}T_VAR = constant ptr ptrauth (ptr @f_var, i32 0, i64 7476), align 8 +// NO_DISC: @{{.*}}T_VAR = constant ptr ptrauth (ptr @f_var, i32 0), align 8 +#[used] +static T_VAR: fn_var = f_var; + +// Enum coercion to int. +#[repr(i32)] +enum MyEnum { + A = 1, + B = 2, +} +extern "C" { + fn f_enum(x: MyEnum) -> MyEnum; +} +type fn_enum = unsafe extern "C" fn(MyEnum) -> MyEnum; +// discriminator: 2981 (0x0BA5), encoding: FiiE +// DISC: @{{.*}}T_ENUM = constant ptr ptrauth (ptr @f_enum, i32 0, i64 2981), align 8 +// NO_DISC: @{{.*}}T_ENUM = constant ptr ptrauth (ptr @f_enum, i32 0), align 8 +#[used] +static T_ENUM: fn_enum = f_enum; + +// Struct types. +#[repr(C)] +struct MyStruct { + x: i32, +} +extern "C" { + fn f_struct(x: MyStruct) -> MyStruct; +} +type fn_struct = unsafe extern "C" fn(MyStruct) -> MyStruct; +// discriminator: 17754 (0x455A), encoding: F8MyStruct8MyStructE +// DISC: @{{.*}}T_STRUCT = constant ptr ptrauth (ptr @f_struct, i32 0, i64 17754), align 8 +// NO_DISC: @{{.*}}T_STRUCT = constant ptr ptrauth (ptr @f_struct, i32 0), align 8 +#[used] +static T_STRUCT: fn_struct = f_struct; + +// Function pointer as arguments. +extern "C" { + fn f_fp(h: extern "C" fn(i32) -> i32) -> i32; +} +type fn_i32_i32_fp_as_arg = extern "C" fn(i32) -> i32; +type fn_fp = unsafe extern "C" fn(fn_i32_i32_fp_as_arg) -> i32; +// discriminator: 12410 (0x307A), encoding: FiPE +// DISC: @{{.*}}T_FP = constant ptr ptrauth (ptr @f_fp, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}T_FP = constant ptr ptrauth (ptr @f_fp, i32 0), align 8 +#[used] +static T_FP: fn_fp = f_fp; + +// SIMD vector type. +#[repr(simd)] +struct Int4([i32; 4]); +extern "C" { + fn f_vec(x: Int4) -> Int4; +} +type FnVec = unsafe extern "C" fn(Int4) -> Int4; +// discriminator: 34246 (0x85C6), encoding: FDv16Dv16E +// DISC: @{{.*}}T_VEC = constant ptr ptrauth (ptr @f_vec, i32 0, i64 34246), align 8 +// NO_DISC: @{{.*}}T_VEC = constant ptr ptrauth (ptr @f_vec, i32 0), align 8 +#[used] +static T_VEC: FnVec = f_vec; + +// Mixed. +type fn_i32_f = unsafe extern "C" fn(f32) -> i32; +extern "C" { + fn f_mixed(g: fn_i32_f, arr: *mut *mut f32, d: f64) -> i32; +} +type fn_mixed = unsafe extern "C" fn(fn_i32_f, *mut *mut f32, f64) -> i32; +// discriminator: 36791 (0x8FB7), encoding: FiPPdE +// DISC: @{{.*}}T_MIXED = constant ptr ptrauth (ptr @f_mixed, i32 0, i64 36791), align 8 +// NO_DISC: @{{.*}}T_MIXED = constant ptr ptrauth (ptr @f_mixed, i32 0), align 8 +#[used] +static T_MIXED: fn_mixed = f_mixed; + +// Quicksort. +type FnCmp = unsafe extern "C" fn(*const c_void, *const c_void) -> i32; +type FnQsort = unsafe extern "C" fn(*mut c_void, usize, usize, FnCmp); +extern "C" { + fn quickSort(base: *mut c_void, n: usize, size: usize, cmp: FnCmp); + + fn cmpI32Ascending(lhs: *const c_void, rhs: *const c_void) -> i32; +} +#[used] +static T_QSORT: FnQsort = quickSort; +// discriminator: 39926 (0x9BF6) of: FvPiiPE +// DISC: @{{.*}}T_QSORT = constant ptr ptrauth (ptr @quickSort, i32 0, i64 39926), align 8 +// NO_DISC: @{{.*}}T_QSORT = constant ptr ptrauth (ptr @quickSort, i32 0), align 8 +#[used] +// discriminator: 58622 (0xE4FE) of: FiPPE +// DISC: @{{.*}}T_CMP_I32_ASCENDING = constant ptr ptrauth (ptr @cmpI32Ascending, i32 0, i64 58622), align 8 +// NO_DISC: @{{.*}}T_CMP_I32_ASCENDING = constant ptr ptrauth (ptr @cmpI32Ascending, i32 0), align 8 +static T_CMP_I32_ASCENDING: FnCmp = cmpI32Ascending; + +// Callbacks. +extern "C" fn callback_i32(x: i32) -> i32 { + x + 1 +} +unsafe extern "C" fn callback_f32_to_i32(x: f32) -> i32 { + x as i32 +} +type FnCallbackI32 = unsafe extern "C" fn(i32) -> i32; +type FnCallbackF32ToI32 = unsafe extern "C" fn(f32) -> i32; +#[used] +// discriminator: 2981 (0x0BA5) of: FiiE +// DISC: @{{.*}}T_CALLBACK_I32 = constant ptr ptrauth (ptr @{{.*}}callback_i32, i32 0, i64 2981), align 8 +// NO_DISC: @{{.*}}T_CALLBACK_I32 = constant ptr ptrauth (ptr @{{.*}}callback_i32, i32 0), align 8 +static T_CALLBACK_I32: FnCallbackI32 = callback_i32; +#[used] +// discriminator: 48468 (0xBD54) of: FifE +// DISC: @{{.*}}T_CALLBACK_F32_TO_I32 = constant ptr ptrauth (ptr @{{.*}}callback_f32_to_i32, i32 0, i64 48468), align 8 +// NO_DISC: @{{.*}}T_CALLBACK_F32_TO_I32 = constant ptr ptrauth (ptr @{{.*}}callback_f32_to_i32, i32 0), align 8 +static T_CALLBACK_F32_TO_I32: FnCallbackF32ToI32 = callback_f32_to_i32; + +// Test the calling of the functions. +pub fn main() { + unsafe { + // Builtin types. + + // DISC: %{{.*}} = call i32 ptrauth (ptr @f_i32, i32 0, i64 2981)(i32 123) {{.*}} [ "ptrauth"(i32 0, i64 2981) ] + // NO_DISC: %{{.*}} = call i32 ptrauth (ptr @f_i32, i32 0)(i32 123) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_I32(123); + // DISC: %{{.*}} = call float ptrauth (ptr @f_f, i32 0, i64 28450)(float 1.250000e+00) {{.*}} [ "ptrauth"(i32 0, i64 28450) ] + // NO_DISC: %{{.*}} = call float ptrauth (ptr @f_f, i32 0)(float 1.250000e+00) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_F(1.25); + // DISC: %{{.*}} = call double ptrauth (ptr @f_d, i32 0, i64 43115)(double 2.500000e+00) {{.*}} [ "ptrauth"(i32 0, i64 43115) ] + // NO_DISC: %{{.*}} = call double ptrauth (ptr @f_d, i32 0)(double 2.500000e+00) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_D(2.5); + // DISC: %{{.*}} = call double ptrauth (ptr @f_2d, i32 0, i64 38695)(double 1.000000e+00, double 2.000000e+00) {{.*}} [ "ptrauth"(i32 0, i64 38695) ] + // NO_DISC: %{{.*}} = call double ptrauth (ptr @f_2d, i32 0)(double 1.000000e+00, double 2.000000e+00) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_2D(1.0, 2.0); + // DISC: call void ptrauth (ptr @f_v, i32 0, i64 18983)() {{.*}} [ "ptrauth"(i32 0, i64 18983) ] + // NO_DISC: call void ptrauth (ptr @f_v, i32 0)() {{.*}} [ "ptrauth"(i32 0, i64 0) ] + T_V(); + + // Pointer type. + let mut x = 42i32; + // DISC: %{{.*}} = call i32 ptrauth (ptr @f_ptr, i32 0, i64 12410)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: %{{.*}} = call i32 ptrauth (ptr @f_ptr, i32 0)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_PTR(&mut x); + + // Array types. + let mut arr2 = [1i32, 2]; + let mut arr4 = [1i32, 2, 3, 4]; + let mut arrn = [1i32, 2, 3]; + + // DISC-DAG: call i32 ptrauth (ptr @f_arr_2, i32 0, i64 12410)(ptr %arr2) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC-DAG: call i32 ptrauth (ptr @f_arr_2, i32 0)(ptr %arr2) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_ARR_2((&mut arr2) as *mut [i32; 2] as *mut i32); + // DISC-DAG: call i32 ptrauth (ptr @f_arr_4, i32 0, i64 12410)(ptr %arr4) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC-DAG: call i32 ptrauth (ptr @f_arr_4, i32 0)(ptr %arr4) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_ARR_4((&mut arr4) as *mut [i32; 4] as *mut i32); + // DISC-DAG: call i32 ptrauth (ptr @f_arr2, i32 0, i64 12410)(ptr %arrn) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC-DAG: call i32 ptrauth (ptr @f_arr2, i32 0)(ptr %arrn) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_ARR2((&mut arrn) as *mut [i32; 3] as *mut i32); + + // Function argument. + // DISC: call i32 ptrauth (ptr @f_nested, i32 0, i64 20679)(ptr ptrauth (ptr @{{.*}}callback_i32, i32 0, i64 2981), i32 123) {{.*}} [ "ptrauth"(i32 0, i64 20679) ] + // NO_DISC: call i32 ptrauth (ptr @f_nested, i32 0)(ptr ptrauth (ptr @{{.*}}callback_i32, i32 0), i32 123) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_NESTED(callback_i32, 123); + + // Variadic. + // DISC: call i32 (i32, ...) ptrauth (ptr @f_var, i32 0, i64 7476){{.*}} [ "ptrauth"(i32 0, i64 7476) ] + // NO_DISC: call i32 (i32, ...) ptrauth (ptr @f_var, i32 0){{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_VAR(3, 10i32, 20i32, 30i32); + + // Enum. + // DISC: call i32 ptrauth (ptr @f_enum, i32 0, i64 2981)(i32 1) {{.*}} [ "ptrauth"(i32 0, i64 2981) ] + // NO_DISC: call i32 ptrauth (ptr @f_enum, i32 0)(i32 1) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_ENUM(MyEnum::A); + + // Struct. + // DISC: call i64 ptrauth (ptr @f_struct, i32 0, i64 17754){{.*}} [ "ptrauth"(i32 0, i64 17754) ] + // NO_DISC: ){{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_STRUCT(MyStruct { x: 123 }); + + // Function pointer argument. + // DISC: call i32 ptrauth (ptr @f_fp, i32 0, i64 12410)(ptr ptrauth (ptr @{{.*}}callback_i32, i32 0, i64 2981)) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @f_fp, i32 0)(ptr ptrauth (ptr @{{.*}}callback_i32, i32 0)) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_FP(callback_i32); + + // SIMD vector. + // DISC: call <4 x i32> ptrauth (ptr @f_vec, i32 0, i64 34246)(<4 x i32>{{.*}} [ "ptrauth"(i32 0, i64 34246) ] + // NO_DISC: call <4 x i32> ptrauth (ptr @f_vec, i32 0)(<4 x i32>{{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_VEC(Int4([1, 2, 3, 4])); + + // Mixed case. + let mut value = 1.0f32; + let mut ptr = &mut value as *mut f32; + // DISC: call i32 ptrauth (ptr @f_mixed, i32 0, i64 36791)(ptr ptrauth (ptr @{{.*}}callback_f32_to_i32, i32 0, i64 48468), {{.*}} [ "ptrauth"(i32 0, i64 36791) ] + // NO_DISC: call i32 ptrauth (ptr @f_mixed, i32 0)(ptr ptrauth (ptr @{{.*}}callback_f32_to_i32, i32 0), {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_MIXED(callback_f32_to_i32, &mut ptr, 2.0); + + // Comparator. + let lhs = 1i32; + let rhs = 2i32; + // DISC: call i32 ptrauth (ptr @cmpI32Ascending, i32 0, i64 58622)(ptr %lhs, ptr %rhs) {{.*}} [ "ptrauth"(i32 0, i64 58622) ] + // NO_DISC: call i32 ptrauth (ptr @cmpI32Ascending, i32 0)(ptr %lhs, ptr %rhs) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_CMP_I32_ASCENDING( + // (&lhs as *const i32).cast(), + // (&rhs as *const i32).cast(), + (&lhs as *const i32) as *const c_void, + (&rhs as *const i32) as *const c_void, + ); + + // Quicksort. + let mut values = [42i32, 7, 19, 3, 11]; + // DISC: call void ptrauth (ptr @quickSort, i32 0, i64 39926)(ptr %values, i64 5, i64 4, ptr ptrauth (ptr @cmpI32Ascending, i32 0, i64 58622)) {{.*}} [ "ptrauth"(i32 0, i64 39926) ] + // NO_DISC: call void ptrauth (ptr @quickSort, i32 0)(ptr %values, i64 5, i64 4, ptr ptrauth (ptr @cmpI32Ascending, i32 0)) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + T_QSORT( + (&mut values as *mut [i32; 5]) as *mut i32 as *mut c_void, + //values.as_mut_ptr().cast(), + 5, + 4, + T_CMP_I32_ASCENDING, + ); + } +} + +// Equivalent C code: +// +// #include +// #include +// +// // Builtin types. +// int32_t f_i32(int32_t x); +// float f_f(float x); +// double f_d(double x); +// double f_2d(double x, double y); +// long double f_ld(long double x); +// void f_v(void); +// +// typedef int32_t (*fn_i32)(int32_t); +// typedef float (*fn_f)(float); +// typedef double (*fn_d)(double); +// typedef double (*fn_2d)(double, double); +// typedef void (*fn_v)(void); +// +// __attribute__((used)) static fn_i32 T_I32 = f_i32; +// __attribute__((used)) static fn_f T_F = f_f; +// __attribute__((used)) static fn_d T_D = f_d; +// __attribute__((used)) static fn_2d T_2D = f_2d; +// __attribute__((used)) static fn_v T_V = f_v; +// +// // Pointer types. +// int32_t f_ptr(int32_t *x); +// +// typedef int32_t (*fn_ptr)(int32_t *); +// +// __attribute__((used)) static fn_ptr T_PTR = f_ptr; +// +// // Array types. +// int32_t f_arr_2(int32_t x[2]); +// int32_t f_arr_4(int32_t x[4]); +// +// typedef int32_t (*fn_arr_2)(int32_t[2]); +// typedef int32_t (*fn_arr_4)(int32_t[4]); +// +// __attribute__((used)) static fn_arr_2 T_ARR_2 = f_arr_2; +// __attribute__((used)) static fn_arr_4 T_ARR_4 = f_arr_4; +// // incomplete array +// int32_t f_arr2(int32_t x[]); +// +// typedef int32_t (*fn_arr2)(int32_t[]); +// +// __attribute__((used)) static fn_arr2 T_ARR2 = f_arr2; +// +// // Function types. +// int32_t f_nested(int32_t (*g)(int32_t), int32_t x); +// +// typedef int32_t (*fn_i32_i32)(int32_t); +// typedef int32_t (*fn_nested)(fn_i32_i32, int32_t); +// +// __attribute__((used)) static fn_nested T_NESTED = f_nested; +// +// // Variadic function. +// int32_t f_var(int32_t x, ...); +// +// typedef int32_t (*fn_var)(int32_t, ...); +// +// __attribute__((used)) static fn_var T_VAR = f_var; +// +// // Enum to integer coercion. +// typedef enum { A = 1, B = 2 } MyEnum; +// +// MyEnum f_enum(MyEnum x); +// +// typedef MyEnum (*fn_enum)(MyEnum); +// +// __attribute__((used)) static fn_enum T_ENUM = f_enum; +// +// // Struct. +// typedef struct { +// int x; +// } MyStruct; +// +// MyStruct f_struct(MyStruct x); +// +// typedef MyStruct (*fn_struct)(MyStruct); +// +// __attribute__((used)) static fn_struct T_STRUCT = f_struct; +// +// // Pointer to function pointer. +// int32_t f_fp(int32_t (*h)(int32_t)); +// +// typedef int32_t (*fn_i32_i32)(int32_t); +// typedef int32_t (*fn_fp)(fn_i32_i32); +// +// __attribute__((used)) static fn_fp T_FP = f_fp; +// +// // SIMD vector. +// typedef int32_t int4 __attribute__((vector_size(16))); +// +// int4 f_vec(int4 x); +// +// typedef int4 (*fn_vec)(int4); +// +// __attribute__((used)) static fn_vec T_VEC = f_vec; +// +// // Mix. +// int32_t f_mixed(int32_t (*g)(float), float *arr[4], double d); +// +// typedef int32_t (*fn_mixed)(int32_t (*)(float), float *[4], double); +// +// __attribute__((used)) static fn_mixed T_MIXED = f_mixed; +// +// // Qsort +// void quickSort(void *Base, size_t N, size_t Size, +// int (*Cmp)(const void *, const void *)); +// +// int cmpI32Ascending(const void *LHS, const void *RHS); +// typedef void (*fn_qsort)(void *, size_t, size_t, +// int (*)(const void *, const void *)); +// typedef int (*fn_cmp)(const void *, const void *); +// +// __attribute__((used)) static fn_qsort T_QSORT = quickSort; +// __attribute__((used)) static fn_cmp T_CMP_I32_ASCENDING = cmpI32Ascending; +// +// // Callbacks +// static int32_t callback_i32(int32_t x) { return x + 1; } +// static int32_t callback_f32_to_i32(float x) { return (int32_t)x; } +// typedef int32_t (*fn_callback_i32)(int32_t); +// typedef int32_t (*fn_callback_f32_to_i32)(float); +// __attribute__((used)) static fn_callback_i32 T_CALLBACK_I32 = callback_i32; +// __attribute__((used)) static fn_callback_f32_to_i32 T_CALLBACK_F32_TO_I32 = +// callback_f32_to_i32; +// +// int main(void) { +// /* Builtin types. */ +// (void)T_I32(123); +// (void)T_F(1.25f); +// (void)T_D(2.5); +// (void)T_2D(1.0, 2.0); +// T_V(); +// +// /* Pointer type. */ +// int32_t x = 42; +// (void)T_PTR(&x); +// +// /* Array types. */ +// int32_t arr2[2] = {1, 2}; +// int32_t arr4[4] = {1, 2, 3, 4}; +// int32_t arrn[3] = {1, 2, 3}; +// +// (void)T_ARR_2(arr2); +// (void)T_ARR_4(arr4); +// (void)T_ARR2(arrn); +// +// /* Function argument. */ +// (void)T_NESTED(callback_i32, 123); +// +// /* Variadic. */ +// (void)T_VAR(3, 10, 20, 30); +// +// /* Enum. */ +// (void)T_ENUM(A); +// +// /* Struct. */ +// (void)T_STRUCT((MyStruct){.x = 123}); +// +// /* Function pointer argument. */ +// (void)T_FP(callback_i32); +// +// /* SIMD vector. */ +// int4 v = {1, 2, 3, 4}; +// (void)T_VEC(v); +// +// /* Mixed case. */ +// float value0 = 1.0f; +// float value1 = 2.0f; +// float value2 = 3.0f; +// float value3 = 4.0f; +// +// float *arrp[4] = {&value0, &value1, &value2, &value3}; +// +// (void)T_MIXED(callback_f32_to_i32, arrp, 1.0); +// +// /* Comparator. */ +// int32_t lhs = 1; +// int32_t rhs = 2; +// +// (void)T_CMP_I32_ASCENDING(&lhs, &rhs); +// +// /* Quicksort. */ +// int32_t values[] = {42, 7, 19, 3, 11}; +// +// T_QSORT(values, sizeof(values) / sizeof(values[0]), sizeof(values[0]), +// T_CMP_I32_ASCENDING); +// +// return 0; +// } diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-fn-ptr-return-type.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-fn-ptr-return-type.rs new file mode 100644 index 0000000000000..99194fc2289c0 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-fn-ptr-return-type.rs @@ -0,0 +1,61 @@ +// ignore-tidy-file-linelength +//@ add-minicore +//@ only-pauthtest +// Run it at O0, so that the compiler doesn't optimise the calls away. +//@ revisions: DISC NO_DISC +//@ [DISC] needs-llvm-components: aarch64 +//@ [DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=+function-pointer-type-discrimination -C opt-level=0 +//@ [NO_DISC] needs-llvm-components: aarch64 +//@ [NO_DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=-function-pointer-type-discrimination -C opt-level=0 + +// Test generation of function-pointer type discriminators. The discriminator values were obtained +// from Clang by compiling equivalent C code (included). Both compilers must generate identical +// values. +// +// Test generation of function-pointer type discriminators for functions returning a function +// pointer themselves. +// +// Equivalent C sample: +// +// ```c +// int (*f_ret_fp(int x))(int); +// +// int (*(*T_RET_FP)(int))(int) = f_ret_fp; +// +// int main(void) { +// int (*cb)(int) = T_RET_FP(123); +// return cb(456); +// } +// ``` + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] +extern crate minicore; + +extern "C" { + fn f_ret_fp(x: i32) -> extern "C" fn(i32) -> i32; +} + +type FnCallback = extern "C" fn(i32) -> i32; +type FnRetFp = unsafe extern "C" fn(i32) -> FnCallback; + +#[used] +// discriminator: 32957 (0x80BD), encoding: FPiE +// DISC: @{{.*}}T_RET_FP = constant ptr ptrauth (ptr @f_ret_fp, i32 0, i64 32957), align 8 +// NO_DISC: @{{.*}}T_RET_FP = constant ptr ptrauth (ptr @f_ret_fp, i32 0), align 8 +static T_RET_FP: FnRetFp = f_ret_fp; + +pub fn main() { + unsafe { + // discriminator: 32957 (0x80BD), encoding: FPiE + // DISC: [[CB:%.*]] = call ptr ptrauth (ptr @f_ret_fp, i32 0, i64 32957)(i32 123) {{.*}} [ "ptrauth"(i32 0, i64 32957) ] + // NO_DISC: [[CB:%.*]] = call ptr ptrauth (ptr @f_ret_fp, i32 0)(i32 123) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let cb = T_RET_FP(123); + // discriminator: 2981 (0x0BA5), encoding: FiiE + // DISC: call i32 [[CB]](i32 456) {{.*}} [ "ptrauth"(i32 0, i64 2981) ] + // NO_DISC: call i32 [[CB]](i32 456) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = cb(456); + } +} diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-callback.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-callback.rs new file mode 100644 index 0000000000000..2e7d2270357fd --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-callback.rs @@ -0,0 +1,124 @@ +// ignore-tidy-file-linelength +//@ add-minicore +//@ only-pauthtest +// Run it at O0, so that the compiler doesn't optimise the calls away. +//@ revisions: DISC NO_DISC +//@ [DISC] needs-llvm-components: aarch64 +//@ [DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=+function-pointer-type-discrimination -C opt-level=0 +//@ [NO_DISC] needs-llvm-components: aarch64 +//@ [NO_DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=-function-pointer-type-discrimination -C opt-level=0 + +// Test generation of function-pointer type discriminators. The discriminator values were obtained +// from Clang by compiling equivalent C code (included). Both compilers must generate identical +// values. +// +// Emulate NULL-able function argument with Option. Make sure that Option is treated +// as function pointer - encoded as P. +// +// Equivalent C sample: +// +// ```c +// #include +// +// typedef int (*FnCallback)(int); +// +// int f_opt(FnCallback cb); +// int f_raw(FnCallback cb); +// +// int d_opt(void *ctx); +// int d_raw(void *ctx); +// +// int callback_i32(int); +// +// int (*T_OPT)(FnCallback) = f_opt; +// int (*T_RAW)(FnCallback) = f_raw; +// +// int (*D_OPT)(void *) = d_opt; +// int (*D_RAW)(void *) = d_raw; +// int main(void) { +// /* function pointers */ +// T_OPT(callback_i32); +// T_OPT(NULL); +// +// T_RAW(callback_i32); +// +// /* data pointers */ +// int x = 42; +// D_OPT(&x); +// D_OPT(NULL); +// +// return 0; +// } +// ``` + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] + +extern crate minicore; +use minicore::Option::{None, Some}; +use minicore::{Option, c_void}; + +extern "C" { + fn f_opt(cb: Option i32>) -> i32; + fn f_raw(cb: unsafe extern "C" fn(i32) -> i32) -> i32; + + fn g_opt(ctx: Option<*mut c_void>) -> i32; + fn g_raw(ctx: *mut c_void) -> i32; + + fn callback_i32(x: i32) -> i32; +} + +type FnOpt = unsafe extern "C" fn(Option i32>) -> i32; +type FnRaw = unsafe extern "C" fn(unsafe extern "C" fn(i32) -> i32) -> i32; +type DataOpt = unsafe extern "C" fn(Option<*mut c_void>) -> i32; +type DataRaw = unsafe extern "C" fn(*mut c_void) -> i32; + +#[used] +// DISC: @{{.*}}T_OPT = constant ptr ptrauth (ptr @{{.*}}f_opt, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}T_OPT = constant ptr ptrauth (ptr @{{.*}}f_opt, i32 0), align 8 +static T_OPT: FnOpt = f_opt; +#[used] +// DISC: @{{.*}}T_RAW = constant ptr ptrauth (ptr @{{.*}}f_raw, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}T_RAW = constant ptr ptrauth (ptr @{{.*}}f_raw, i32 0), align 8 +static T_RAW: FnRaw = f_raw; + +// DISC: @{{.*}}G_OPT = constant ptr ptrauth (ptr @{{.*}}g_opt, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}G_OPT = constant ptr ptrauth (ptr @{{.*}}g_opt, i32 0), align 8 +#[used] +static G_OPT: DataOpt = g_opt; + +// DISC: @{{.*}}G_RAW = constant ptr ptrauth (ptr @{{.*}}g_raw, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}G_RAW = constant ptr ptrauth (ptr @{{.*}}g_raw, i32 0), align 8 +#[used] +static G_RAW: DataRaw = g_raw; +// CHECK-LABEL: main +pub fn main() { + let mut x = 42i32; + + unsafe { + // Function pointers + //DISC: call i32 ptrauth (ptr @f_opt, i32 0, i64 12410)(ptr ptrauth (ptr @callback_i32, i32 0, i64 2981)) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + //NO_DISC: call i32 ptrauth (ptr @f_opt, i32 0)(ptr ptrauth (ptr @callback_i32, i32 0)) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_OPT(Some(callback_i32)); + //DISC: call i32 ptrauth (ptr @f_opt, i32 0, i64 12410)(ptr null) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + //NO_DISC: call i32 ptrauth (ptr @f_opt, i32 0)(ptr null) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_OPT(None); + // DISC: call i32 ptrauth (ptr @f_raw, i32 0, i64 12410)(ptr ptrauth (ptr @callback_i32, i32 0, i64 2981)) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @f_raw, i32 0)(ptr ptrauth (ptr @callback_i32, i32 0)) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_RAW(callback_i32); + + // Data pointers + // DISC: call i32 ptrauth (ptr @g_opt, i32 0, i64 12410){{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @g_opt, i32 0){{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = G_OPT(Some((&mut x as *mut i32) as *mut c_void)); + // DISC: call i32 ptrauth (ptr @g_opt, i32 0, i64 12410){{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @g_opt, i32 0){{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = G_OPT(None); + + // DISC: call i32 ptrauth (ptr @g_raw, i32 0, i64 12410){{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @g_raw, i32 0){{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = G_RAW((&mut x as *mut i32) as *mut c_void); + } +} diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-return.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-return.rs new file mode 100644 index 0000000000000..92c6c9172b35d --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-return.rs @@ -0,0 +1,62 @@ +// ignore-tidy-file-linelength +//@ add-minicore +//@ only-pauthtest +// Run it at O0, so that the compiler doesn't optimise the calls away. +//@ revisions: DISC NO_DISC +//@ [DISC] needs-llvm-components: aarch64 +//@ [DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=+function-pointer-type-discrimination -C opt-level=0 +//@ [NO_DISC] needs-llvm-components: aarch64 +//@ [NO_DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=-function-pointer-type-discrimination -C opt-level=0 + +// Test generation of function-pointer type discriminators. The discriminator values were obtained +// from Clang by compiling equivalent C code (included). Both compilers must generate identical +// values. +// +// Test generation of function-pointer type discriminators for optional returns. +// +// Equivalent C sample: +// +// ```c +// typedef int (*FnCallback)(int); +// FnCallback f_ret_option(void); +// FnCallback (*T_RET_OPTION)(void) = f_ret_option; +// +// int main(void) { +// FnCallback cb = T_RET_OPTION(); +// +// if (cb) +// cb(123); +// } +// ``` + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] + +extern crate minicore; +use minicore::Option; +use minicore::Option::{None, Some}; + +extern "C" { + fn f_ret_option() -> Option i32>; +} + +type FnRetOption = unsafe extern "C" fn() -> Option i32>; + +#[used] +// DISC: @{{.*}}T_RET_OPTION = constant ptr ptrauth (ptr @{{.*}}f_ret_option, i32 0, i64 34128), align 8 +// NO_DISC: @{{.*}}T_RET_OPTION = constant ptr ptrauth (ptr @{{.*}}f_ret_option, i32 0), align 8 +static T_RET_OPTION: FnRetOption = f_ret_option; + +pub fn main() { + unsafe { + // DISC: call ptr ptrauth (ptr @f_ret_option, i32 0, i64 34128)() {{.*}} [ "ptrauth"(i32 0, i64 34128) ] + // NO_DISC: call ptr ptrauth (ptr @f_ret_option, i32 0)() {{.*}} [ "ptrauth"(i32 0, i64 0) ] + if let Some(cb) = T_RET_OPTION() { + // DISC: call i32 %cb(i32 123) {{.*}} [ "ptrauth"(i32 0, i64 2981) ] + // NO_DISC: call i32 %cb(i32 123) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = cb(123); + } + } +} diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option.rs new file mode 100644 index 0000000000000..fac01fcbae5c8 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option.rs @@ -0,0 +1,48 @@ +// ignore-tidy-file-linelength +//@ add-minicore +//@ only-pauthtest +// Run it at O0, so that the compiler doesn't optimise the calls away. + +//@ revisions: DISC NO_DISC +//@ [DISC] needs-llvm-components: aarch64 +//@ [DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=+function-pointer-type-discrimination -C opt-level=0 +//@ [NO_DISC] needs-llvm-components: aarch64 +//@ [NO_DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=-function-pointer-type-discrimination -C opt-level=0 + +// Test generation of function-pointer type discriminators. The discriminator values were obtained +// from Clang by compiling equivalent C code (included). Both compilers must generate identical +// values. +// +// Test generation of function-pointer type discriminators for optional variables. +// +// Equivalent C sample: +// +// ```c +// extern void f(int); +// void (*test_constant_null)(int) = 0; +// void (*test_constant_non_null)(int) = f; +// ``` + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] + +extern crate minicore; +use minicore::Option; +use minicore::Option::{None, Some}; + +extern "C" fn f(_: i32) {} + +// Rust function pointers are no-nullable, so this can not be expressed: +// void (*test_constant_null)(int) = 0; +// Use Option instead. +type TestConstantNullTy = unsafe extern "C" fn(i32); + +#[used] +// DISC: @{{.*}}TEST_CONSTANT_NON_NULL = constant ptr ptrauth (ptr @{{.*}}f, i32 0, i64 2712), align 8 +// NO_DISC: @{{.*}}TEST_CONSTANT_NON_NULL = constant ptr ptrauth (ptr @{{.*}}f, i32 0), align 8 +static TEST_CONSTANT_NON_NULL: Option = Some(f); +#[used] +// CHECK: @{{.*}}TEST_CONSTANT_NULL = constant {{.*}} zeroinitializer, align 8 +static TEST_CONSTANT_NULL: Option = None; diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-running-test.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-running-test.rs new file mode 100644 index 0000000000000..3fa3c90ffdf5e --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-running-test.rs @@ -0,0 +1,304 @@ +// ignore-tidy-file-linelength +//@ add-minicore +//@ only-pauthtest +// Run it at O0, so that the compiler doesn't optimise the calls away. + +//@ revisions: DISC NO_DISC +//@ [DISC] needs-llvm-components: aarch64 +//@ [DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=+function-pointer-type-discrimination -C opt-level=0 +//@ [NO_DISC] needs-llvm-components: aarch64 +//@ [NO_DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=-function-pointer-type-discrimination -C opt-level=0 + +// This is a Rust reimplementation of Clang's main type-discrimination test: +// https://github.com/llvm/llvm-project/blob/main/clang/test/CodeGen/ptrauth-function-type-discriminator.c +// Variable and function names match the original C test. + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] + +extern crate minicore; +use minicore::Option::{None, Some}; +use minicore::hint::black_box; +use minicore::mem::transmute; +use minicore::{Option, c_void, ptr}; + +extern "C" fn f() {} +extern "C" fn f2(_: i32) {} + +// 1 +// Rust function pointers are no-nullable, so this can not be expressed directly. +// ```c +// void (*test_constant_null)(int) = 0; +// ``` +// Use Option instead. +type TestConstantNullTy = unsafe extern "C" fn(i32); + +#[used] +// CHECK-DAG: @{{.*}}TEST_CONSTANT_NULL = constant {{.*}} zeroinitializer, +static TEST_CONSTANT_NULL: Option = None; +#[used] +// DISC-DAG: @{{.*}}TEST_CONSTANT_NON_NULL = constant ptr ptrauth (ptr @{{.*}}f2, i32 0, i64 2712), align 8 +// NO_DISC-DAG: @{{.*}}TEST_CONSTANT_NON_NULL = constant ptr ptrauth (ptr @{{.*}}f2, i32 0), align 8 +static TEST_CONSTANT_NON_NULL: Option = Some(f2); + +// 2 +// Clang expects to generate the discriminator based on the "casted to" type +// ```c +// void f(void); +// void (*test_constant_cast)(int) = (void (*)(int))f; +// ``` +// Rust does not allow incompatible function pointer casts. `transmute` seems to be the closes to +// the cast. +#[used] +// DISC-DAG: @{{.*}}TEST_CONSTANT_CAST = constant ptr ptrauth (ptr @{{.*}}f, i32 0, i64 2712), align 8 +// NO_DISC-DAG: @{{.*}}TEST_CONSTANT_CAST = constant ptr ptrauth (ptr @{{.*}}f, i32 0), align 8 +static TEST_CONSTANT_CAST: unsafe extern "C" fn(i32) = unsafe { transmute(f as extern "C" fn()) }; + +// 3 +// Clang can handle incomplete enum declaration, collapsing it to int: +// ```c +// enum Enum0; +// void enum_func(enum Enum0); +// void (*enum_func_ptr)(enum Enum0) = enum_func; +// ``` +// Mimic it with type Enum0 assigned to i32 and `__opaque`. +type Enum0 = i32; +#[repr(C)] +enum Enum1 { + __opaque, +} +extern "C" { + fn enum_func(arg: Enum0); +} +unsafe extern "C" fn enum_func_1(_x: Enum1) {} +#[used] +// DISC-DAG: @{{.*}}TEST_ENUM_FUNC_PTR = constant ptr ptrauth (ptr @{{.*}}enum_func, i32 0, i64 2712), align 8 +// NO_DISC-DAG: @{{.*}}TEST_ENUM_FUNC_PTR = constant ptr ptrauth (ptr @{{.*}}enum_func, i32 0), align 8 +static TEST_ENUM_FUNC_PTR: unsafe extern "C" fn(Enum0) = enum_func; +#[used] +// DISC-DAG: @{{.*}}TEST_ENUM_FUNC_PTR_1 = constant ptr ptrauth (ptr @{{.*}}enum_func_1, i32 0, i64 2712), align 8 +// NO_DISC-DAG: @{{.*}}TEST_ENUM_FUNC_PTR_1 = constant ptr ptrauth (ptr @{{.*}}enum_func_1, i32 0), align 8 +static TEST_ENUM_FUNC_PTR_1: unsafe extern "C" fn(Enum1) = enum_func_1; + +// 4 +// Rust can't fn -> *mut c_void casts. Use a chain of transmute. +// ```c +// void *test_opaque = +// #ifdef __cplusplus +// (void *) +// #endif +// (void (*)(int))(double (*)(double))f; +// ``` +// We expect zero-discriminator. +#[used] +// CHECK-DAG: @{{.*}}TEST_OPAQUE = {{.*}} ptr ptrauth (ptr @{{.*}}f, i32 0), align 8 +static mut TEST_OPAQUE: *const c_void = unsafe { + let p: extern "C" fn(f64) -> f64 = transmute:: f64>(f); + transmute:: f64, *const c_void>(p) +}; +#[used] +// Also test a case that uses: as *const c_void. +// CHECK-DAG: @{{.*}}TEST_OPAQUE_1 = {{.*}} ptr ptrauth (ptr @{{.*}}f, i32 0), align 8 +static mut TEST_OPAQUE_1: *const c_void = f as *const () as *const c_void; + +// 5 +// ```c +// unsigned long test_intptr_t = (unsigned long)f; +// ``` +// This is explicitly forbidden in Rust. Hypothetically we could get it through: +// #[used] +// static TEST_INTPTR_T: usize = f as usize; +// #[used] +// static TEST_INTPTR_T_1: usize = unsafe { +// transmute::(f) +// }; +// But the compiler would not allow that issuing an error: +// error: pointers cannot be cast to integers during const eval +// And diagnostic: +// * for TEST_INTPTR_T: +// | static TEST_INTPTR_T: usize = f as usize; +// | ^^^^^^^^^^ +// | +// = note: at compile-time, pointers do not have an integer value +// = note: avoiding this restriction via `transmute`, `union`, or raw pointers leads to compile-time undefined behavior +// * for TEST_INTPTR_T_1: +// | static TEST_INTPTR_T_1: usize = unsafe { +// | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ evaluation of `TEST_INTPTR_T_1` failed here +// | +// = help: this code performed an operation that depends on the underlying bytes representing a pointer +// = help: the absolute address of a pointer is not known at compile-time, so such operations are not supported +// +// The same limitation applies to: +// 6 +// ```c +// void (*test_through_long)(int) = (void (*)(int))(long)f; +// ``` +// 7 +// ```c +// long test_to_long = (long)(double (*)())f; +// ``` + +extern "C" fn external_function() {} + +// 8 and 9 +// In Rust function automatically decays to function pointer. Furthermore, `&` used on function is +// not meant to produce a pointer to the function, instead it generates a reference to the function +// item. Use an intermediate `REF` variable to perform a round trip through reference. +// ```c +// void (*fptr1)(void) = external_function; +// void (*fptr2)(void) = &external_function; +// ``` +#[used] +// DISC-DAG: @{{.*}}FPTR1 = constant ptr ptrauth (ptr @{{.*}}external_function, i32 0, i64 18983), align 8 +// NO_DISC-DAG: @{{.*}}FPTR1 = constant ptr ptrauth (ptr @{{.*}}external_function, i32 0), align 8 +static FPTR1: extern "C" fn() = external_function; +// 9 +#[used] +static REF: &extern "C" fn() = &(external_function as extern "C" fn()); +#[used] +// DISC-DAG: @{{.*}}FPTR2 = constant ptr ptrauth (ptr @{{.*}}external_function, i32 0, i64 18983), align 8 +// NO_DISC-DAG: @{{.*}}FPTR2 = constant ptr ptrauth (ptr @{{.*}}external_function, i32 0), align 8 +static FPTR2: extern "C" fn() = *REF; + +// Rust doesn't support `__builtin_ptrauth_blend_discriminator` or `__builtin_ptrauth_sign_constant` +// builtins. +// 10 +// ```c +// void (*fptr3)(void) = __builtin_ptrauth_sign_constant(&external_function, 2, 26); +// ``` +// 11 +// ```c +// void (*fptr4)(void) = __builtin_ptrauth_sign_constant(&external_function, 2, __builtin_ptrauth_blend_discriminator(&fptr4, 26)); +// ``` + +// 12 +// Test calling through a global function pointer. +// ```c +// void (*fnptr)(void); +// void test_call() { +// fnptr(); +// } +// ``` +#[used] +// DISC-DAG: @{{.*}}FNPTR = {{.*}}ptr ptrauth (ptr @{{.*}}external_function, i32 0, i64 18983), align 8 +// NO_DISC-DAG: @{{.*}}FNPTR = {{.*}}ptr ptrauth (ptr @{{.*}}external_function, i32 0), align 8 +static mut FNPTR: extern "C" fn() = external_function; +// CHECK-LABEL {{.*}}test_call +pub unsafe fn test_call() { + // CHECK: [[FNPTR_PTR:%.*]] = load ptr, ptr @{{.*}}FNPTR, align 8 + // DISC: call void [[FNPTR_PTR]]() {{.*}} "ptrauth"(i32 0, i64 18983) ] + // NO_DISC: call void [[FNPTR_PTR]]() {{.*}} "ptrauth"(i32 0, i64 0) ] + FNPTR(); +} + +// 13 +// ```c +// void (*test_function_pointer())(void) { +// return external_function; +// } +// ``` +// CHECK-LABEL: @{{.*}}test_function_pointer +pub extern "C" fn test_function_pointer() -> extern "C" fn() { + // DISC: ret ptr ptrauth (ptr @{{.*}}external_function, i32 0, i64 18983) + // NO_DISC: ret ptr ptrauth (ptr @{{.*}}external_function, i32 0) + external_function +} + +// 14 +// C tests that the discriminator is stable when a struct type transitions from incomplete to +// complete. Rust has no notion of type completion, so this case has no direct equivalent. +// ```c +// struct InitiallyIncomplete; +// extern struct InitiallyIncomplete returns_initially_incomplete(void); +// +// void use_while_incomplete() { +// struct InitiallyIncomplete (*fnptr)(void) = &returns_initially_incomplete; +// } +// +// struct InitiallyIncomplete { int x; }; +// void use_while_complete() { +// struct InitiallyIncomplete (*fnptr)(void) = &returns_initially_incomplete; +// } +// ``` +// Test each case in isolation (complete/incomplete) - the difference in discrimnators is expected. +#[repr(C)] +pub struct InitiallyIncomplete { + _private: [u8; 0], +} + +extern "C" fn returns_initially_incomplete() -> InitiallyIncomplete { + InitiallyIncomplete { _private: [] } +} + +// CHECK-LABEL: @{{.*}}use_while_incomplete +pub unsafe fn use_while_incomplete() { + // DISC: call ptr @{{.*}}InitiallyIncomplete{{.*}}(ptr ptrauth (ptr @{{.*}}returns_initially_incomplete, i32 0, i64 25106)) + // NO_DISC: call ptr @{{.*}}InitiallyIncomplete{{.*}}(ptr ptrauth (ptr @{{.*}}returns_initially_incomplete, i32 0)) + let INITIALLY_INCOMPLETE_FNPTR: extern "C" fn() -> InitiallyIncomplete = + returns_initially_incomplete; + + black_box(INITIALLY_INCOMPLETE_FNPTR); +} + +#[repr(C)] +pub struct InitiallyComplete { + x: i32, +} +extern "C" fn returns_initially_complete() -> InitiallyComplete { + { InitiallyComplete { x: 42 } } +} +// CHECK-LABEL: @{{.*}}use_while_complete +pub fn use_while_complete() { + // DISC: call ptr @{{.*}}InitiallyComplete{{.*}}(ptr ptrauth (ptr @{{.*}}returns_initially_complete, i32 0, i64 9528)) + // NO_DISC: call ptr @{{.*}}InitiallyComplete{{.*}}(ptr ptrauth (ptr @{{.*}}returns_initially_complete, i32 0)) + let INITIALLY_COMPLETE_FNPTR: extern "C" fn() -> InitiallyComplete = returns_initially_complete; + black_box(INITIALLY_COMPLETE_FNPTR); +} + +// 15 +// K&R function definition can be expressed in Rust and in any case a function definition without a +// prototype is deprecated in all versions of C and is not supported in C23 +// ```c +// void knr(param) +// int param; +// {} +// +// void test_knr() { +// void (*p)() = knr; +// p(0); +// } +// ``` + +// 16 +// Rust does not allow for redeclaration of functions +// ```c +// void test_redeclaration() { +// void redecl(); +// void (*ptr)() = redecl; +// void redecl(int); +// void (*ptr2)(int) = redecl; +// ptr(); +// ptr2(0); +// } +// ``` + +// 17 +// This is redeclaration of functions using Kernighan and Ritchie notation, not supported. +// ```c +// void knr2(param) +// int param; +// {} +// +// void test_redecl_knr() { +// void (*p)() = knr2; +// p(); +// +// void knr2(int); +// +// void (*p2)(int) = knr2; +// p2(0); +// +// } +// ``` diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-rust-array.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-rust-array.rs new file mode 100644 index 0000000000000..7d47c886d36e5 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-rust-array.rs @@ -0,0 +1,111 @@ +// ignore-tidy-file-linelength +//@ add-minicore +//@ only-pauthtest +// Run it at O0, so that the compiler doesn't optimise the calls away. + +//@ revisions: DISC NO_DISC +//@ [DISC] needs-llvm-components: aarch64 +//@ [DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=+function-pointer-type-discrimination -C opt-level=0 +//@ [NO_DISC] needs-llvm-components: aarch64 +//@ [NO_DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=-function-pointer-type-discrimination -C opt-level=0 + +// Test generation of function-pointer type discriminators. The discriminator values were obtained +// from Clang by compiling equivalent C code (included). Both compilers must generate identical +// values. +// +// Make sure that signing/auth happens for every element of an array. +// +// Equivalent C sample: +// +// ```c +// #include +// +// typedef int32_t (*Fn)(int32_t); +// +// struct S { +// Fn f; +// uint32_t x; +// }; +// +// int32_t foo(int32_t x) { return x + 1; } +// +// __attribute__((used)) static const struct S TEST_ARR[3] = { +// {.f = foo, .x = 1}, +// {.f = foo, .x = 2}, +// {.f = foo, .x = 3}, +// }; +// +// __attribute__((noinline)) int32_t use_array(const struct S (*arr)[3]) { +// const struct S *a = &(*arr)[0]; +// const struct S *b = &(*arr)[1]; +// const struct S *c = &(*arr)[2]; +// +// return a->f((int32_t)a->x) + b->f((int32_t)b->x) + c->f((int32_t)c->x); +// } +// +// int32_t test(void) { +// struct S TEST_LOCAL_ARR[3]; +// TEST_LOCAL_ARR[0].f = foo; +// TEST_LOCAL_ARR[0].x = 1; +// TEST_LOCAL_ARR[1].f = foo; +// TEST_LOCAL_ARR[1].x = 2; +// TEST_LOCAL_ARR[2].f = foo; +// TEST_LOCAL_ARR[2].x = 3; +// +// return use_array(&TEST_ARR) + use_array(&TEST_LOCAL_ARR); +// } +// ``` + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] + +extern crate minicore; +use minicore::mem; + +type Fn = extern "C" fn(i32) -> i32; + +#[repr(C)] +pub struct S { + pub f: Fn, + pub x: u32, +} + +extern "C" fn foo(x: i32) -> i32 { + x + 1 +} + +#[used] +// DISC: @{{.*}}TEST_ARR = {{.*}} ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 2981), {{.*}} ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 2981), {{.*}} ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 2981) +// NO_DISC: @{{.*}}TEST_ARR = {{.*}} ptr ptrauth (ptr @{{.*}}foo, i32 0), {{.*}} ptr ptrauth (ptr @{{.*}}foo, i32 0), {{.*}} ptr ptrauth (ptr @{{.*}}foo, i32 0) +static TEST_ARR: [S; 3] = [S { f: foo, x: 1 }, S { f: foo, x: 2 }, S { f: foo, x: 3 }]; + +#[inline(never)] +// CHECK-LABEL: use_array +pub fn use_array(arr: &[S; 3]) -> i32 { + let [a, b, c] = arr; + // DISC: call i32 {{.*}} [ "ptrauth"(i32 0, i64 2981) ] + // DISC: call i32 {{.*}} [ "ptrauth"(i32 0, i64 2981) ] + // DISC: call i32 {{.*}} [ "ptrauth"(i32 0, i64 2981) ] + // NO_DISC: call i32 {{.*}} [ "ptrauth"(i32 0, i64 0) ] + // NO_DISC: call i32 {{.*}} [ "ptrauth"(i32 0, i64 0) ] + // NO_DISC: call i32 {{.*}} [ "ptrauth"(i32 0, i64 0) ] + (a.f)(a.x as i32) + (b.f)(b.x as i32) + (c.f)(c.x as i32) +} + +#[no_mangle] +// CHECK-LABEL: test +pub fn test() -> i32 { + // DISC: store ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 2981) + // NO_DISC: store ptr ptrauth (ptr @{{.*}}foo, i32 0) + // CHECK: store i32 1 + // DISC: store ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 2981) + // NO_DISC: store ptr ptrauth (ptr @{{.*}}foo, i32 0) + // CHECK: store i32 2 + // DISC: store ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 2981) + // NO_DISC: store ptr ptrauth (ptr @{{.*}}foo, i32 0) + // CHECK: store i32 3 + let TEST_LOCAL_ARR: [S; 3] = [S { f: foo, x: 1 }, S { f: foo, x: 2 }, S { f: foo, x: 3 }]; + use_array(&TEST_ARR) + use_array(&TEST_LOCAL_ARR) +} diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-simd.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-simd.rs new file mode 100644 index 0000000000000..f4589e9add436 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-simd.rs @@ -0,0 +1,208 @@ +// ignore-tidy-file-linelength +//@ add-minicore +//@ only-pauthtest +// Run it at O0, so that the compiler doesn't optimise the calls away. + +//@ revisions: DISC NO_DISC +//@ [DISC] needs-llvm-components: aarch64 +//@ [DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=+function-pointer-type-discrimination -C opt-level=0 +//@ [NO_DISC] needs-llvm-components: aarch64 +//@ [NO_DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=-function-pointer-type-discrimination -C opt-level=0 + +// Test generation of function-pointer type discriminators. The discriminator values were obtained +// from Clang by compiling equivalent C code (included). Both compilers must generate identical +// values. +// +// For encoding purposes, Clang is only interested in the total size of the vector, so all the +// combinations below should generate the same encoding: FDv16Dv16E, discriminator: 34246 (0x85C6). + +#![feature(no_core, lang_items, repr_simd, simd_ffi)] +#![no_std] +#![no_core] +#![crate_type = "lib"] + +extern crate minicore; + +#[repr(simd)] +struct I8x16([i8; 16]); +#[repr(simd)] +struct I16x8([i16; 8]); +#[repr(simd)] +struct I32x4([i32; 4]); +#[repr(simd)] +struct I64x2([i64; 2]); +#[repr(simd)] +struct U8x16([u8; 16]); +#[repr(simd)] +struct U16x8([u16; 8]); +#[repr(simd)] +struct U32x4([u32; 4]); +#[repr(simd)] +struct U64x2([u64; 2]); +#[repr(simd)] +struct F32x4([f32; 4]); +#[repr(simd)] +struct F64x2([f64; 2]); + +extern "C" { + fn f_i8x16(x: I8x16) -> I8x16; + fn f_i16x8(x: I16x8) -> I16x8; + fn f_i32x4(x: I32x4) -> I32x4; + fn f_i64x2(x: I64x2) -> I64x2; + fn f_u8x16(x: U8x16) -> U8x16; + fn f_u16x8(x: U16x8) -> U16x8; + fn f_u32x4(x: U32x4) -> U32x4; + fn f_u64x2(x: U64x2) -> U64x2; + fn f_f32x4(x: F32x4) -> F32x4; + fn f_f64x2(x: F64x2) -> F64x2; +} + +type FnI8x16 = unsafe extern "C" fn(I8x16) -> I8x16; +type FnI16x8 = unsafe extern "C" fn(I16x8) -> I16x8; +type FnI32x4 = unsafe extern "C" fn(I32x4) -> I32x4; +type FnI64x2 = unsafe extern "C" fn(I64x2) -> I64x2; +type FnU8x16 = unsafe extern "C" fn(U8x16) -> U8x16; +type FnU16x8 = unsafe extern "C" fn(U16x8) -> U16x8; +type FnU32x4 = unsafe extern "C" fn(U32x4) -> U32x4; +type FnU64x2 = unsafe extern "C" fn(U64x2) -> U64x2; +type FnF32x4 = unsafe extern "C" fn(F32x4) -> F32x4; +type FnF64x2 = unsafe extern "C" fn(F64x2) -> F64x2; + +#[used] +// DISC: {{.*}}T_I8x16 = constant ptr ptrauth (ptr @f_i8x16, i32 0, i64 34246) +// NO_DISC: {{.*}}T_I8x16 = constant ptr ptrauth (ptr @f_i8x16, i32 0) +static T_I8x16: FnI8x16 = f_i8x16; +#[used] +// DISC: {{.*}}T_I16x8 = constant ptr ptrauth (ptr @f_i16x8, i32 0, i64 34246) +// NO_DISC: {{.*}}T_I16x8 = constant ptr ptrauth (ptr @f_i16x8, i32 0) +static T_I16x8: FnI16x8 = f_i16x8; +#[used] +// DISC: {{.*}}T_I32x4 = constant ptr ptrauth (ptr @f_i32x4, i32 0, i64 34246) +// NO_DISC: {{.*}}T_I32x4 = constant ptr ptrauth (ptr @f_i32x4, i32 0) +static T_I32x4: FnI32x4 = f_i32x4; +#[used] +// DISC: {{.*}}T_I64x2 = constant ptr ptrauth (ptr @f_i64x2, i32 0, i64 34246) +// NO_DISC: {{.*}}T_I64x2 = constant ptr ptrauth (ptr @f_i64x2, i32 0) +static T_I64x2: FnI64x2 = f_i64x2; +#[used] +// DISC: {{.*}}T_U8x16 = constant ptr ptrauth (ptr @f_u8x16, i32 0, i64 34246) +// NO_DISC: {{.*}}T_U8x16 = constant ptr ptrauth (ptr @f_u8x16, i32 0) +static T_U8x16: FnU8x16 = f_u8x16; +#[used] +// DISC: {{.*}}T_U16x8 = constant ptr ptrauth (ptr @f_u16x8, i32 0, i64 34246) +// NO_DISC: {{.*}}T_U16x8 = constant ptr ptrauth (ptr @f_u16x8, i32 0) +static T_U16x8: FnU16x8 = f_u16x8; +#[used] +// DISC: {{.*}}T_U32x4 = constant ptr ptrauth (ptr @f_u32x4, i32 0, i64 34246) +// NO_DISC: {{.*}}T_U32x4 = constant ptr ptrauth (ptr @f_u32x4, i32 0) +static T_U32x4: FnU32x4 = f_u32x4; +#[used] +// DISC: {{.*}}T_U64x2 = constant ptr ptrauth (ptr @f_u64x2, i32 0, i64 34246) +// NO_DISC: {{.*}}T_U64x2 = constant ptr ptrauth (ptr @f_u64x2, i32 0) +static T_U64x2: FnU64x2 = f_u64x2; +#[used] +// DISC: {{.*}}T_F32x4 = constant ptr ptrauth (ptr @f_f32x4, i32 0, i64 34246) +// NO_DISC: {{.*}}T_F32x4 = constant ptr ptrauth (ptr @f_f32x4, i32 0) +static T_F32x4: FnF32x4 = f_f32x4; +#[used] +// DISC: {{.*}}T_F64x2 = constant ptr ptrauth (ptr @f_f64x2, i32 0, i64 34246) +// NO_DISC: {{.*}}T_F64x2 = constant ptr ptrauth (ptr @f_f64x2, i32 0) +static T_F64x2: FnF64x2 = f_f64x2; + +pub fn main() { + unsafe { + // DISC: call <16 x i8> ptrauth (ptr @f_i8x16, i32 0, i64 34246)(<16 x i8> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 34246) ] + // NO_DISC: call <16 x i8> ptrauth (ptr @f_i8x16, i32 0)(<16 x i8> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_I8x16(I8x16([0; 16])); + // DISC: call <8 x i16> ptrauth (ptr @f_i16x8, i32 0, i64 34246)(<8 x i16> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 34246) ] + // NO_DISC: call <8 x i16> ptrauth (ptr @f_i16x8, i32 0)(<8 x i16> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_I16x8(I16x8([0; 8])); + // DISC: call <4 x i32> ptrauth (ptr @f_i32x4, i32 0, i64 34246)(<4 x i32> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 34246) ] + // NO_DISC: call <4 x i32> ptrauth (ptr @f_i32x4, i32 0)(<4 x i32> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_I32x4(I32x4([0; 4])); + // DISC: call <2 x i64> ptrauth (ptr @f_i64x2, i32 0, i64 34246)(<2 x i64> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 34246) ] + // NO_DISC: call <2 x i64> ptrauth (ptr @f_i64x2, i32 0)(<2 x i64> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_I64x2(I64x2([0; 2])); + // DISC: call <16 x i8> ptrauth (ptr @f_u8x16, i32 0, i64 34246)(<16 x i8> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 34246) ] + // NO_DISC: call <16 x i8> ptrauth (ptr @f_u8x16, i32 0)(<16 x i8> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_U8x16(U8x16([0; 16])); + // DISC: call <8 x i16> ptrauth (ptr @f_u16x8, i32 0, i64 34246)(<8 x i16> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 34246) ] + // NO_DISC: call <8 x i16> ptrauth (ptr @f_u16x8, i32 0)(<8 x i16> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_U16x8(U16x8([0; 8])); + // DISC: call <4 x i32> ptrauth (ptr @f_u32x4, i32 0, i64 34246)(<4 x i32> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 34246) ] + // NO_DISC: call <4 x i32> ptrauth (ptr @f_u32x4, i32 0)(<4 x i32> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_U32x4(U32x4([0; 4])); + // DISC: call <2 x i64> ptrauth (ptr @f_u64x2, i32 0, i64 34246)(<2 x i64> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 34246) ] + // NO_DISC: call <2 x i64> ptrauth (ptr @f_u64x2, i32 0)(<2 x i64> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_U64x2(U64x2([0; 2])); + // DISC: call <4 x float> ptrauth (ptr @f_f32x4, i32 0, i64 34246)(<4 x float> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 34246) ] + // NO_DISC: call <4 x float> ptrauth (ptr @f_f32x4, i32 0)(<4 x float> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_F32x4(F32x4([0.0; 4])); + // DISC: call <2 x double> ptrauth (ptr @f_f64x2, i32 0, i64 34246)(<2 x double> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 34246) ] + // NO_DISC: call <2 x double> ptrauth (ptr @f_f64x2, i32 0)(<2 x double> %{{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_F64x2(F64x2([0.0; 2])); + } +} + +// Equivalent C sample: +// +// ```c +// typedef signed char I8x16 __attribute__((vector_size(16))); +// typedef short I16x8 __attribute__((vector_size(16))); +// typedef int I32x4 __attribute__((vector_size(16))); +// typedef long long I64x2 __attribute__((vector_size(16))); +// +// typedef unsigned char U8x16 __attribute__((vector_size(16))); +// typedef unsigned short U16x8 __attribute__((vector_size(16))); +// typedef unsigned int U32x4 __attribute__((vector_size(16))); +// typedef unsigned long long U64x2 __attribute__((vector_size(16))); +// +// typedef float F32x4 __attribute__((vector_size(16))); +// typedef double F64x2 __attribute__((vector_size(16))); +// +// extern I8x16 f_i8x16(I8x16); +// extern I16x8 f_i16x8(I16x8); +// extern I32x4 f_i32x4(I32x4); +// extern I64x2 f_i64x2(I64x2); +// extern U8x16 f_u8x16(U8x16); +// extern U16x8 f_u16x8(U16x8); +// extern U32x4 f_u32x4(U32x4); +// extern U64x2 f_u64x2(U64x2); +// extern F32x4 f_f32x4(F32x4); +// extern F64x2 f_f64x2(F64x2); +// +// typedef I8x16 (*FnI8x16)(I8x16); +// typedef I16x8 (*FnI16x8)(I16x8); +// typedef I32x4 (*FnI32x4)(I32x4); +// typedef I64x2 (*FnI64x2)(I64x2); +// typedef U8x16 (*FnU8x16)(U8x16); +// typedef U16x8 (*FnU16x8)(U16x8); +// typedef U32x4 (*FnU32x4)(U32x4); +// typedef U64x2 (*FnU64x2)(U64x2); +// typedef F32x4 (*FnF32x4)(F32x4); +// typedef F64x2 (*FnF64x2)(F64x2); +// +// FnI8x16 T_I8x16 = f_i8x16; +// FnI16x8 T_I16x8 = f_i16x8; +// FnI32x4 T_I32x4 = f_i32x4; +// FnI64x2 T_I64x2 = f_i64x2; +// FnU8x16 T_U8x16 = f_u8x16; +// FnU16x8 T_U16x8 = f_u16x8; +// FnU32x4 T_U32x4 = f_u32x4; +// FnU64x2 T_U64x2 = f_u64x2; +// FnF32x4 T_F32x4 = f_f32x4; +// FnF64x2 T_F64x2 = f_f64x2; +// +// void test(void) { +// T_I8x16((I8x16){0}); +// T_I16x8((I16x8){0}); +// T_I32x4((I32x4){0}); +// T_I64x2((I64x2){0}); +// T_U8x16((U8x16){0}); +// T_U16x8((U16x8){0}); +// T_U32x4((U32x4){0}); +// T_U64x2((U64x2){0}); +// T_F32x4((F32x4){0.0f}); +// T_F64x2((F64x2){0.0}); +// } +// ``` diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-struct-members.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-struct-members.rs new file mode 100644 index 0000000000000..cbc719471419a --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-struct-members.rs @@ -0,0 +1,529 @@ +// ignore-tidy-file-linelength +//@ add-minicore +//@ only-pauthtest +// Run it at O0, so that the compiler doesn't optimise the calls away. + +//@ revisions: DISC NO_DISC +//@ [DISC] needs-llvm-components: aarch64 +//@ [DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=+function-pointer-type-discrimination -C opt-level=0 +//@ [NO_DISC] needs-llvm-components: aarch64 +//@ [NO_DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=-function-pointer-type-discrimination -C opt-level=0 + +// Test generation of function-pointer type discriminators. The discriminator values were obtained +// from Clang by compiling equivalent C code (included at the end of the file). Both compilers must +// generate identical values. +// +// Check the signing of internal members of structs that are themselves function pointers. + +#![feature(no_core, lang_items)] +#![crate_type = "lib"] +#![no_std] +#![no_core] + +extern crate minicore; +use minicore::{Sync, mem, ptr}; + +// Function definitions, used as members in structs. +extern "C" fn f() {} +extern "C" fn g(i32: i32) {} +extern "C" fn h(i64: i64, j: i64) {} +extern "C" fn i(i64: i64, b: i64, c: f32) {} + +// Structs... +#[repr(transparent)] +struct A(extern "C" fn()); + +#[repr(transparent)] +struct B(extern "C" fn(i32)); + +#[repr(transparent)] +struct C(extern "C" fn(i64, i64)); + +#[repr(transparent)] +struct NotFn(u64); + +#[repr(transparent)] +struct AlsoNotFn(u64); + +// and their wrappers (L - level). +#[repr(transparent)] +struct L1A(A); + +#[repr(transparent)] +struct L1B(B); + +#[repr(transparent)] +struct L2A(L1A); + +#[repr(transparent)] +struct L2B(L1B); + +#[repr(transparent)] +struct L3A(L2A); + +#[repr(transparent)] +struct L3B(L2B); + +#[repr(transparent)] +struct L4A(L3A); + +#[repr(transparent)] +struct L4B(L3B); + +#[repr(transparent)] +struct L5A(L4A); + +#[repr(transparent)] +struct L5B(L4B); + +#[repr(C)] +struct MixedPair { + f0: extern "C" fn(), + f1: extern "C" fn(i32), +} + +#[repr(transparent)] +struct L1NotFn(NotFn); + +#[repr(transparent)] +struct L1AlsoNotFn(AlsoNotFn); + +// Make sure that static initialization traverses struct members and uses correct discriminators. +// DISC-DAG: @{{.*}}T_TREE_SRC = internal constant <{ ptr, ptr, ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}f, i32 0, i64 18983), ptr ptrauth (ptr @{{.*}}g, i32 0, i64 2712), ptr ptrauth (ptr @{{.*}}h, i32 0, i64 55265), ptr ptrauth (ptr @{{.*}}i, i32 0, i64 44485) }> +// NO_DISC-DAG: @{{.*}}T_TREE_SRC = internal constant <{ ptr, ptr, ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}f, i32 0), ptr ptrauth (ptr @{{.*}}g, i32 0), ptr ptrauth (ptr @{{.*}}h, i32 0), ptr ptrauth (ptr @{{.*}}i, i32 0) }> +// DISC-DAG: @{{.*}}T_WRAPPED_FN_PTR = internal constant ptr ptrauth (ptr @{{.*}}g, i32 0, i64 2712) +// NO_DISC-DAG: @{{.*}}T_WRAPPED_FN_PTR = internal constant ptr ptrauth (ptr @{{.*}}g, i32 0) + +// Simplest fn ptr resign through a struct transmute. +#[inline(never)] +// CHECK-DAG: test_1_struct_resign +pub fn test_1_struct_resign() { + let a: A = A(f); + // DISC: [[PTR_RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 ptrtoint (ptr ptrauth (ptr @{{.*}}f, i32 0, i64 18983) to i64), i32 0, i64 18983, i32 0, i64 2712) + // DISC: [[INT_TO_PTR:%.*]] = inttoptr i64 [[PTR_RESIGNED]] to ptr + // DISC: store ptr [[INT_TO_PTR]], ptr [[PTR_STORED:%*.]] + // NO_DISC-NOT: call i64 @llvm.ptrauth.resign + // NO_DISC: store ptr ptrauth (ptr @{{.*}}f, i32 0), ptr [[STORE:%.*]], align 8 + let b: B = unsafe { mem::transmute(a) }; + + unsafe { + // DISC: [[PTR_RELOADED:%.*]] = load ptr, ptr [[PTR_STORED]] + // NO_DISC: [[LOAD:%.*]] = load ptr, ptr [[STORE]] + ptr::read_volatile(&b); + // DISC: call void [[PTR_RELOADED]](i32 42) {{.*}} [ "ptrauth"(i32 0, i64 2712) ] + // NO_DISC: call void [[LOAD]](i32 42) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + (b.0)(42); + } +} + +// Same as above but in a deep chain, expect the chain to disappear. +#[inline(never)] +// CHECK-DAG: test_2_deep_nested +pub fn test_2_deep_nested() { + let a = L5A(L4A(L3A(L2A(L1A(A(f)))))); + // DISC: [[PTR_RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 ptrtoint (ptr ptrauth (ptr @{{.*}}f, i32 0, i64 18983) to i64), i32 0, i64 18983, i32 0, i64 2712) + // DISC: [[INT_TO_PTR:%.*]] = inttoptr i64 [[PTR_RESIGNED]] to ptr + // DISC: store ptr [[INT_TO_PTR]], ptr [[PTR_STORED:%*.]] + // NO_DISC-NOT: call i64 @llvm.ptrauth.resign + // NO_DISC: store ptr ptrauth (ptr @{{.*}}f, i32 0), ptr [[STORE:%.*]], align 8 + let b: L5B = unsafe { mem::transmute(a) }; + + unsafe { + // DISC: [[PTR_RELOADED:%.*]] = load ptr, ptr [[PTR_STORED]] + // NO_DISC: [[LOAD:%.*]] = load ptr, ptr [[STORE]] + ptr::read_volatile(&b); + // DISC: call void [[PTR_RELOADED]](i32 4242) {{.*}} [ "ptrauth"(i32 0, i64 2712) ] + // NO_DISC: call void [[LOAD]](i32 4242) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + (b.0.0.0.0.0.0)(4242); + } +} + +// Different destination discriminator. +#[inline(never)] +// CHECK-DAG: test_3_cross_fnptr_cast +pub fn test_3_cross_fnptr_cast() { + let a: A = A(f); + // DISC: [[PTR_RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 ptrtoint (ptr ptrauth (ptr @{{.*}}f, i32 0, i64 18983) to i64), i32 0, i64 18983, i32 0, i64 55265) + // DISC: [[INT_TO_PTR:%.*]] = inttoptr i64 [[PTR_RESIGNED]] to ptr + // DISC: store ptr [[INT_TO_PTR]], ptr [[PTR_STORED:%*.]] + // NO_DISC-NOT: call i64 @llvm.ptrauth.resign + // NO_DISC: store ptr ptrauth (ptr @{{.*}}f, i32 0), ptr [[STORE:%.*]], align 8 + let c: C = unsafe { mem::transmute(a) }; + + unsafe { + // DISC: [[PTR_RELOADED:%.*]] = load ptr, ptr [[PTR_STORED]] + // NO_DISC: [[LOAD:%.*]] = load ptr, ptr [[STORE]] + ptr::read_volatile(&c); + // DISC: call void [[PTR_RELOADED]](i64 1, i64 2) {{.*}} [ "ptrauth"(i32 0, i64 55265) ] + // NO_DISC: call void [[LOAD]](i64 1, i64 2) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + (c.0)(1, 2); + } +} + +// Negative control. +#[inline(never)] +// CHECK-DAG: test_4_non_fnptr_cast +pub fn test_4_non_fnptr_cast() { + // CHECK-NOT: llvm.ptrauth.resign + // CHECK-NOT: ptrauth + let x = L1NotFn(NotFn(123)); + let y: L1AlsoNotFn = unsafe { mem::transmute(x) }; + + unsafe { + ptr::read_volatile(&y); + } +} + +// Mixed resigned and non-resigned fields. +#[inline(never)] +// CHECK-DAG: test_5_mixed_fnptr_cast_resign +pub fn test_5_mixed_fnptr_cast_resign() { + // Allocate the MixedPair. + // DISC: [[M:%.*]] = alloca [16 x i8] + let mut m = MixedPair { + // Resign fn() -> fn(i32). + // DISC: [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 ptrtoint (ptr ptrauth (ptr @{{.*}}f, i32 0, i64 18983) to i64), i32 0, i64 18983, i32 0, i64 2712) + // DISC: [[F1:%.*]] = inttoptr i64 [[RESIGNED]] to ptr + // Store first struct member. + // DISC: store ptr ptrauth (ptr @{{.*}}f, i32 0, i64 18983), ptr [[M]] + // Compute address of second member and store it + // DISC: [[M1:%.*]] = getelementptr inbounds i8, ptr [[M]], i64 8 + // DISC: store ptr [[F1]], ptr [[M1]], align 8 + // NO_DISC-NOT: call i64 @llvm.ptrauth.resign + // NO_DISC: store ptr ptrauth (ptr @{{.*}}f, i32 0), ptr [[M:%.*]], align 8 + // NO_DISC: [[M_0:%.*]] = getelementptr inbounds i8, ptr [[M]], i64 8 + // NO_DISC: store ptr ptrauth (ptr @{{.*}}f, i32 0), ptr [[M_0]], align 8 + f0: f, + f1: unsafe { mem::transmute::(f) }, + }; + + // Volatile read of the whole struct. + // DISC: [[PAIR:%.*]] = call { ptr, ptr } @{{.*}}read_volatile + // NO_DISC: [[PAIR:%.*]] = call { ptr, ptr } @{{.*}}read_volatile + let tmp = unsafe { ptr::read_volatile(&m) }; + + // Extract both fields and call each of them + // DISC: [[TMP0:%.*]] = extractvalue { ptr, ptr } [[PAIR]], 0 + // DISC: [[TMP1:%.*]] = extractvalue { ptr, ptr } [[PAIR]], 1 + // DISC: call void [[TMP0]]() {{.*}} "ptrauth"(i32 0, i64 18983) + // DISC: call void [[TMP1]](i32 123) {{.*}} "ptrauth"(i32 0, i64 2712) + // NO_DISC: [[TMP0:%.*]] = extractvalue { ptr, ptr } [[PAIR]], 0 + // NO_DISC: [[TMP1:%.*]] = extractvalue { ptr, ptr } [[PAIR]], 1 + // NO_DISC: call void {{.*}}() {{.*}} [ "ptrauth"(i32 0, i64 0) ] + // NO_DISC: call void {{.*}}(i32 123) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + (tmp.f0)(); + (tmp.f1)(123); +} + +// Aggregate reinterpretation (the whole Struct, not just a Member) with mixed members. +#[inline(never)] +// CHECK-DAG: test_6_mixed_layout_cast +pub fn test_6_mixed_layout_cast() { + let x = (A(f), NotFn(999)); + // DISC: [[Y:%.*]] = alloca [16 x i8] + // DISC: store ptr ptrauth (ptr @{{.*}}f, i32 0, i64 18983), ptr [[Y]] + // NO_DISC: [[Y:%.*]] = alloca [16 x i8] + // NO_DISC: store ptr ptrauth (ptr @{{.*}}f, i32 0), ptr [[Y]] + let y: (B, AlsoNotFn) = unsafe { mem::transmute(x) }; + + unsafe { + ptr::read_volatile(&y); + // DISC: [[Y_LOAD:%.*]] = load ptr, ptr [[Y]] + // DISC: call void [[Y_LOAD]](i32 42) {{.*}} [ "ptrauth"(i32 0, i64 2712) ] + // NO_DISC: [[Y_LOAD:%.*]] = load ptr, ptr [[Y]] + // NO_DISC: call void [[Y_LOAD]](i32 42) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + (y.0.0)(42); + } +} + +impl Sync for RootSrc {} +impl Sync for RootDst {} + +#[repr(C)] +struct RootSrc { + f0: extern "C" fn(), + f1: extern "C" fn(i32), + f2: extern "C" fn(i64, i64), + f3: extern "C" fn(i64, i64, f32), +} + +type G0 = extern "C" fn(i32); +type G1 = extern "C" fn(i64, i64); +type G2 = extern "C" fn(i64, i64, f32); +type G3 = extern "C" fn(); + +#[repr(C)] +struct RootDst { + f0: G0, + f1: G1, + f2: G2, + f3: G3, +} + +static T_TREE_SRC: RootSrc = RootSrc { f0: f, f1: g, f2: h, f3: i }; + +#[inline(never)] +// Aggregate stress test, multiple resigning. +// CHECK-DAG: test_7_tree_cast_mixed +pub fn test_7_tree_cast_mixed() { + let src: RootSrc = unsafe { ptr::read_volatile(&T_TREE_SRC) }; + let dst = RootDst { + // field 0: load -> resign(18983 -> 2712) -> store + // DISC: [[SRC0:%.*]] = load ptr, ptr [[SRC:%.*]], + // DISC: [[SRC0I:%.*]] = ptrtoint ptr [[SRC0]] to i64 + // DISC: [[RESIGN0:%.*]] = call i64 @llvm.ptrauth.resign(i64 [[SRC0I]], i32 0, i64 18983, i32 0, i64 2712) + // DISC: [[DST0:%.*]] = inttoptr i64 [[RESIGN0]] to ptr + f0: unsafe { mem::transmute::(src.f0) }, + // field 1: load -> resign(2712 -> 55265) -> store + // DISC: [[SRC1PTR:%.*]] = getelementptr inbounds i8, ptr [[SRC]], i64 8 + // DISC: [[SRC1:%.*]] = load ptr, ptr [[SRC1PTR]] + // DISC: [[SRC1I:%.*]] = ptrtoint ptr [[SRC1]] to i64 + // DISC: [[RESIGN1:%.*]] = call i64 @llvm.ptrauth.resign(i64 [[SRC1I]], i32 0, i64 2712, i32 0, i64 55265) + // DISC: [[DST1:%.*]] = inttoptr i64 [[RESIGN1]] to ptr + f1: unsafe { mem::transmute::(src.f1) }, + // field 2: load -> resign(5526 -> 44485) -> store + // DISC: [[SRC2PTR:%.*]] = getelementptr inbounds i8, ptr [[SRC]], i64 16 + // DISC: [[SRC2:%.*]] = load ptr, ptr [[SRC2PTR]] + // DISC: [[SRC2I:%.*]] = ptrtoint ptr [[SRC2]] to i64 + // DISC: [[RESIGN2:%.*]] = call i64 @llvm.ptrauth.resign(i64 [[SRC2I]], i32 0, i64 55265, i32 0, i64 44485) + // DISC: [[DST2:%.*]] = inttoptr i64 [[RESIGN2]] to ptr + f2: unsafe { mem::transmute::(src.f2) }, + // field 3: load -> resign(44485 -> 18983) -> store + // DISC: [[SRC3PTR:%.*]] = getelementptr inbounds i8, ptr [[SRC]], i64 24 + // DISC: [[SRC3:%.*]] = load ptr, ptr [[SRC3PTR]] + // DISC: [[SRC3I:%.*]] = ptrtoint ptr [[SRC3]] to i64 + // DISC: [[RESIGN3:%.*]] = call i64 @llvm.ptrauth.resign(i64 [[SRC3I]], i32 0, i64 44485, i32 0, i64 18983) + // DISC: [[DST3:%.*]] = inttoptr i64 [[RESIGN3]] to ptr + f3: unsafe { mem::transmute::(src.f3) }, + // DISC: store ptr [[DST0]], ptr [[DST:%.*]], + // DISC: [[DST1PTR:%.*]] = getelementptr inbounds i8, ptr %dst, i64 8 + // DISC: store ptr [[DST1]], ptr [[DST1PTR]] + // DISC: [[DST2PTR:%.*]] = getelementptr inbounds i8, ptr %dst, i64 16 + // DISC: store ptr [[DST2]], ptr [[DST2PTR]] + // DISC: [[DST3PTR:%.*]] = getelementptr inbounds i8, ptr %dst, i64 24 + // DISC: store ptr [[DST3]], ptr [[DST3PTR]] + }; + + unsafe { + ptr::read_volatile(&dst); + } + // NO_DISC-NOT: call i64 @llvm.ptrauth.resign + + // Field loads and authed calls: + // DISC: [[CALL0:%.*]] = load ptr, ptr [[DST]] + // DISC: call void [[CALL0]](i32 1) {{.*}} [ "ptrauth"(i32 0, i64 2712) ] + // NO_DISC: call void {{.*}}(i32 1) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + (dst.f0)(1); + // DISC: [[CALL1PTR:%.*]] = getelementptr inbounds i8, ptr [[DST]], i64 8 + // DISC: [[CALL1:%.*]] = load ptr, ptr [[CALL1PTR]], + // DISC: call void [[CALL1]](i64 2, i64 3) {{.*}} [ "ptrauth"(i32 0, i64 55265) ] + // NO_DISC: call void {{.*}}(i64 2, i64 3) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + (dst.f1)(2, 3); + // DISC: [[CALL2PTR:%.*]] = getelementptr inbounds i8, ptr [[DST]], i64 16 + // DISC: [[CALL2:%.*]] = load ptr, ptr [[CALL2PTR]], + // DISC: call void [[CALL2]](i64 4, i64 5, float 6.000000e+00) {{.*}} [ "ptrauth"(i32 0, i64 44485) ] + // NO_DISC: call void {{.*}}(i64 4, i64 5, float 6.000000e+00) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + (dst.f2)(4, 5, 6.0); + // DISC: [[CALL3PTR:%.*]] = getelementptr inbounds i8, ptr [[DST]], i64 24 + // DISC: [[CALL3:%.*]] = load ptr, ptr [[CALL3PTR]], + // DISC: call void [[CALL3]]() {{.*}} [ "ptrauth"(i32 0, i64 18983) ] + // NO_DISC: call void {{.*}}() {{.*}} [ "ptrauth"(i32 0, i64 0) ] + (dst.f3)(); +} + +#[repr(transparent)] +struct Wrapper(extern "C" fn(i32)); + +impl Sync for Wrapper {} + +static T_WRAPPED_FN_PTR: Wrapper = Wrapper(g); + +// C equivalent. +// #include +// +// typedef void (*fn0)(void); +// typedef void (*fn1)(int); +// typedef void (*fn2)(long long, long long); +// typedef void (*fn3)(long long, long long, float); +// +// typedef void (*g0)(int); +// typedef void (*g1)(long long, long long); +// typedef void (*g2)(long long, long long, float); +// typedef void (*g3)(void); +// +// void f(void); +// void g(int); +// void h(long long, long long); +// void i(long long, long long, float); +// +// typedef struct { +// fn0 f; +// } A; +// typedef struct { +// fn1 f; +// } B; +// typedef struct { +// fn2 f; +// } C; +// typedef struct { +// A a; +// } L1A; +// typedef struct { +// B b; +// } L1B; +// typedef struct { +// L1A a; +// } L2A; +// typedef struct { +// L1B b; +// } L2B; +// typedef struct { +// L2A a; +// } L3A; +// typedef struct { +// L2B b; +// } L3B; +// typedef struct { +// L3A a; +// } L4A; +// typedef struct { +// L3B b; +// } L4B; +// typedef struct { +// L4A a; +// } L5A; +// typedef struct { +// L4B b; +// } L5B; +// typedef struct { +// fn0 f0; +// fn1 f1; +// } MixedPair; +// typedef struct { +// uint64_t x; +// } NotFn; +// typedef struct { +// uint64_t x; +// } AlsoNotFn; +// typedef struct { +// NotFn n; +// } L1NotFn; +// typedef struct { +// AlsoNotFn a; +// } L1AlsoNotFn; +// +// __attribute__((noinline)) void test_1_struct_resign(void) { +// A a; +// a.f = f; +// +// B b; +// b.f = (fn1)a.f; +// +// volatile B tmp = b; +// b.f(42); +// } +// +// __attribute__((noinline)) void test_2_deep_nested(void) { +// L5A a; +// a.a.a.a.a.a.f = f; +// +// L5B b; +// b.b.b.b.b.b.f = (fn1)a.a.a.a.a.a.f; +// +// volatile L5B tmp = b; +// b.b.b.b.b.b.f(42); +// } +// +// __attribute__((noinline)) void test_3_cross_fnptr_cast(void) { +// A a; +// a.f = f; +// +// C c; +// c.f = (fn2)a.f; +// +// volatile C tmp = c; +// c.f(1, 2); +// } +// +// __attribute__((noinline)) void test_4_non_fnptr_cast(void) { +// L1NotFn x; +// x.n.x = 123; +// +// L1AlsoNotFn y; +// y.a = *(AlsoNotFn *)&x; +// +// volatile L1AlsoNotFn tmp = y; +// } +// +// __attribute__((noinline)) void test_5_mixed_fnptr_cast_resign(void) { +// MixedPair m; +// m.f0 = f; +// m.f1 = (fn1)f; +// +// volatile MixedPair tmp = m; +// m.f0(); +// m.f1(123); +// } +// +// typedef struct { +// A a; +// NotFn n; +// } TupleA; +// +// typedef struct { +// B b; +// AlsoNotFn n; +// } TupleB; +// +// __attribute__((noinline)) void test_6_mixed_layout_cast(void) { +// TupleA x; +// x.a.f = f; +// x.n.x = 999; +// +// TupleB y = *(TupleB *)&x; +// +// volatile TupleB tmp = y; +// +// y.b.f(42); +// } +// +// typedef struct { +// fn0 f0; +// fn1 f1; +// fn2 f2; +// fn3 f3; +// } RootSrc; +// +// typedef struct { +// g0 f0; +// g1 f1; +// g2 f2; +// g3 f3; +// } RootDst; +// +// static const RootSrc T_TREE_SRC = { +// .f0 = f, +// .f1 = g, +// .f2 = h, +// .f3 = i, +// }; +// +// __attribute__((noinline)) void test_7_tree_cast_mixed(void) { +// volatile const RootSrc *vp = &T_TREE_SRC; +// +// RootSrc src = *vp; +// +// RootDst dst = { +// .f0 = (g0)src.f0, +// .f1 = (g1)src.f1, +// .f2 = (g2)src.f2, +// .f3 = (g3)src.f3, +// }; +// +// volatile RootDst tmp = dst; +// +// dst.f0(1); +// dst.f1(2, 3); +// dst.f2(4, 5, 6.0f); +// dst.f3(); +// } diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-struct-name.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-struct-name.rs new file mode 100644 index 0000000000000..301d04558fe93 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-struct-name.rs @@ -0,0 +1,100 @@ +// ignore-tidy-file-linelength +//@ add-minicore +//@ only-pauthtest +// Run it at O0, so that the compiler doesn't optimise the calls away. + +//@ revisions: DISC NO_DISC +//@ [DISC] needs-llvm-components: aarch64 +//@ [DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest --crate-type=lib -Zpointer-authentication=+function-pointer-type-discrimination -C opt-level=0 + +// Test generation of function-pointer type discriminators. The discriminator values were obtained +// from Clang by compiling equivalent C code (included). Both compilers must generate identical +// values. +// +// Make sure that rust only uses the final part of struct's name (`Foo` or `Bar`), so that the +// discriminators are `F3FooE` and `F3BarE`, not using def path for the base of encoding. + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] +extern crate minicore; +use minicore::hint::black_box; + +#[repr(C)] +pub struct Foo { + x: i32, +} + +#[repr(C)] +pub struct Bar { + x: i32, +} + +extern "C" fn takes_foo(_: Foo) {} +extern "C" fn takes_bar(_: Bar) {} + +#[used] +// DISC-DAG: @{{.*}}FOO_FNPTR = constant ptr ptrauth (ptr @{{.*}}takes_foo, i32 0, i64 58649) +// Without type discriminators all the functions are the same, so compiler is able to use both +// takes_foo/take_bar. +// NO_DISC-DAG: @{{.*}}FOO_FNPTR = constant ptr ptrauth (ptr @{{.*}}takes_{{foo|bar}}, i32 0) +static FOO_FNPTR: extern "C" fn(Foo) = takes_foo; + +#[used] +// DISC-DAG: @{{.*}}BAR_FNPTR = constant ptr ptrauth (ptr @{{.*}}takes_bar, i32 0, i64 41614) +// NO_DISC-DAG: @{{.*}}BAR_FNPTR = constant ptr ptrauth (ptr @{{.*}}takes_{{foo|bar}}, i32 0) +static BAR_FNPTR: extern "C" fn(Bar) = takes_bar; + +// While not possible to express in C, we could force it through C++ path with something along the +// lines of: +// ```c++ +// namespace a { +// struct SameName { +// int x; +// }; +// +// void takes(SameName) {} +// } +// +// namespace b { +// struct SameName { +// int x; +// }; +// +// void takes(SameName) {} +// } +// +// void (*a_fnptr)(a::SameName) = a::takes; +// void (*b_fnptr)(b::SameName) = b::takes; +// ``` +// Make sure that Rust uses `Fv8SameNameE` for both `A_FNPTR` and `B_FNPTR`, not +// `Fv11a::SameNameE`, or `Fv11b::SameNameE`. + +mod a { + #[repr(C)] + pub struct SameName { + pub x: i32, + } + + pub extern "C" fn takes(_: SameName) {} +} + +mod b { + #[repr(C)] + pub struct SameName { + pub x: i32, + } + + pub extern "C" fn takes(_: SameName) {} +} + +#[used] +// DISC-DAG: @{{.*}}A_FNPTR = constant ptr ptrauth (ptr @{{.*}}takes, i32 0, i64 57535) +// NO_DISC-DAG: @{{.*}}A_FNPTR = constant ptr ptrauth (ptr @{{.*}}takes{{.*}}, i32 0) +static A_FNPTR: extern "C" fn(a::SameName) = a::takes; + +#[used] +// DISC-DAG: @{{.*}}B_FNPTR = constant ptr ptrauth (ptr @{{.*}}takes, i32 0, i64 57535) +// NO_DISC-DAG: @{{.*}}B_FNPTR = constant ptr ptrauth (ptr @{{.*}}takes{{.*}}, i32 0) +static B_FNPTR: extern "C" fn(b::SameName) = b::takes; diff --git a/tests/codegen-llvm/pauth/pauth-init-fini.rs b/tests/codegen-llvm/pauth/pauth-init-fini.rs index b54006e56f1f4..948838145b2f2 100644 --- a/tests/codegen-llvm/pauth/pauth-init-fini.rs +++ b/tests/codegen-llvm/pauth/pauth-init-fini.rs @@ -1,7 +1,7 @@ // ignore-tidy-file-linelength //@ add-minicore //@ only-pauthtest -//@ revisions: O0_PAUTH O3_PAUTH O0_PAUTH-ADDR-DISC O3_PAUTH-ADDR-DISC O0_PAUTH-NO-INIT-FINI O3_PAUTH-NO-INIT-FINI +//@ revisions: O0_PAUTH O3_PAUTH O0_PAUTH-ADDR-DISC O3_PAUTH-ADDR-DISC O0_PAUTH-NO-INIT-FINI O3_PAUTH-NO-INIT-FINI O0_PAUTH-INIT-FINI-FN-TY-DISC O3_PAUTH-INIT-FINI-FN-TY-DISC //@ [O0_PAUTH] needs-llvm-components: aarch64 //@ [O0_PAUTH] compile-flags: --target=aarch64-unknown-linux-pauthtest -C opt-level=0 @@ -15,9 +15,13 @@ //@ [O3_PAUTH-ADDR-DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest -C opt-level=3 -Zpointer-authentication=+init-fini-address-discrimination //@ [O3_PAUTH-NO-INIT-FINI] needs-llvm-components: aarch64 //@ [O3_PAUTH-NO-INIT-FINI] compile-flags: --target=aarch64-unknown-linux-pauthtest -C opt-level=0 -Zpointer-authentication=-init-fini - +//@ [O0_PAUTH-INIT-FINI-FN-TY-DISC] needs-llvm-components: aarch64 +//@ [O0_PAUTH-INIT-FINI-FN-TY-DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest -C opt-level=0 -Zpointer-authentication=+init-fini,+function-pointer-type-discrimination,-init-fini-address-discrimination +//@ [O3_PAUTH-INIT-FINI-FN-TY-DISC] needs-llvm-components: aarch64 +//@ [O3_PAUTH-INIT-FINI-FN-TY-DISC] compile-flags: --target=aarch64-unknown-linux-pauthtest -C opt-level=0 -Zpointer-authentication=+init-fini,+function-pointer-type-discrimination,-init-fini-address-discrimination // Make sure that init/fini metadata uses correct discriminator: 0xd9d4/55764 - ptrauth_string_discriminator("init_fini"). // And that address discriminator can be enabled. +// Function pointer type discrimination does not apply to init/fini entries. #![feature(no_core, lang_items)] #![no_std] @@ -33,6 +37,8 @@ use minicore::*; // O3_PAUTH-ADDR-DISC: @{{[0-9A-Za-z_]+}}GLOBAL_INIT = constant ptr ptrauth (ptr @{{[0-9A-Za-z_]+}}init_fn, i32 0, i64 55764, ptr @_RNvCsf7kshQi9mOB_15pauth_init_fini7init_fn), section ".init_array.90" // O0_PAUTH-NO-INIT-FINI-NOT: @{{[0-9A-Za-z_]+}}GLOBAL_INIT = constant ptr ptrauth // O0_PAUTH-NO-INIT-FINI-ADDR-DISC: @{{[0-9A-Za-z_]+}}GLOBAL_INIT = constant ptr ptrauth +// O0_PAUTH-INIT-FINI-FN-TY-DISC: @{{[0-9A-Za-z_]+}}GLOBAL_INIT = constant ptr ptrauth (ptr @{{[0-9A-Za-z_]+}}init_fn, i32 0, i64 55764, ptr inttoptr (i64 1 to ptr)), section ".init_array.90" +// O3_PAUTH-INIT-FINI-FN-TY-DISC: @{{[0-9A-Za-z_]+}}GLOBAL_INIT = constant ptr ptrauth (ptr @{{[0-9A-Za-z_]+}}init_fn, i32 0, i64 55764, ptr inttoptr (i64 1 to ptr)), section ".init_array.90" #[used] #[link_section = ".init_array.90"] static GLOBAL_INIT: extern "C" fn() = init_fn; @@ -43,6 +49,8 @@ static GLOBAL_INIT: extern "C" fn() = init_fn; // O3_PAUTH-ADDR-DISC: @{{[0-9A-Za-z_]+}}GLOBAL_FINI = constant ptr ptrauth (ptr @{{[0-9A-Za-z_]+}}fini_fn, i32 0, i64 55764, ptr @_RNvCsf7kshQi9mOB_15pauth_init_fini7fini_fn), section ".fini_array.90" // O0_PAUTH-NO-INIT-FINI-NOT: @{{[0-9A-Za-z_]+}}GLOBAL_FINI = constant ptr ptrauth // O3_PAUTH-NO-INIT-FINI-NOT: @{{[0-9A-Za-z_]+}}GLOBAL_FINI = constant ptr ptrauth +// O0_PAUTH-INIT-FINI-FN-TY-DISC: @{{[0-9A-Za-z_]+}}GLOBAL_FINI = constant ptr ptrauth (ptr @{{[0-9A-Za-z_]+}}fini_fn, i32 0, i64 55764, ptr inttoptr (i64 1 to ptr)), section ".fini_array.90" +// O3_PAUTH-INIT-FINI-FN-TY-DISC: @{{[0-9A-Za-z_]+}}GLOBAL_FINI = constant ptr ptrauth (ptr @{{[0-9A-Za-z_]+}}fini_fn, i32 0, i64 55764, ptr inttoptr (i64 1 to ptr)), section ".fini_array.90" #[used] #[link_section = ".fini_array.90"] static GLOBAL_FINI: extern "C" fn(i32) = fini_fn; diff --git a/tests/run-make/pauth-drop-terminator/before_instcombine.check b/tests/run-make/pauth-drop-terminator/before_instcombine.check new file mode 100644 index 0000000000000..50b46ecf4ae2e --- /dev/null +++ b/tests/run-make/pauth-drop-terminator/before_instcombine.check @@ -0,0 +1,4 @@ +// CHECK-LABEL: ; *** IR Dump Before InstCombinePass + +// CHECK-LABEL: define void @{{.*}}Drop4drop +// CHECK: call void ptrauth (ptr @c_cleanup, i32 0)(i32 noundef %{{.*}}) #[[#]] [ "ptrauth"(i32 0, i64 0) ] diff --git a/tests/run-make/pauth-drop-terminator/before_instcombine_ty_disc.check b/tests/run-make/pauth-drop-terminator/before_instcombine_ty_disc.check new file mode 100644 index 0000000000000..57f2d8d91e28f --- /dev/null +++ b/tests/run-make/pauth-drop-terminator/before_instcombine_ty_disc.check @@ -0,0 +1,4 @@ +// CHECK-LABEL: ; *** IR Dump Before InstCombinePass + +// CHECK-LABEL: define void @{{.*}}Drop4drop +// CHECK: call void ptrauth (ptr @c_cleanup, i32 0, i64 2712)(i32 noundef %{{.*}}) #[[#]] [ "ptrauth"(i32 0, i64 2712) ] diff --git a/tests/run-make/pauth-drop-terminator/full_ir.check b/tests/run-make/pauth-drop-terminator/full_ir.check new file mode 100644 index 0000000000000..ad4c80c0675c4 --- /dev/null +++ b/tests/run-make/pauth-drop-terminator/full_ir.check @@ -0,0 +1,3 @@ +// CHECK-LABEL: define void @{{.*}}Drop4drop +// CHECK-NOT: call void ptrauth (ptr @c_cleanup +// CHECK: tail call void @c_cleanup diff --git a/tests/run-make/pauth-drop-terminator/main.rs b/tests/run-make/pauth-drop-terminator/main.rs new file mode 100644 index 0000000000000..45646cfd6da60 --- /dev/null +++ b/tests/run-make/pauth-drop-terminator/main.rs @@ -0,0 +1,22 @@ +extern "C" { + fn c_cleanup(x: i32); +} + +struct Bomb(i32); + +impl Drop for Bomb { + fn drop(&mut self) { + unsafe { + c_cleanup(self.0); + } + } +} + +pub fn may_unwind(x: i32) { + let b = Bomb(x); + + match b.0 { + 0 => return, + _ => {} + } +} diff --git a/tests/run-make/pauth-drop-terminator/rmake.rs b/tests/run-make/pauth-drop-terminator/rmake.rs new file mode 100644 index 0000000000000..bb69570ae2adf --- /dev/null +++ b/tests/run-make/pauth-drop-terminator/rmake.rs @@ -0,0 +1,61 @@ +// Make sure that for `aarch64-unknown-linux-pauthtest` compiler correctly signs drop terminators. +// Please note that the generated pattern: +// ```llvm +// tail call void ptrauth (ptr @c_cleanup, i32 0)(ptr @c_cleanup, i32 0, i64 2712) #2 [ "ptrauth"(i32 0, i64 2712) ] +// ``` +// is optimised out by LLVM's instcombine, hence dump the IR before that pass and inspect it. + +//@ only-pauthtest +// ignore-tidy-file-linelength + +use run_make_support::path_helpers::source_root; +use run_make_support::{llvm_filecheck, rfs, rustc}; + +fn main() { + let sibling = source_root().join("tests/run-make/pauth-drop-terminator"); + + let output = rustc() + .input("main.rs") + .target("aarch64-unknown-linux-pauthtest") + .opt_level("3") + .arg("--crate-type=lib") + .arg("--emit=llvm-ir") + .arg("-C") + .arg("llvm-args=-print-before=instcombine") + .run(); + + let stderr = output.stderr_utf8(); + + // -print-before outputs to stderr, so copy it over to a file, that can later be used by + // filecheck. + rfs::write("before_instcombine.ll", stderr); + + llvm_filecheck() + .patterns(sibling.join("before_instcombine.check")) + .stdin_buf(rfs::read("before_instcombine.ll")) + .run(); + + llvm_filecheck().patterns(sibling.join("full_ir.check")).stdin_buf(rfs::read("main.ll")).run(); + + // Compile again now using function pointer type discrimination. + let output = rustc() + .input("main.rs") + .target("aarch64-unknown-linux-pauthtest") + .opt_level("3") + .arg("--crate-type=lib") + .arg("--emit=llvm-ir") + .arg("-Zpointer-authentication=+function-pointer-type-discrimination") + .arg("-Cunsafe-allow-abi-mismatch=pointer-authentication") + .arg("-C") + .arg("llvm-args=-print-before=instcombine") + .run(); + + let stderr = output.stderr_utf8(); + + rfs::write("before_instcombine_ty_disc.ll", stderr); + + llvm_filecheck() + .patterns(sibling.join("before_instcombine_ty_disc.check")) + .stdin_buf(rfs::read("before_instcombine_ty_disc.ll")) + .run(); +} diff --git a/tests/ui/abi/c-zst.aarch64-darwin.stderr b/tests/ui/abi/c-zst.aarch64-darwin.stderr index 6d2ac90c0c975..7981923585305 100644 --- a/tests/ui/abi/c-zst.aarch64-darwin.stderr +++ b/tests/ui/abi/c-zst.aarch64-darwin.stderr @@ -59,6 +59,7 @@ error: fn_abi_of(pass_zst) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/c-zst.rs:65:1 | diff --git a/tests/ui/abi/c-zst.powerpc-linux.stderr b/tests/ui/abi/c-zst.powerpc-linux.stderr index edea2d5772280..03f3ffd295be0 100644 --- a/tests/ui/abi/c-zst.powerpc-linux.stderr +++ b/tests/ui/abi/c-zst.powerpc-linux.stderr @@ -70,6 +70,7 @@ error: fn_abi_of(pass_zst) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/c-zst.rs:65:1 | diff --git a/tests/ui/abi/c-zst.s390x-linux.stderr b/tests/ui/abi/c-zst.s390x-linux.stderr index edea2d5772280..03f3ffd295be0 100644 --- a/tests/ui/abi/c-zst.s390x-linux.stderr +++ b/tests/ui/abi/c-zst.s390x-linux.stderr @@ -70,6 +70,7 @@ error: fn_abi_of(pass_zst) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/c-zst.rs:65:1 | diff --git a/tests/ui/abi/c-zst.sparc64-linux.stderr b/tests/ui/abi/c-zst.sparc64-linux.stderr index edea2d5772280..03f3ffd295be0 100644 --- a/tests/ui/abi/c-zst.sparc64-linux.stderr +++ b/tests/ui/abi/c-zst.sparc64-linux.stderr @@ -70,6 +70,7 @@ error: fn_abi_of(pass_zst) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/c-zst.rs:65:1 | diff --git a/tests/ui/abi/c-zst.x86_64-linux.stderr b/tests/ui/abi/c-zst.x86_64-linux.stderr index 6d2ac90c0c975..7981923585305 100644 --- a/tests/ui/abi/c-zst.x86_64-linux.stderr +++ b/tests/ui/abi/c-zst.x86_64-linux.stderr @@ -59,6 +59,7 @@ error: fn_abi_of(pass_zst) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/c-zst.rs:65:1 | diff --git a/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr b/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr index edea2d5772280..03f3ffd295be0 100644 --- a/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr +++ b/tests/ui/abi/c-zst.x86_64-pc-windows-gnu.stderr @@ -70,6 +70,7 @@ error: fn_abi_of(pass_zst) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/c-zst.rs:65:1 | diff --git a/tests/ui/abi/debug.generic.stderr b/tests/ui/abi/debug.generic.stderr index 125150f2c2e3f..40ef1a7450def 100644 --- a/tests/ui/abi/debug.generic.stderr +++ b/tests/ui/abi/debug.generic.stderr @@ -121,6 +121,7 @@ error: fn_abi_of(test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:31:1 | @@ -217,6 +218,7 @@ error: fn_abi_of(TestFnPtr) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:37:1 | @@ -295,6 +297,7 @@ error: fn_abi_of(test_generic) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:40:1 | @@ -379,6 +382,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } right ABI = FnAbi { args: [ @@ -451,6 +455,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:59:1 | @@ -530,6 +535,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } right ABI = FnAbi { args: [ @@ -603,6 +609,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:62:1 | @@ -680,6 +687,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } right ABI = FnAbi { args: [ @@ -752,6 +760,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:65:1 | @@ -830,6 +839,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } right ABI = FnAbi { args: [ @@ -902,6 +912,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:69:1 | @@ -1007,6 +1018,7 @@ error: fn_abi_of(assoc_test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:52:5 | diff --git a/tests/ui/abi/debug.loongarch64.stderr b/tests/ui/abi/debug.loongarch64.stderr index 5f05174c12760..b50e92966d95a 100644 --- a/tests/ui/abi/debug.loongarch64.stderr +++ b/tests/ui/abi/debug.loongarch64.stderr @@ -121,6 +121,7 @@ error: fn_abi_of(test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:31:1 | @@ -217,6 +218,7 @@ error: fn_abi_of(TestFnPtr) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:37:1 | @@ -295,6 +297,7 @@ error: fn_abi_of(test_generic) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:40:1 | @@ -379,6 +382,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } right ABI = FnAbi { args: [ @@ -451,6 +455,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:59:1 | @@ -530,6 +535,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } right ABI = FnAbi { args: [ @@ -603,6 +609,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:62:1 | @@ -680,6 +687,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } right ABI = FnAbi { args: [ @@ -752,6 +760,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:65:1 | @@ -830,6 +839,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } right ABI = FnAbi { args: [ @@ -902,6 +912,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:69:1 | @@ -1007,6 +1018,7 @@ error: fn_abi_of(assoc_test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:52:5 | diff --git a/tests/ui/abi/debug.riscv64.stderr b/tests/ui/abi/debug.riscv64.stderr index 5f05174c12760..b50e92966d95a 100644 --- a/tests/ui/abi/debug.riscv64.stderr +++ b/tests/ui/abi/debug.riscv64.stderr @@ -121,6 +121,7 @@ error: fn_abi_of(test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:31:1 | @@ -217,6 +218,7 @@ error: fn_abi_of(TestFnPtr) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:37:1 | @@ -295,6 +297,7 @@ error: fn_abi_of(test_generic) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:40:1 | @@ -379,6 +382,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } right ABI = FnAbi { args: [ @@ -451,6 +455,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:59:1 | @@ -530,6 +535,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } right ABI = FnAbi { args: [ @@ -603,6 +609,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:62:1 | @@ -680,6 +687,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } right ABI = FnAbi { args: [ @@ -752,6 +760,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:65:1 | @@ -830,6 +839,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } right ABI = FnAbi { args: [ @@ -902,6 +912,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:69:1 | @@ -1007,6 +1018,7 @@ error: fn_abi_of(assoc_test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_type_discriminator: 0, } --> $DIR/debug.rs:52:5 | diff --git a/tests/ui/abi/numbers-arithmetic/x86-64-sysv64-arg-ext.apple.stderr b/tests/ui/abi/numbers-arithmetic/x86-64-sysv64-arg-ext.apple.stderr index 02015d2a2e51f..f875a14411fd1 100644 --- a/tests/ui/abi/numbers-arithmetic/x86-64-sysv64-arg-ext.apple.stderr +++ b/tests/ui/abi/numbers-arithmetic/x86-64-sysv64-arg-ext.apple.stderr @@ -81,6 +81,7 @@ error: fn_abi_of(i8) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:13:1 | @@ -170,6 +171,7 @@ error: fn_abi_of(u8) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:19:1 | @@ -259,6 +261,7 @@ error: fn_abi_of(i16) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:25:1 | @@ -348,6 +351,7 @@ error: fn_abi_of(u16) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:31:1 | @@ -437,6 +441,7 @@ error: fn_abi_of(i32) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:37:1 | @@ -526,6 +531,7 @@ error: fn_abi_of(u32) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:43:1 | diff --git a/tests/ui/abi/numbers-arithmetic/x86-64-sysv64-arg-ext.other.stderr b/tests/ui/abi/numbers-arithmetic/x86-64-sysv64-arg-ext.other.stderr index 9bb2ab45d9841..7ea941662e9ca 100644 --- a/tests/ui/abi/numbers-arithmetic/x86-64-sysv64-arg-ext.other.stderr +++ b/tests/ui/abi/numbers-arithmetic/x86-64-sysv64-arg-ext.other.stderr @@ -81,6 +81,7 @@ error: fn_abi_of(i8) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:13:1 | @@ -170,6 +171,7 @@ error: fn_abi_of(u8) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:19:1 | @@ -259,6 +261,7 @@ error: fn_abi_of(i16) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:25:1 | @@ -348,6 +351,7 @@ error: fn_abi_of(u16) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:31:1 | @@ -437,6 +441,7 @@ error: fn_abi_of(i32) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:37:1 | @@ -526,6 +531,7 @@ error: fn_abi_of(u32) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:43:1 | diff --git a/tests/ui/abi/pass-indirectly-attr.stderr b/tests/ui/abi/pass-indirectly-attr.stderr index 320840c8149f5..e02eed82ad85e 100644 --- a/tests/ui/abi/pass-indirectly-attr.stderr +++ b/tests/ui/abi/pass-indirectly-attr.stderr @@ -83,6 +83,7 @@ error: fn_abi_of(extern_c) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/pass-indirectly-attr.rs:20:1 | @@ -174,6 +175,7 @@ error: fn_abi_of(extern_rust) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/pass-indirectly-attr.rs:27:1 | diff --git a/tests/ui/abi/sysv64-zst.stderr b/tests/ui/abi/sysv64-zst.stderr index 82d3793c35328..7c375cb593a6c 100644 --- a/tests/ui/abi/sysv64-zst.stderr +++ b/tests/ui/abi/sysv64-zst.stderr @@ -61,6 +61,7 @@ error: fn_abi_of(pass_zst) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/sysv64-zst.rs:8:1 | diff --git a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr index 45edd7bc0e0ee..b470e4935f5f2 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr @@ -70,6 +70,7 @@ error: fn_abi_of(take_va_list) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/pass-by-value-abi.rs:27:1 | diff --git a/tests/ui/c-variadic/pass-by-value-abi.win.stderr b/tests/ui/c-variadic/pass-by-value-abi.win.stderr index b8e3f699b30e8..8b2cb0773ae13 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.win.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.win.stderr @@ -73,6 +73,7 @@ error: fn_abi_of(take_va_list) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/pass-by-value-abi.rs:27:1 | diff --git a/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr index 1e203b93e66b3..e6daafcbb2fd6 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.x86_64.stderr @@ -70,6 +70,7 @@ error: fn_abi_of(take_va_list) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/pass-by-value-abi.rs:27:1 | @@ -150,6 +151,7 @@ error: fn_abi_of(take_va_list_sysv64) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/pass-by-value-abi.rs:37:1 | @@ -230,6 +232,7 @@ error: fn_abi_of(take_va_list_win64) = FnAbi { Win64, ), can_unwind: false, + ptrauth_type_discriminator: 0, } --> $DIR/pass-by-value-abi.rs:44:1 | diff --git a/tests/ui/pointer_authentication/type_discrimination_not_supported_pointer_authentication.rs b/tests/ui/pointer_authentication/type_discrimination_not_supported_pointer_authentication.rs deleted file mode 100644 index 6838e749fd333..0000000000000 --- a/tests/ui/pointer_authentication/type_discrimination_not_supported_pointer_authentication.rs +++ /dev/null @@ -1,12 +0,0 @@ -//@ ignore-backends: gcc -//@ check-fail -//@ needs-llvm-components: aarch64 - -//@ compile-flags: -Zpointer-authentication=+function-pointer-type-discrimination --crate-type=lib --target aarch64-unknown-linux-pauthtest - -#![feature(no_core)] -#![no_std] -#![no_main] -#![no_core] - -//~? ERROR function pointer type discrimination is not supported diff --git a/tests/ui/pointer_authentication/type_discrimination_not_supported_pointer_authentication.stderr b/tests/ui/pointer_authentication/type_discrimination_not_supported_pointer_authentication.stderr deleted file mode 100644 index c040b0cb61f66..0000000000000 --- a/tests/ui/pointer_authentication/type_discrimination_not_supported_pointer_authentication.stderr +++ /dev/null @@ -1,4 +0,0 @@ -error: function pointer type discrimination is not supported - -error: aborting due to 1 previous error -