From 3519ad59ae70e657b7f0175254705e282f8b2df4 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Fri, 10 Jul 2026 12:01:47 +0000 Subject: [PATCH 01/12] [PAC] Include discriminator in `FnAbi`, add `llvm.ptrauth.resign` This patch introduces the following: * Extends `FnAbi` (`callconv`) with a `ptrauth_type_discriminator` field. This field is only used when emitting pointer authentication call bundles. It is stored in `FnAbi` because the call site is not guaranteed to have access to an `Instance`, so the discriminator cannot always be computed on demand. * Adds support for `llvm.ptrauth.resign`. This intrinsic will be used when support for semantic transmute is added. * Performs a minor API redesign as groundwork for allowing call sites to modify schemas in place. --- compiler/rustc_codegen_gcc/src/builder.rs | 11 +++++++ compiler/rustc_codegen_gcc/src/common.rs | 2 +- compiler/rustc_codegen_gcc/src/context.rs | 2 +- compiler/rustc_codegen_gcc/src/int.rs | 1 + compiler/rustc_codegen_llvm/src/builder.rs | 32 +++++++++++++++++-- compiler/rustc_codegen_llvm/src/common.rs | 23 +++++++------ compiler/rustc_codegen_llvm/src/context.rs | 4 +-- .../rustc_codegen_ssa/src/traits/builder.rs | 9 ++++++ .../rustc_codegen_ssa/src/traits/consts.rs | 2 +- compiler/rustc_codegen_ssa/src/traits/misc.rs | 2 +- compiler/rustc_session/src/session.rs | 25 ++++++++++++--- compiler/rustc_target/src/callconv/mod.rs | 8 +++-- compiler/rustc_ty_utils/src/abi.rs | 6 ++++ tests/ui/abi/c-zst.aarch64-darwin.stderr | 1 + tests/ui/abi/c-zst.powerpc-linux.stderr | 1 + tests/ui/abi/c-zst.s390x-linux.stderr | 1 + tests/ui/abi/c-zst.sparc64-linux.stderr | 1 + tests/ui/abi/c-zst.x86_64-linux.stderr | 1 + .../ui/abi/c-zst.x86_64-pc-windows-gnu.stderr | 1 + tests/ui/abi/debug.generic.stderr | 12 +++++++ tests/ui/abi/debug.loongarch64.stderr | 12 +++++++ tests/ui/abi/debug.riscv64.stderr | 12 +++++++ .../x86-64-sysv64-arg-ext.apple.stderr | 6 ++++ .../x86-64-sysv64-arg-ext.other.stderr | 6 ++++ tests/ui/abi/pass-indirectly-attr.stderr | 2 ++ tests/ui/abi/sysv64-zst.stderr | 1 + .../pass-by-value-abi.aarch64.stderr | 1 + .../c-variadic/pass-by-value-abi.win.stderr | 1 + .../pass-by-value-abi.x86_64.stderr | 3 ++ 29 files changed, 165 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs index 88049d67964b1..d42438ba92f59 100644 --- a/compiler/rustc_codegen_gcc/src/builder.rs +++ b/compiler/rustc_codegen_gcc/src/builder.rs @@ -1850,6 +1850,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 6bd186f1121fc..712a1c14ef8e6 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -323,7 +323,7 @@ impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> { cv: Scalar, layout: abi::Scalar, ty: Type<'gcc>, - _schema: Option<&PointerAuthSchema>, + _ptrauth_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 19fbe37c27b9e..8c1fc18ee7a78 100644 --- a/compiler/rustc_codegen_gcc/src/context.rs +++ b/compiler/rustc_codegen_gcc/src/context.rs @@ -405,7 +405,7 @@ impl<'gcc, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'gcc, 'tcx> { fn get_fn_addr( &self, instance: Instance<'tcx>, - _pointer_auth_schema: Option<&PointerAuthSchema>, + _ptrauth_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..10cbe1ccb8059 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_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 87c941cdeb23a..63e6faf3ced93 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -1553,6 +1553,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, '_> { @@ -2171,8 +2195,12 @@ 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; + // If sess().pointer_authentication_fn_ptr_type_discrimination() is true, this contains + // the function pointer type discriminator; otherwise, it is 0. + let discriminator = fn_abi?.ptrauth_discriminator; + 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 3fe4550b6759e..8f63e409028ce 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, + ptrauth_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(); @@ -54,7 +52,7 @@ pub(crate) fn maybe_sign_fn_ptr<'ll, 'tcx>( return llfn; } - let addr_diversity = match schema.is_address_discriminated { + let addr_diversity = match ptrauth_schema.is_address_discriminated { PointerAuthAddressDiscriminator::HardwareAddress(true) => Some(llfn), PointerAuthAddressDiscriminator::HardwareAddress(false) => None, PointerAuthAddressDiscriminator::Synthetic(val) => { @@ -63,7 +61,12 @@ pub(crate) fn maybe_sign_fn_ptr<'ll, 'tcx>( Some(unsafe { llvm::LLVMConstIntToPtr(llval, llty) }) } }; - const_ptr_auth(llfn, schema.key as u32, schema.constant_discriminator as u64, addr_diversity) + const_ptr_auth( + llfn, + ptrauth_schema.key as u32, + ptrauth_schema.constant_discriminator as u64, + addr_diversity, + ) } /* @@ -179,11 +182,11 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { &self, global_alloc: GlobalAlloc<'tcx>, need_symbol_name: bool, - schema: Option<&PointerAuthSchema>, + ptrauth_schema: Option, ) -> Result<&'ll Value, u64> { let alloc = match global_alloc { GlobalAlloc::Function { instance, .. } => { - return Ok(self.get_fn_addr(instance, schema)); + return Ok(self.get_fn_addr(instance, ptrauth_schema)); } GlobalAlloc::Static(def_id) => { assert!(self.tcx.is_static(def_id)); @@ -405,7 +408,7 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { cv: Scalar, layout: abi::Scalar, llty: &'ll Type, - schema: Option<&PointerAuthSchema>, + ptrauth_schema: Option, ) -> &'ll Value { let bitsize = if layout.is_bool() { 1 } else { layout.size(self).bits() }; match cv { @@ -422,7 +425,7 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { let (prov, offset) = ptr.prov_and_relative_offset(); let global_alloc = self.tcx.global_alloc(prov.alloc_id()); let base_addr_space = global_alloc.address_space(self); - let base_addr = match self.alloc_to_backend(global_alloc, false, schema) { + let base_addr = match self.alloc_to_backend(global_alloc, false, ptrauth_schema) { Ok(base_addr) => base_addr, Err(base_addr) => { let val = base_addr.wrapping_add(offset.bytes()); diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 853c4bfc9ca3f..c136fb6bbde8a 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -938,7 +938,7 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { fn get_fn_addr( &self, instance: Instance<'tcx>, - pointer_auth_schema: Option<&PointerAuthSchema>, + ptrauth_schema: Option, ) -> &'ll Value { // When pointer authentication metadata is provided, `get_fn_addr` will // attempt to sign the pointer using LLVM's `ConstPtrAuth` constant @@ -953,7 +953,7 @@ impl<'ll, 'tcx> MiscCodegenMethods<'tcx> for CodegenCx<'ll, 'tcx> { // , and comment in // builder's `ptrauth_operand_bundle`. let llfn = get_fn(self, instance); - match pointer_auth_schema { + match ptrauth_schema { Some(schema) => common::maybe_sign_fn_ptr(self, instance, llfn, schema), None => llfn, } diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs index cb0209a0ae369..0e37705ec82b5 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..b45b5667be6c9 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>, + ptrauth_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..3d1a931a12e83 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>, + ptrauth_schema: Option, ) -> Self::Value; fn eh_personality(&self) -> Self::Function; fn sess(&self) -> &Session; diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index f04f40dd17168..e31176a88acee 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -95,6 +95,7 @@ pub enum PointerAuthARM8_3Key { } /// Forms of extra discrimination. +#[derive(Clone, Debug, PartialEq)] pub enum PointerAuthDiscrimination { /// No additional discrimination. None, @@ -107,6 +108,7 @@ pub enum PointerAuthDiscrimination { } /// Types of address discrimination. +#[derive(Clone, Debug)] pub enum PointerAuthAddressDiscriminator { /// Enable/disable hardware address discrimination. HardwareAddress(bool), @@ -115,6 +117,7 @@ pub enum PointerAuthAddressDiscriminator { Synthetic(u64), } +#[derive(Clone, Debug)] pub struct PointerAuthSchema { pub is_address_discriminated: PointerAuthAddressDiscriminator, pub discrimination_kind: PointerAuthDiscrimination, @@ -1185,12 +1188,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) } } diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs index 26fedbd8a5481..83f38b5be8a9b 100644 --- a/compiler/rustc_target/src/callconv/mod.rs +++ b/compiler/rustc_target/src/callconv/mod.rs @@ -625,12 +625,15 @@ 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_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_discriminator } = + self; f.debug_struct("FnAbi") .field("args", args) .field("ret", ret) @@ -638,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_discriminator", ptrauth_discriminator) .finish() } } @@ -950,6 +954,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 65d589cc35aa0..2b59c905b19f5 100644 --- a/compiler/rustc_ty_utils/src/abi.rs +++ b/compiler/rustc_ty_utils/src/abi.rs @@ -6,6 +6,7 @@ use rustc_hir::attrs::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::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, @@ -611,6 +612,11 @@ fn fn_abi_new_uncached<'tcx>( determined_fn_def_id, sig.abi(), ), + ptrauth_discriminator: if tcx.sess.pointer_authentication_fn_ptr_type_discrimination() { + ptrauth_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/tests/ui/abi/c-zst.aarch64-darwin.stderr b/tests/ui/abi/c-zst.aarch64-darwin.stderr index 6d2ac90c0c975..a99eb7cd1e830 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_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..308d3a8625638 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_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..308d3a8625638 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_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..308d3a8625638 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_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..a99eb7cd1e830 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_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..308d3a8625638 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_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 6242d93b09534..ae0edfd7369a2 100644 --- a/tests/ui/abi/debug.generic.stderr +++ b/tests/ui/abi/debug.generic.stderr @@ -106,6 +106,7 @@ error: fn_abi_of(test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:31:1 | @@ -187,6 +188,7 @@ error: fn_abi_of(TestFnPtr) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:37:1 | @@ -258,6 +260,7 @@ error: fn_abi_of(test_generic) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:40:1 | @@ -336,6 +339,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } right ABI = FnAbi { args: [ @@ -402,6 +406,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:59:1 | @@ -481,6 +486,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } right ABI = FnAbi { args: [ @@ -554,6 +560,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:62:1 | @@ -626,6 +633,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } right ABI = FnAbi { args: [ @@ -692,6 +700,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:65:1 | @@ -764,6 +773,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } right ABI = FnAbi { args: [ @@ -830,6 +840,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:69:1 | @@ -924,6 +935,7 @@ error: fn_abi_of(assoc_test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:52:5 | diff --git a/tests/ui/abi/debug.loongarch64.stderr b/tests/ui/abi/debug.loongarch64.stderr index 176c68ecd4c7b..ed20ec1ef3283 100644 --- a/tests/ui/abi/debug.loongarch64.stderr +++ b/tests/ui/abi/debug.loongarch64.stderr @@ -106,6 +106,7 @@ error: fn_abi_of(test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:31:1 | @@ -187,6 +188,7 @@ error: fn_abi_of(TestFnPtr) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:37:1 | @@ -258,6 +260,7 @@ error: fn_abi_of(test_generic) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:40:1 | @@ -336,6 +339,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } right ABI = FnAbi { args: [ @@ -402,6 +406,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:59:1 | @@ -481,6 +486,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } right ABI = FnAbi { args: [ @@ -554,6 +560,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:62:1 | @@ -626,6 +633,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } right ABI = FnAbi { args: [ @@ -692,6 +700,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:65:1 | @@ -764,6 +773,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } right ABI = FnAbi { args: [ @@ -830,6 +840,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:69:1 | @@ -924,6 +935,7 @@ error: fn_abi_of(assoc_test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:52:5 | diff --git a/tests/ui/abi/debug.riscv64.stderr b/tests/ui/abi/debug.riscv64.stderr index 176c68ecd4c7b..ed20ec1ef3283 100644 --- a/tests/ui/abi/debug.riscv64.stderr +++ b/tests/ui/abi/debug.riscv64.stderr @@ -106,6 +106,7 @@ error: fn_abi_of(test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:31:1 | @@ -187,6 +188,7 @@ error: fn_abi_of(TestFnPtr) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:37:1 | @@ -258,6 +260,7 @@ error: fn_abi_of(test_generic) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:40:1 | @@ -336,6 +339,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } right ABI = FnAbi { args: [ @@ -402,6 +406,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:59:1 | @@ -481,6 +486,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } right ABI = FnAbi { args: [ @@ -554,6 +560,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:62:1 | @@ -626,6 +633,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } right ABI = FnAbi { args: [ @@ -692,6 +700,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:65:1 | @@ -764,6 +773,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } right ABI = FnAbi { args: [ @@ -830,6 +840,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_discriminator: 0, } --> $DIR/debug.rs:69:1 | @@ -924,6 +935,7 @@ error: fn_abi_of(assoc_test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, + ptrauth_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 65818feab4297..927594534d9b8 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 @@ -69,6 +69,7 @@ error: fn_abi_of(i8) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:13:1 | @@ -146,6 +147,7 @@ error: fn_abi_of(u8) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:19:1 | @@ -223,6 +225,7 @@ error: fn_abi_of(i16) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:25:1 | @@ -300,6 +303,7 @@ error: fn_abi_of(u16) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:31:1 | @@ -377,6 +381,7 @@ error: fn_abi_of(i32) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:37:1 | @@ -454,6 +459,7 @@ error: fn_abi_of(u32) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_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 cbe389c42d40a..113ad20e16bc4 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 @@ -69,6 +69,7 @@ error: fn_abi_of(i8) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:13:1 | @@ -146,6 +147,7 @@ error: fn_abi_of(u8) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:19:1 | @@ -223,6 +225,7 @@ error: fn_abi_of(i16) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:25:1 | @@ -300,6 +303,7 @@ error: fn_abi_of(u16) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:31:1 | @@ -377,6 +381,7 @@ error: fn_abi_of(i32) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_discriminator: 0, } --> $DIR/x86-64-sysv64-arg-ext.rs:37:1 | @@ -454,6 +459,7 @@ error: fn_abi_of(u32) = FnAbi { SysV64, ), can_unwind: false, + ptrauth_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 efeec0d86982b..e03828c2e78a4 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_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_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..ed8fe5b83fe7c 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_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..7f25ffc3c4481 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_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 d5da912a9b89a..150a9262b0f88 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.win.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.win.stderr @@ -66,6 +66,7 @@ error: fn_abi_of(take_va_list) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, + ptrauth_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..1705d3ca7509b 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_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_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_discriminator: 0, } --> $DIR/pass-by-value-abi.rs:44:1 | From 1846500cbc24252a6678ba48452086707c9a0599 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Thu, 20 Aug 2026 06:47:34 +0000 Subject: [PATCH 02/12] [PAC] Use Option in FnAbi's discriminator field --- compiler/rustc_codegen_gcc/src/int.rs | 2 +- compiler/rustc_codegen_llvm/src/builder.rs | 7 +++--- compiler/rustc_target/src/callconv/mod.rs | 4 ++-- compiler/rustc_ty_utils/src/abi.rs | 4 ++-- tests/ui/abi/c-zst.aarch64-darwin.stderr | 2 +- tests/ui/abi/c-zst.powerpc-linux.stderr | 2 +- tests/ui/abi/c-zst.s390x-linux.stderr | 2 +- tests/ui/abi/c-zst.sparc64-linux.stderr | 2 +- tests/ui/abi/c-zst.x86_64-linux.stderr | 2 +- .../ui/abi/c-zst.x86_64-pc-windows-gnu.stderr | 2 +- tests/ui/abi/debug.generic.stderr | 24 +++++++++---------- tests/ui/abi/debug.loongarch64.stderr | 24 +++++++++---------- tests/ui/abi/debug.riscv64.stderr | 24 +++++++++---------- .../x86-64-sysv64-arg-ext.apple.stderr | 12 +++++----- .../x86-64-sysv64-arg-ext.other.stderr | 12 +++++----- tests/ui/abi/pass-indirectly-attr.stderr | 4 ++-- tests/ui/abi/sysv64-zst.stderr | 2 +- .../pass-by-value-abi.aarch64.stderr | 2 +- .../c-variadic/pass-by-value-abi.win.stderr | 2 +- .../pass-by-value-abi.x86_64.stderr | 6 ++--- 20 files changed, 71 insertions(+), 70 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/int.rs b/compiler/rustc_codegen_gcc/src/int.rs index 10cbe1ccb8059..fe0654c665c76 100644 --- a/compiler/rustc_codegen_gcc/src/int.rs +++ b/compiler/rustc_codegen_gcc/src/int.rs @@ -375,7 +375,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> { fixed_count: 3, conv: CanonAbi::C, can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, }; 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 63e6faf3ced93..63563bad966d9 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -2197,9 +2197,10 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> { // discussion in the rust-lang issue: let key: u32 = self.sess().pointer_authentication_fn_ptr_key().unwrap() as u32; - // If sess().pointer_authentication_fn_ptr_type_discrimination() is true, this contains - // the function pointer type discriminator; otherwise, it is 0. - let discriminator = fn_abi?.ptrauth_discriminator; + // If sess().pointer_authentication_fn_ptr_type_discrimination() is enabled, this contains + // the function pointer type discriminator; otherwise, it is None. LLVM expects a u64 here, + // so use 0 when no discriminator is present. + let discriminator = fn_abi?.ptrauth_discriminator.unwrap_or(0); Some(llvm::OperandBundleBox::new( "ptrauth", diff --git a/compiler/rustc_target/src/callconv/mod.rs b/compiler/rustc_target/src/callconv/mod.rs index 83f38b5be8a9b..14ad0eb477d5d 100644 --- a/compiler/rustc_target/src/callconv/mod.rs +++ b/compiler/rustc_target/src/callconv/mod.rs @@ -626,7 +626,7 @@ pub struct FnAbi<'a, Ty> { /// 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_discriminator: u64, + pub ptrauth_discriminator: Option, } // Needs to be a custom impl because of the bounds on the `TyAndLayout` debug impl. @@ -954,6 +954,6 @@ mod size_asserts { use super::*; // tidy-alphabetical-start static_assert_size!(ArgAbi<'_, usize>, 56); - static_assert_size!(FnAbi<'_, usize>, 88); + static_assert_size!(FnAbi<'_, usize>, 96); // tidy-alphabetical-end } diff --git a/compiler/rustc_ty_utils/src/abi.rs b/compiler/rustc_ty_utils/src/abi.rs index 2b59c905b19f5..318b3273219a5 100644 --- a/compiler/rustc_ty_utils/src/abi.rs +++ b/compiler/rustc_ty_utils/src/abi.rs @@ -613,9 +613,9 @@ fn fn_abi_new_uncached<'tcx>( sig.abi(), ), ptrauth_discriminator: if tcx.sess.pointer_authentication_fn_ptr_type_discrimination() { - ptrauth_compute_fn_ptr_type_discriminator_for(tcx, sig).unwrap_or(0).into() + Some(ptrauth_compute_fn_ptr_type_discriminator_for(tcx, sig).unwrap_or(0).into()) } else { - 0 + None }, }; fn_abi_adjust_for_abi(cx, &mut fn_abi, sig.abi()); diff --git a/tests/ui/abi/c-zst.aarch64-darwin.stderr b/tests/ui/abi/c-zst.aarch64-darwin.stderr index a99eb7cd1e830..e7cb6199ab45c 100644 --- a/tests/ui/abi/c-zst.aarch64-darwin.stderr +++ b/tests/ui/abi/c-zst.aarch64-darwin.stderr @@ -59,7 +59,7 @@ error: fn_abi_of(pass_zst) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $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 308d3a8625638..437ebd63ceba5 100644 --- a/tests/ui/abi/c-zst.powerpc-linux.stderr +++ b/tests/ui/abi/c-zst.powerpc-linux.stderr @@ -70,7 +70,7 @@ error: fn_abi_of(pass_zst) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $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 308d3a8625638..437ebd63ceba5 100644 --- a/tests/ui/abi/c-zst.s390x-linux.stderr +++ b/tests/ui/abi/c-zst.s390x-linux.stderr @@ -70,7 +70,7 @@ error: fn_abi_of(pass_zst) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $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 308d3a8625638..437ebd63ceba5 100644 --- a/tests/ui/abi/c-zst.sparc64-linux.stderr +++ b/tests/ui/abi/c-zst.sparc64-linux.stderr @@ -70,7 +70,7 @@ error: fn_abi_of(pass_zst) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $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 a99eb7cd1e830..e7cb6199ab45c 100644 --- a/tests/ui/abi/c-zst.x86_64-linux.stderr +++ b/tests/ui/abi/c-zst.x86_64-linux.stderr @@ -59,7 +59,7 @@ error: fn_abi_of(pass_zst) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $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 308d3a8625638..437ebd63ceba5 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,7 +70,7 @@ error: fn_abi_of(pass_zst) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/c-zst.rs:65:1 | diff --git a/tests/ui/abi/debug.generic.stderr b/tests/ui/abi/debug.generic.stderr index ae0edfd7369a2..82c469e0f4f80 100644 --- a/tests/ui/abi/debug.generic.stderr +++ b/tests/ui/abi/debug.generic.stderr @@ -106,7 +106,7 @@ error: fn_abi_of(test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:31:1 | @@ -188,7 +188,7 @@ error: fn_abi_of(TestFnPtr) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:37:1 | @@ -260,7 +260,7 @@ error: fn_abi_of(test_generic) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:40:1 | @@ -339,7 +339,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } right ABI = FnAbi { args: [ @@ -406,7 +406,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:59:1 | @@ -486,7 +486,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } right ABI = FnAbi { args: [ @@ -560,7 +560,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:62:1 | @@ -633,7 +633,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } right ABI = FnAbi { args: [ @@ -700,7 +700,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:65:1 | @@ -773,7 +773,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } right ABI = FnAbi { args: [ @@ -840,7 +840,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:69:1 | @@ -935,7 +935,7 @@ error: fn_abi_of(assoc_test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:52:5 | diff --git a/tests/ui/abi/debug.loongarch64.stderr b/tests/ui/abi/debug.loongarch64.stderr index ed20ec1ef3283..b5c73d00564c6 100644 --- a/tests/ui/abi/debug.loongarch64.stderr +++ b/tests/ui/abi/debug.loongarch64.stderr @@ -106,7 +106,7 @@ error: fn_abi_of(test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:31:1 | @@ -188,7 +188,7 @@ error: fn_abi_of(TestFnPtr) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:37:1 | @@ -260,7 +260,7 @@ error: fn_abi_of(test_generic) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:40:1 | @@ -339,7 +339,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } right ABI = FnAbi { args: [ @@ -406,7 +406,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:59:1 | @@ -486,7 +486,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } right ABI = FnAbi { args: [ @@ -560,7 +560,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:62:1 | @@ -633,7 +633,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } right ABI = FnAbi { args: [ @@ -700,7 +700,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:65:1 | @@ -773,7 +773,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } right ABI = FnAbi { args: [ @@ -840,7 +840,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:69:1 | @@ -935,7 +935,7 @@ error: fn_abi_of(assoc_test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:52:5 | diff --git a/tests/ui/abi/debug.riscv64.stderr b/tests/ui/abi/debug.riscv64.stderr index ed20ec1ef3283..b5c73d00564c6 100644 --- a/tests/ui/abi/debug.riscv64.stderr +++ b/tests/ui/abi/debug.riscv64.stderr @@ -106,7 +106,7 @@ error: fn_abi_of(test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:31:1 | @@ -188,7 +188,7 @@ error: fn_abi_of(TestFnPtr) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:37:1 | @@ -260,7 +260,7 @@ error: fn_abi_of(test_generic) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:40:1 | @@ -339,7 +339,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } right ABI = FnAbi { args: [ @@ -406,7 +406,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:59:1 | @@ -486,7 +486,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } right ABI = FnAbi { args: [ @@ -560,7 +560,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:62:1 | @@ -633,7 +633,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } right ABI = FnAbi { args: [ @@ -700,7 +700,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:65:1 | @@ -773,7 +773,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } right ABI = FnAbi { args: [ @@ -840,7 +840,7 @@ error: ABIs are not compatible fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/debug.rs:69:1 | @@ -935,7 +935,7 @@ error: fn_abi_of(assoc_test) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: $SOME_BOOL, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $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 927594534d9b8..df49fbfe3d272 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 @@ -69,7 +69,7 @@ error: fn_abi_of(i8) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/x86-64-sysv64-arg-ext.rs:13:1 | @@ -147,7 +147,7 @@ error: fn_abi_of(u8) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/x86-64-sysv64-arg-ext.rs:19:1 | @@ -225,7 +225,7 @@ error: fn_abi_of(i16) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/x86-64-sysv64-arg-ext.rs:25:1 | @@ -303,7 +303,7 @@ error: fn_abi_of(u16) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/x86-64-sysv64-arg-ext.rs:31:1 | @@ -381,7 +381,7 @@ error: fn_abi_of(i32) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/x86-64-sysv64-arg-ext.rs:37:1 | @@ -459,7 +459,7 @@ error: fn_abi_of(u32) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $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 113ad20e16bc4..7ceb6a2092af2 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 @@ -69,7 +69,7 @@ error: fn_abi_of(i8) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/x86-64-sysv64-arg-ext.rs:13:1 | @@ -147,7 +147,7 @@ error: fn_abi_of(u8) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/x86-64-sysv64-arg-ext.rs:19:1 | @@ -225,7 +225,7 @@ error: fn_abi_of(i16) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/x86-64-sysv64-arg-ext.rs:25:1 | @@ -303,7 +303,7 @@ error: fn_abi_of(u16) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/x86-64-sysv64-arg-ext.rs:31:1 | @@ -381,7 +381,7 @@ error: fn_abi_of(i32) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/x86-64-sysv64-arg-ext.rs:37:1 | @@ -459,7 +459,7 @@ error: fn_abi_of(u32) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $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 e03828c2e78a4..935ab97647321 100644 --- a/tests/ui/abi/pass-indirectly-attr.stderr +++ b/tests/ui/abi/pass-indirectly-attr.stderr @@ -83,7 +83,7 @@ error: fn_abi_of(extern_c) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/pass-indirectly-attr.rs:20:1 | @@ -175,7 +175,7 @@ error: fn_abi_of(extern_rust) = FnAbi { fixed_count: 1, conv: Rust, can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/pass-indirectly-attr.rs:27:1 | diff --git a/tests/ui/abi/sysv64-zst.stderr b/tests/ui/abi/sysv64-zst.stderr index ed8fe5b83fe7c..f19480faec4fa 100644 --- a/tests/ui/abi/sysv64-zst.stderr +++ b/tests/ui/abi/sysv64-zst.stderr @@ -61,7 +61,7 @@ error: fn_abi_of(pass_zst) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $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 7f25ffc3c4481..c616e45a9ab8e 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.aarch64.stderr @@ -70,7 +70,7 @@ error: fn_abi_of(take_va_list) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $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 150a9262b0f88..638a0856b7b7f 100644 --- a/tests/ui/c-variadic/pass-by-value-abi.win.stderr +++ b/tests/ui/c-variadic/pass-by-value-abi.win.stderr @@ -66,7 +66,7 @@ error: fn_abi_of(take_va_list) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $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 1705d3ca7509b..776d0a72287f9 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,7 +70,7 @@ error: fn_abi_of(take_va_list) = FnAbi { fixed_count: 1, conv: C, can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/pass-by-value-abi.rs:27:1 | @@ -151,7 +151,7 @@ error: fn_abi_of(take_va_list_sysv64) = FnAbi { SysV64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/pass-by-value-abi.rs:37:1 | @@ -232,7 +232,7 @@ error: fn_abi_of(take_va_list_win64) = FnAbi { Win64, ), can_unwind: false, - ptrauth_discriminator: 0, + ptrauth_discriminator: None, } --> $DIR/pass-by-value-abi.rs:44:1 | From 9705c127fc79454fe782e948317489cb0e928893 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Thu, 10 Sep 2026 13:33:36 +0000 Subject: [PATCH 03/12] [PAC] repr(transparent) and correct handling of Option in encoder Also tighten the handling of enums. --- .../rustc_middle/src/ptrauth/discriminator.rs | 72 ++++++++++++------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/compiler/rustc_middle/src/ptrauth/discriminator.rs b/compiler/rustc_middle/src/ptrauth/discriminator.rs index 7501f1b072188..30226b8eebe07 100644 --- a/compiler/rustc_middle/src/ptrauth/discriminator.rs +++ b/compiler/rustc_middle/src/ptrauth/discriminator.rs @@ -81,6 +81,7 @@ use rustc_session::PointerAuthSchema; use rustc_span::sym; use crate::ptrauth::llvm_siphash::llvm_pointer_auth_stable_siphash; +use crate::ty::layout::LayoutCx; /// Types that can serve as a source for function pointer type discrimination. /// @@ -317,35 +318,46 @@ enum ClangDiscTy<'tcx> { Void, } -// Canonicalize Option-wrapped pointer types used to model C nullable pointers. +// Canonicalize types that are ABI-compatible with C's nullable pointer +// convention, so the rest of this encoder can treat them like the corresponding +// plain pointer type. // -// 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. +// Rust guarantees the null-pointer optimization for references, function +// pointers, Box, NonNull, and NonZero*. `Option` and `Option<&T>` are +// therefore unwrapped here. `Option<*mut T>` and `Option<*const T>` are +// deliberately left unchanged: raw pointers are not covered by the NPO +// guarantee and are handled by the general `Adt` arm in `to_clang_disc_ty`. // -// 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, - _ => {} +// Also peels `repr(transparent)` wrappers to canonicalize them to their +// underlying type. +fn canonicalize_c_type<'tcx>(tcx: TyCtxt<'tcx>, mut ty: Ty<'tcx>) -> Ty<'tcx> { + loop { + let before = ty; + + if let ty::Adt(def, args) = ty.kind() + && tcx.is_diagnostic_item(sym::Option, def.did()) + { + let inner = args.type_at(0); + if let ty::FnPtr(..) | ty::Ref(..) = inner.kind() { + ty = inner; + } } - } - ty + // Only ADTs can be repr(transparent); skip the layout query entirely + // for everything else. + if matches!(ty.kind(), ty::Adt(..)) { + let typing_env = ty::TypingEnv::fully_monomorphized(); + + if let Ok(layout) = tcx.layout_of(typing_env.as_query_input(ty)) { + let cx = LayoutCx::new(tcx, typing_env); + ty = layout.peel_transparent_wrappers(&cx).ty; + } + } + + if ty == before { + return ty; + } + } } /// Lowers a Rust type into a Clang-compatible discriminator type. @@ -388,8 +400,14 @@ fn to_clang_disc_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ClangDiscTy<'tcx> // arrays ignore size ty::Array(elem, _) => ClangDiscTy::Array { elem: *elem }, - // enums to integer collapse - ty::Adt(def, _) if def.is_enum() => ClangDiscTy::EnumLikeInt, + // enums to integer collapse - mirrors Clang's Type::Enum handling, + // which recurses into the enum's underlying integer type per C11 + // 6.7.2.2p4. + // A non-niche, data-carrying enum (e.g. Option<*mut T>) is not an + // "enumerated type" in the C11 sense, such enums fall through to the + // general Adt(_) => AdtName(..) arm below instead. + ty::Adt(def, _) if def.is_enum() && def.is_payloadfree() => ClangDiscTy::EnumLikeInt, + // simd vectors ty::Adt(def, args) if def.repr().simd() => { // Clang encodes SIMD vectors by their total size From b36b64fc5765dd9270482898910b2062c44bb35e Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Fri, 10 Jul 2026 12:16:40 +0000 Subject: [PATCH 04/12] [PAC] Enable support for FPTR_TYPE_DISCR in ABI Version Also remove error messages/tests that used to guarded it. --- compiler/rustc_session/src/diagnostics.rs | 6 ------ compiler/rustc_session/src/session.rs | 21 +++++-------------- ...on_not_supported_pointer_authentication.rs | 12 ----------- ...ot_supported_pointer_authentication.stderr | 4 ---- 4 files changed, 5 insertions(+), 38 deletions(-) delete mode 100644 tests/ui/pointer_authentication/type_discrimination_not_supported_pointer_authentication.rs delete mode 100644 tests/ui/pointer_authentication/type_discrimination_not_supported_pointer_authentication.stderr diff --git a/compiler/rustc_session/src/diagnostics.rs b/compiler/rustc_session/src/diagnostics.rs index e8f29d8a9ee77..deb786245197b 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 e31176a88acee..8db8cd163e6e0 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -208,8 +208,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) @@ -227,7 +226,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 } @@ -1469,19 +1471,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/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 - From 2baf197208d2d3ab4baa4a7f66b05a678704ef46 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Fri, 10 Jul 2026 12:59:12 +0000 Subject: [PATCH 05/12] [PAC] Support type discriminators in static allocations The codegen now walks the layout of static initializer types to find extern "C" function pointer fields, computes their type discriminators, and applies those discriminators when emitting authenticated function pointer relocations. Also make sure that type discrimination is never applied to init/fini entries. --- compiler/rustc_codegen_gcc/src/common.rs | 4 +- compiler/rustc_codegen_llvm/src/asm.rs | 8 +- compiler/rustc_codegen_llvm/src/common.rs | 20 ++- compiler/rustc_codegen_llvm/src/consts.rs | 169 +++++++++++++++++- .../rustc_codegen_ssa/src/traits/consts.rs | 5 +- 5 files changed, 194 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index 712a1c14ef8e6..80396e7eb70de 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -1,9 +1,10 @@ use gccjit::{GlobalKind, LValue, RValue, ToRValue, Type}; use rustc_abi::Primitive::Pointer; -use rustc_abi::{self as abi, HasDataLayout}; +use rustc_abi::{self as abi, HasDataLayout, Size}; use rustc_codegen_ssa::traits::{ BaseTypeCodegenMethods, ConstCodegenMethods, MiscCodegenMethods, StaticCodegenMethods, }; +use rustc_data_structures::fx::FxHashMap; use rustc_middle::mir::Mutability; use rustc_middle::mir::interpret::{GlobalAlloc, PointerArithmetic, Scalar}; use rustc_middle::ty::layout::LayoutOf; @@ -324,6 +325,7 @@ impl<'gcc, 'tcx> ConstCodegenMethods for CodegenCx<'gcc, 'tcx> { layout: abi::Scalar, ty: Type<'gcc>, _ptrauth_schema: Option, + _ptrauth_discriminators: Option<&FxHashMap>, ) -> RValue<'gcc> { let bitsize = if layout.is_bool() { 1 } else { layout.size(self).bits() }; match cv { diff --git a/compiler/rustc_codegen_llvm/src/asm.rs b/compiler/rustc_codegen_llvm/src/asm.rs index 0f74f5e81d684..8e71e587235c6 100644 --- a/compiler/rustc_codegen_llvm/src/asm.rs +++ b/compiler/rustc_codegen_llvm/src/asm.rs @@ -165,7 +165,8 @@ impl<'ll, 'tcx> AsmBuilderMethods<'tcx> for Builder<'_, 'll, 'tcx> { ConstScalar::Ptr(ptr, _) => { let (prov, _) = ptr.prov_and_relative_offset(); let global_alloc = self.tcx.global_alloc(prov.alloc_id()); - let value = self.cx.alloc_to_backend(global_alloc, false, None).unwrap(); + let value = + self.cx.alloc_to_backend(global_alloc, false, None, None).unwrap(); inputs.push(value); op_idx.insert(idx, constraints.len()); constraints.push("s".to_string()); @@ -455,8 +456,9 @@ impl<'tcx> AsmCodegenMethods<'tcx> for CodegenCx<'_, 'tcx> { ConstScalar::Ptr(ptr, _) => { let (prov, offset) = ptr.prov_and_relative_offset(); let global_alloc = self.tcx.global_alloc(prov.alloc_id()); - let llval = - self.alloc_to_backend(global_alloc, true, None).unwrap(); + let llval = self + .alloc_to_backend(global_alloc, true, None, None) + .unwrap(); self.add_compiler_used_global(llval); let symbol = llvm::build_string(|s| unsafe { diff --git a/compiler/rustc_codegen_llvm/src/common.rs b/compiler/rustc_codegen_llvm/src/common.rs index 8f63e409028ce..3892ad7ab3dea 100644 --- a/compiler/rustc_codegen_llvm/src/common.rs +++ b/compiler/rustc_codegen_llvm/src/common.rs @@ -4,11 +4,12 @@ use std::borrow::Borrow; use libc::{c_char, c_uint}; use rustc_abi::Primitive::Pointer; -use rustc_abi::{self as abi, ExternAbi, HasDataLayout as _}; +use rustc_abi::{self as abi, ExternAbi, HasDataLayout as _, Size}; use rustc_ast::Mutability; use rustc_codegen_ssa::common::TypeKind; use rustc_codegen_ssa::traits::*; use rustc_crate_store::DllImport; +use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::stable_hash::{StableHash, StableHasher}; use rustc_hashes::Hash128; use rustc_hir::def::DefKind; @@ -183,6 +184,7 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { global_alloc: GlobalAlloc<'tcx>, need_symbol_name: bool, ptrauth_schema: Option, + ptrauth_discriminators: Option<&FxHashMap>, ) -> Result<&'ll Value, u64> { let alloc = match global_alloc { GlobalAlloc::Function { instance, .. } => { @@ -229,7 +231,13 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { } }; - let init = const_alloc_to_llvm(self, alloc.inner(), IsStatic::No, IsInitOrFini::No); + let init = const_alloc_to_llvm( + self, + alloc.inner(), + IsStatic::No, + IsInitOrFini::No, + ptrauth_discriminators, + ); let alloc = alloc.inner(); if need_symbol_name { @@ -409,6 +417,7 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { layout: abi::Scalar, llty: &'ll Type, ptrauth_schema: Option, + ptrauth_discriminators: Option<&FxHashMap>, ) -> &'ll Value { let bitsize = if layout.is_bool() { 1 } else { layout.size(self).bits() }; match cv { @@ -425,7 +434,12 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> { let (prov, offset) = ptr.prov_and_relative_offset(); let global_alloc = self.tcx.global_alloc(prov.alloc_id()); let base_addr_space = global_alloc.address_space(self); - let base_addr = match self.alloc_to_backend(global_alloc, false, ptrauth_schema) { + let base_addr = match self.alloc_to_backend( + global_alloc, + false, + ptrauth_schema, + ptrauth_discriminators, + ) { Ok(base_addr) => base_addr, Err(base_addr) => { let val = base_addr.wrapping_add(offset.bytes()); diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs index 8ecbcf72b1ea4..64849b4eef1d8 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::attrs::Linkage; use rustc_hir::attrs::lang_items::LangItem; 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::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,135 @@ pub(crate) enum IsInitOrFini { Yes, No, } + +/// 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: +/// - references +/// - direct function pointers +/// - 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>, +) -> FxHashMap { + let mut map = FxHashMap::default(); + + collect_fn_ptr_discriminators_inner(tcx, typing_env, ty, Size::ZERO, &mut map); + + 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) = ptrauth_compute_fn_ptr_type_discriminator_for(tcx, ty) { + map.insert(base_offset, disc.into()); + + return; + } + + match ty.kind() { + ty::Ref(_, pointee, _) => { + collect_fn_ptr_discriminators_inner(tcx, typing_env, *pointee, 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, + ptrauth_discriminators: Option<&FxHashMap>, ) -> &'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 +247,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 +255,16 @@ pub(crate) fn const_alloc_to_llvm<'ll>( } else { None }; + let discr = + ptrauth_discriminators.as_ref().and_then(|m| m.get(&Size::from_bytes(offset as u64))); + + // Init/fini entries must not participate in function pointer type discrimination, they use + // a dedicated constant value (ptrauth_string_discriminator("init_fini") which is: 0xd9d4). + 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 { @@ -137,6 +273,7 @@ pub(crate) fn const_alloc_to_llvm<'ll>( }, cx.type_ptr_ext(address_space), schema, + ptrauth_discriminators, )); next_offset = offset + pointer_size_bytes; } @@ -160,6 +297,15 @@ fn codegen_static_initializer<'ll, 'tcx>( cx: &CodegenCx<'ll, 'tcx>, def_id: DefId, ) -> Result<(&'ll Value, ConstAllocation<'tcx>), ErrorHandled> { + let ptrauth_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 +321,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, + ptrauth_discriminators.as_ref(), + ), + alloc, + )) } fn set_global_alignment<'ll>(cx: &CodegenCx<'ll, '_>, gv: &'ll Value, mut align: Align) { @@ -837,7 +992,13 @@ 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); + // FIXME(jchlanda): Add support for pointer authentication type discrimination. + // `static_addr_of` only receives a `ConstAllocation`, so it does not have the type + // information needed to compute function pointer type discriminators. We'll likely need + // to either compute the discriminator map at callers that still know the Rust type, or + // extend this API to accept the required type information. See + // `codegen_static_initializer` for an example of how the discriminator map is computed. + 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_ssa/src/traits/consts.rs b/compiler/rustc_codegen_ssa/src/traits/consts.rs index b45b5667be6c9..7f82ebda2e5fb 100644 --- a/compiler/rustc_codegen_ssa/src/traits/consts.rs +++ b/compiler/rustc_codegen_ssa/src/traits/consts.rs @@ -1,4 +1,6 @@ use rustc_abi as abi; +use rustc_abi::Size; +use rustc_data_structures::fx::FxHashMap; use rustc_middle::mir::interpret::Scalar; use rustc_session::PointerAuthSchema; @@ -40,7 +42,7 @@ pub trait ConstCodegenMethods: BackendTypes { fn const_to_opt_u128(&self, v: Self::Value, sign_ext: bool) -> Option; fn scalar_to_backend(&self, cv: Scalar, layout: abi::Scalar, llty: Self::Type) -> Self::Value { - self.scalar_to_backend_with_pac(cv, layout, llty, None) + self.scalar_to_backend_with_pac(cv, layout, llty, None, None) } fn scalar_to_backend_with_pac( &self, @@ -48,6 +50,7 @@ pub trait ConstCodegenMethods: BackendTypes { layout: abi::Scalar, llty: Self::Type, ptrauth_schema: Option, + ptrauth_discriminators: Option<&FxHashMap>, ) -> Self::Value; fn const_ptr_byte_offset(&self, val: Self::Value, offset: abi::Size) -> Self::Value; From 4837779cda631ea4f8d580bd6094d45bc474288c Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Wed, 29 Jul 2026 14:18:57 +0000 Subject: [PATCH 06/12] [PAC] Extend support to compile-time constants This covers standalone function pointer constants, promoted temporaries, immutable and mutable statics, arrays of function pointers, and mixed structs containing function pointers. Consult pauth-fn-ptr-type-discrimination-static-allocs.rs test for example uses. Revolves around threading PAC information through: * static_addr_of (StaticCodegenMethods) * from_const and from_const_alloc (both on rustc_codegen_ssa::mir::operand / OperandRef) --- compiler/rustc_codegen_gcc/src/common.rs | 2 +- compiler/rustc_codegen_gcc/src/consts.rs | 8 +- compiler/rustc_codegen_llvm/src/consts.rs | 149 ++-------------- compiler/rustc_codegen_ssa/src/meth.rs | 5 +- compiler/rustc_codegen_ssa/src/mir/operand.rs | 43 ++++- compiler/rustc_codegen_ssa/src/mir/retag.rs | 4 +- .../rustc_codegen_ssa/src/traits/statics.rs | 10 +- .../rustc_middle/src/ptrauth/discriminator.rs | 160 ++++++++++++++++-- compiler/rustc_middle/src/ptrauth/mod.rs | 2 +- compiler/rustc_middle/src/ty/vtable.rs | 23 +++ 10 files changed, 255 insertions(+), 151 deletions(-) diff --git a/compiler/rustc_codegen_gcc/src/common.rs b/compiler/rustc_codegen_gcc/src/common.rs index 80396e7eb70de..0b8b046f49c7d 100644 --- a/compiler/rustc_codegen_gcc/src/common.rs +++ b/compiler/rustc_codegen_gcc/src/common.rs @@ -116,7 +116,7 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> { Mutability::Mut => { self.static_addr_of_mut(const_alloc_to_gcc(self, alloc), alloc.inner().align, None) } - _ => self.static_addr_of(alloc, None), + _ => self.static_addr_of(alloc, None, None), }; if !self.sess().fewer_names() { // FIXME(antoyo): set value name. diff --git a/compiler/rustc_codegen_gcc/src/consts.rs b/compiler/rustc_codegen_gcc/src/consts.rs index 42ff930968501..a25073f964eaf 100644 --- a/compiler/rustc_codegen_gcc/src/consts.rs +++ b/compiler/rustc_codegen_gcc/src/consts.rs @@ -5,6 +5,7 @@ use rustc_abi::{self as abi, Align, HasDataLayout, Primitive, Size, WrappingRang use rustc_codegen_ssa::traits::{ BaseTypeCodegenMethods, ConstCodegenMethods, StaticCodegenMethods, }; +use rustc_data_structures::fx::FxHashMap; use rustc_hir::attrs::Linkage; use rustc_hir::def::DefKind; use rustc_hir::def_id::LOCAL_CRATE; @@ -56,7 +57,12 @@ fn set_global_alignment<'gcc, 'tcx>( } impl<'gcc, 'tcx> StaticCodegenMethods for CodegenCx<'gcc, 'tcx> { - fn static_addr_of(&self, alloc: ConstAllocation<'_>, kind: Option<&str>) -> RValue<'gcc> { + fn static_addr_of( + &self, + alloc: ConstAllocation<'_>, + kind: Option<&str>, + _ptrauth_discriminators: Option<&FxHashMap>, + ) -> RValue<'gcc> { let cv = const_alloc_to_gcc(self, alloc); let align = alloc.inner().align; diff --git a/compiler/rustc_codegen_llvm/src/consts.rs b/compiler/rustc_codegen_llvm/src/consts.rs index 64849b4eef1d8..fd91942f018dc 100644 --- a/compiler/rustc_codegen_llvm/src/consts.rs +++ b/compiler/rustc_codegen_llvm/src/consts.rs @@ -14,9 +14,9 @@ use rustc_middle::mir::interpret::{ read_target_uint, }; use rustc_middle::mono::MonoItem; -use rustc_middle::ptrauth::ptrauth_compute_fn_ptr_type_discriminator_for; +use rustc_middle::ptrauth::ptrauth_collect_fn_ptr_discriminators; use rustc_middle::ty::layout::{HasTypingEnv, LayoutOf}; -use rustc_middle::ty::{self, Instance, Ty, TyCtxt}; +use rustc_middle::ty::{self, Instance}; use rustc_middle::{bug, span_bug}; use rustc_span::Symbol; use rustc_target::spec::Arch; @@ -40,128 +40,6 @@ pub(crate) enum IsInitOrFini { No, } -/// 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: -/// - references -/// - direct function pointers -/// - 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>, -) -> FxHashMap { - let mut map = FxHashMap::default(); - - collect_fn_ptr_discriminators_inner(tcx, typing_env, ty, Size::ZERO, &mut map); - - 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) = ptrauth_compute_fn_ptr_type_discriminator_for(tcx, ty) { - map.insert(base_offset, disc.into()); - - return; - } - - match ty.kind() { - ty::Ref(_, pointee, _) => { - collect_fn_ptr_discriminators_inner(tcx, typing_env, *pointee, 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, @@ -301,7 +179,7 @@ fn codegen_static_initializer<'ll, 'tcx>( 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)) + Some(ptrauth_collect_fn_ptr_discriminators(cx.tcx, cx.typing_env(), ty)) } else { None }; @@ -989,16 +867,21 @@ impl<'ll> StaticCodegenMethods for CodegenCx<'ll, '_> { /// /// The pointer will always be in the default address space. If global variables default to a /// different address space, an addrspacecast is inserted. - fn static_addr_of(&self, alloc: ConstAllocation<'_>, kind: Option<&str>) -> &'ll Value { + fn static_addr_of( + &self, + alloc: ConstAllocation<'_>, + kind: Option<&str>, + ptrauth_discriminators: Option<&FxHashMap>, + ) -> &'ll Value { // FIXME: should we cache `const_alloc_to_llvm` to avoid repeating this for the // same `ConstAllocation`? - // FIXME(jchlanda): Add support for pointer authentication type discrimination. - // `static_addr_of` only receives a `ConstAllocation`, so it does not have the type - // information needed to compute function pointer type discriminators. We'll likely need - // to either compute the discriminator map at callers that still know the Rust type, or - // extend this API to accept the required type information. See - // `codegen_static_initializer` for an example of how the discriminator map is computed. - let cv = const_alloc_to_llvm(self, alloc.inner(), IsStatic::No, IsInitOrFini::No, None); + let cv = const_alloc_to_llvm( + self, + alloc.inner(), + IsStatic::No, + IsInitOrFini::No, + ptrauth_discriminators, + ); 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_ssa/src/meth.rs b/compiler/rustc_codegen_ssa/src/meth.rs index b87034f9b33b7..959812b67e894 100644 --- a/compiler/rustc_codegen_ssa/src/meth.rs +++ b/compiler/rustc_codegen_ssa/src/meth.rs @@ -114,7 +114,10 @@ pub(crate) fn get_vtable<'tcx, Cx: CodegenMethods<'tcx>>( let vtable_alloc_id = tcx.vtable_allocation((ty, trait_ref)); let vtable_allocation = tcx.global_alloc(vtable_alloc_id).unwrap_memory(); - let vtable = cx.static_addr_of(vtable_allocation, Some("vtable")); + // Vtables cannot contain extern "C"/"System" function pointers (asserted in + // vtable_allocation_provider), so they do not require function pointer type + // discriminators. + let vtable = cx.static_addr_of(vtable_allocation, Some("vtable"), None); cx.apply_vcall_visibility_metadata(ty, trait_ref, vtable); cx.create_vtable_debuginfo(ty, trait_ref, vtable); diff --git a/compiler/rustc_codegen_ssa/src/mir/operand.rs b/compiler/rustc_codegen_ssa/src/mir/operand.rs index c1a1b2db6fa9d..8e39a4de8c53b 100644 --- a/compiler/rustc_codegen_ssa/src/mir/operand.rs +++ b/compiler/rustc_codegen_ssa/src/mir/operand.rs @@ -8,6 +8,7 @@ use rustc_abi::{ use rustc_hir::attrs::lang_items::LangItem; use rustc_middle::mir::interpret::{Pointer, Scalar, alloc_range}; use rustc_middle::mir::{self, ConstValue}; +use rustc_middle::ptrauth::ptrauth_collect_fn_ptr_discriminators; use rustc_middle::ty::layout::{LayoutOf, TyAndLayout}; use rustc_middle::ty::{self, Ty}; use rustc_middle::{bug, span_bug}; @@ -182,7 +183,34 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { let BackendRepr::Scalar(scalar) = layout.backend_repr else { bug!("from_const: invalid ByVal layout: {:#?}", layout); }; - let llval = bx.scalar_to_backend(x, scalar, bx.immediate_backend_type(layout)); + let llval = match x { + Scalar::Ptr(..) => { + let pointee_ty = match ty.kind() { + ty::Ref(_, pointee, _) => *pointee, + _ => ty, + }; + let ptrauth_discriminators = + if bx.tcx().sess.pointer_authentication_fn_ptr_type_discrimination() { + Some(ptrauth_collect_fn_ptr_discriminators( + bx.tcx(), + bx.typing_env(), + pointee_ty, + )) + } else { + None + }; + let ptrauth_schema = bx.sess().pointer_authentication_functions(); + bx.scalar_to_backend_with_pac( + x, + scalar, + bx.immediate_backend_type(layout), + ptrauth_schema, + ptrauth_discriminators.as_ref(), + ) + } + _ => bx.scalar_to_backend(x, scalar, bx.immediate_backend_type(layout)), + }; + OperandValue::Immediate(llval) } ConstValue::ZeroSized => return OperandRef::zero_sized(layout), @@ -267,8 +295,19 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { } _ if layout.is_zst() => OperandRef::zero_sized(layout), _ => { + let ptrauth_discriminators = + if bx.tcx().sess.pointer_authentication_fn_ptr_type_discrimination() { + Some(ptrauth_collect_fn_ptr_discriminators( + bx.tcx(), + bx.typing_env(), + layout.ty, + )) + } else { + None + }; + // Neither a scalar nor scalar pair. Load from a place - let base_addr = bx.static_addr_of(alloc, None); + let base_addr = bx.static_addr_of(alloc, None, ptrauth_discriminators.as_ref()); let llval = bx.const_ptr_byte_offset(base_addr, offset); bx.load_operand(PlaceRef::new_sized(llval, layout)) diff --git a/compiler/rustc_codegen_ssa/src/mir/retag.rs b/compiler/rustc_codegen_ssa/src/mir/retag.rs index 397fb423e8da3..725f1302170ed 100644 --- a/compiler/rustc_codegen_ssa/src/mir/retag.rs +++ b/compiler/rustc_codegen_ssa/src/mir/retag.rs @@ -271,7 +271,9 @@ impl<'a, 'tcx, V> RetagPlan { let global_alloc = tcx.global_alloc(alloc_id); let global_mem = global_alloc.unwrap_memory(); - bx.cx().static_addr_of(global_mem, None) + // The range table contains only integer data, not pointer relocations, so no ptrauth + // discriminators are needed. + bx.cx().static_addr_of(global_mem, None, None) } } diff --git a/compiler/rustc_codegen_ssa/src/traits/statics.rs b/compiler/rustc_codegen_ssa/src/traits/statics.rs index c726213025350..20394c98812c1 100644 --- a/compiler/rustc_codegen_ssa/src/traits/statics.rs +++ b/compiler/rustc_codegen_ssa/src/traits/statics.rs @@ -1,10 +1,18 @@ +use rustc_abi::Size; +use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::DefId; use rustc_middle::mir::interpret::ConstAllocation; use super::BackendTypes; pub trait StaticCodegenMethods: BackendTypes { - fn static_addr_of(&self, alloc: ConstAllocation<'_>, kind: Option<&str>) -> Self::Value; + fn static_addr_of( + &self, + alloc: ConstAllocation<'_>, + kind: Option<&str>, + ptrauth_discriminators: Option<&FxHashMap>, + ) -> Self::Value; + fn codegen_static(&mut self, def_id: DefId); } diff --git a/compiler/rustc_middle/src/ptrauth/discriminator.rs b/compiler/rustc_middle/src/ptrauth/discriminator.rs index 30226b8eebe07..65310ea0de427 100644 --- a/compiler/rustc_middle/src/ptrauth/discriminator.rs +++ b/compiler/rustc_middle/src/ptrauth/discriminator.rs @@ -1,11 +1,13 @@ //! 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. +//! discriminator computation used in pointer authentication, as well as the +//! machinery required to locate function pointer fields in Rust layouts that +//! require such discriminators. //! //! 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 +//! 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 @@ -15,14 +17,16 @@ //! //! ## Overview //! -//! The computation is structured into three conceptual stages: +//! The implementation 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)` @@ -31,29 +35,40 @@ //! 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`. //! +//! In addition to computing discriminators for individual function pointer +//! types, this module can recursively walk Rust type layouts and produce a map +//! from byte offsets to discriminators for function pointer fields contained +//! within aggregates. +//! //! ## Module structure //! //! - High-level API //! - `FnPtrDiscriminatorSource` //! - `ptrauth_compute_fn_ptr_type_discriminator_for` //! - `ptrauth_clone_discriminated_schema_for` +//! - `ptrauth_collect_fn_ptr_discriminators` //! //! - Low-level API //! - `FnPtrTypeDiscriminatorInput` //! - `compute_fn_ptr_type_discriminator` //! +//! - Layout traversal +//! - `ptrauth_collect_fn_ptr_discriminators` +//! //! - Signature extraction //! - `extract_fn_ptr_type` //! @@ -68,14 +83,16 @@ //! //! ## 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. +//! 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. +//! This implementation intentionally approximates Clang's behavior for +//! `extern "C"` and `extern "System"` function types only. It does NOT attempt +//! to model full Rust type system rules. -use rustc_abi::ExternAbi; +use rustc_abi::{ExternAbi, Size}; +use rustc_data_structures::fx::FxHashMap; use rustc_middle::ty::{self, Instance, Ty, TyCtxt, Unnormalized}; use rustc_session::PointerAuthSchema; use rustc_span::sym; @@ -256,6 +273,129 @@ fn extract_fn_ptr_type<'tcx>(tcx: TyCtxt<'tcx>, mut ty: Ty<'tcx>) -> Option( + tcx: TyCtxt<'tcx>, + typing_env: ty::TypingEnv<'tcx>, + ty: Ty<'tcx>, +) -> FxHashMap { + let mut map = FxHashMap::default(); + + collect_fn_ptr_discriminators_inner(tcx, typing_env, ty, Size::ZERO, &mut map); + + 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) = ptrauth_compute_fn_ptr_type_discriminator_for(tcx, ty) { + map.insert(base_offset, disc.into()); + + return; + } + + match ty.kind() { + ty::Ref(_, pointee, _) => { + collect_fn_ptr_discriminators_inner(tcx, typing_env, *pointee, 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); + } + } + } + _ => {} + } +} + /// Computes the Clang-compatible function pointer type discriminator. /// /// This is the low-level discriminator computation routine operating on an diff --git a/compiler/rustc_middle/src/ptrauth/mod.rs b/compiler/rustc_middle/src/ptrauth/mod.rs index 0eeef5e07f61c..46b3068524be7 100644 --- a/compiler/rustc_middle/src/ptrauth/mod.rs +++ b/compiler/rustc_middle/src/ptrauth/mod.rs @@ -3,5 +3,5 @@ pub mod llvm_siphash; pub use discriminator::{ FnPtrDiscriminatorSource, FnPtrTypeDiscriminatorInput, ptrauth_clone_discriminated_schema_for, - ptrauth_compute_fn_ptr_type_discriminator_for, + ptrauth_collect_fn_ptr_discriminators, ptrauth_compute_fn_ptr_type_discriminator_for, }; diff --git a/compiler/rustc_middle/src/ty/vtable.rs b/compiler/rustc_middle/src/ty/vtable.rs index fb56bda7d4562..95176361eac3e 100644 --- a/compiler/rustc_middle/src/ty/vtable.rs +++ b/compiler/rustc_middle/src/ty/vtable.rs @@ -85,6 +85,27 @@ pub(super) fn vtable_allocation_provider<'tcx>( tcx: TyCtxt<'tcx>, key: (Ty<'tcx>, Option>), ) -> AllocId { + let ptrauth_assert_not_c_abi_fn_ptr = |instance: ty::Instance<'tcx>| { + if tcx.sess.pointer_authentication_fn_ptr_type_discrimination() { + let sig = tcx + .instantiate_and_normalize_erasing_regions( + instance.args, + ty::TypingEnv::fully_monomorphized(), + tcx.fn_sig(instance.def_id()), + ) + .skip_binder(); + + assert!( + !matches!( + sig.abi(), + rustc_abi::ExternAbi::C { .. } | rustc_abi::ExternAbi::System { .. } + ), + "vtable entry unexpectedly has a C ABI function pointer type: {:?}", + instance + ); + } + }; + let (ty, poly_trait_ref) = key; let vtable_entries = if let Some(poly_trait_ref) = poly_trait_ref { @@ -123,6 +144,7 @@ pub(super) fn vtable_allocation_provider<'tcx>( VtblEntry::MetadataDropInPlace => { if ty.needs_drop(tcx, ty::TypingEnv::fully_monomorphized()) { let instance = ty::Instance::resolve_drop_glue(tcx, ty); + ptrauth_assert_not_c_abi_fn_ptr(instance); let fn_alloc_id = tcx.reserve_and_set_fn_alloc(instance, CTFE_ALLOC_SALT); let fn_ptr = Pointer::from(fn_alloc_id); Scalar::from_pointer(fn_ptr, &tcx) @@ -134,6 +156,7 @@ pub(super) fn vtable_allocation_provider<'tcx>( VtblEntry::MetadataAlign => Scalar::from_uint(align, ptr_size), VtblEntry::Vacant => continue, VtblEntry::Method(instance) => { + ptrauth_assert_not_c_abi_fn_ptr(instance); // Prepare the fn ptr we write into the vtable. let fn_alloc_id = tcx.reserve_and_set_fn_alloc(instance, CTFE_ALLOC_SALT); let fn_ptr = Pointer::from(fn_alloc_id); From 3ac1661524fcf2d68079812400f28d62f4e4e2ce Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Thu, 10 Sep 2026 14:02:00 +0000 Subject: [PATCH 07/12] [PAC] Use scalar_to_backend_with_pac for const globals Also a fix for non function (closure, coroutines, etc) in discriminator_input. Fix in v-table assert. --- compiler/rustc_codegen_ssa/src/mir/operand.rs | 63 +++++++++++++------ .../rustc_middle/src/ptrauth/discriminator.rs | 32 +++++++--- compiler/rustc_middle/src/ty/vtable.rs | 15 ++--- 3 files changed, 72 insertions(+), 38 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/mir/operand.rs b/compiler/rustc_codegen_ssa/src/mir/operand.rs index 8e39a4de8c53b..964b926f3833c 100644 --- a/compiler/rustc_codegen_ssa/src/mir/operand.rs +++ b/compiler/rustc_codegen_ssa/src/mir/operand.rs @@ -199,7 +199,13 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { } else { None }; - let ptrauth_schema = bx.sess().pointer_authentication_functions(); + let mut ptrauth_schema = bx.sess().pointer_authentication_functions(); + if let Some(schema) = ptrauth_schema.as_mut() + && let Some(discr) = + ptrauth_discriminators.as_ref().and_then(|m| m.get(&Size::ZERO)) + { + schema.constant_discriminator = *discr as u16; + } bx.scalar_to_backend_with_pac( x, scalar, @@ -247,13 +253,44 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { let alloc_align = alloc.inner().align; assert!(alloc_align >= layout.align.abi, "{alloc_align:?} < {:?}", layout.align.abi); - let read_scalar = |start, size, s: abi::Scalar, ty| { + let ptrauth_discriminators = + if bx.tcx().sess.pointer_authentication_fn_ptr_type_discrimination() { + Some(ptrauth_collect_fn_ptr_discriminators(bx.tcx(), bx.typing_env(), layout.ty)) + } else { + None + }; + + // `start` is the absolute position to read from within `alloc` (which may be a larger, + // shared allocation). `local_offset` is this field's offset within the value's own layout, + // used only to key into `ptrauth_discriminators`. The two are NOT interchangeable whenever + // `offset != 0` (e.g. the `b` field of a ScalarPair, or a value that isn't at the start of + // its allocation). + let read_scalar = |start: Size, local_offset: Size, size, s: abi::Scalar, ty| { match alloc.0.read_scalar( bx, alloc_range(start, size), /*read_provenance*/ matches!(s.primitive(), abi::Primitive::Pointer(_)), ) { - Ok(val) => bx.scalar_to_backend(val, s, ty), + Ok(val) => { + if let abi::Primitive::Pointer(_) = s.primitive() { + let mut schema = bx.sess().pointer_authentication_functions(); + if let Some(schema) = schema.as_mut() + && let Some(discr) = + ptrauth_discriminators.as_ref().and_then(|m| m.get(&local_offset)) + { + schema.constant_discriminator = *discr as u16; + } + bx.scalar_to_backend_with_pac( + val, + s, + ty, + schema, + ptrauth_discriminators.as_ref(), + ) + } else { + bx.scalar_to_backend(val, s, ty) + } + } Err(_) => bx.const_poison(ty), } }; @@ -268,7 +305,8 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { BackendRepr::Scalar(s @ abi::Scalar::Initialized { .. }) => { let size = s.size(bx); assert_eq!(size, layout.size, "abi::Scalar size does not match layout size"); - let val = read_scalar(offset, size, s, bx.immediate_backend_type(layout)); + let val = + read_scalar(offset, Size::ZERO, size, s, bx.immediate_backend_type(layout)); OperandRef { val: OperandValue::Immediate(val), layout, move_annotation: None } } BackendRepr::ScalarPair { @@ -281,12 +319,14 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { assert!(alloc_b_offset.bytes() > 0); let a_val = read_scalar( offset, + Size::ZERO, a_size, a, bx.scalar_pair_element_backend_type(layout, 0, true), ); let b_val = read_scalar( alloc_b_offset, + local_b_offset, b_size, b, bx.scalar_pair_element_backend_type(layout, 1, true), @@ -295,26 +335,13 @@ impl<'a, 'tcx, V: CodegenObject> OperandRef<'tcx, V> { } _ if layout.is_zst() => OperandRef::zero_sized(layout), _ => { - let ptrauth_discriminators = - if bx.tcx().sess.pointer_authentication_fn_ptr_type_discrimination() { - Some(ptrauth_collect_fn_ptr_discriminators( - bx.tcx(), - bx.typing_env(), - layout.ty, - )) - } else { - None - }; - - // Neither a scalar nor scalar pair. Load from a place + // Neither a scalar nor scalar pair. Load from a place. let base_addr = bx.static_addr_of(alloc, None, ptrauth_discriminators.as_ref()); - let llval = bx.const_ptr_byte_offset(base_addr, offset); bx.load_operand(PlaceRef::new_sized(llval, layout)) } } } - /// Asserts that this operand refers to a scalar and returns /// a reference to its value. pub fn immediate(self) -> V { diff --git a/compiler/rustc_middle/src/ptrauth/discriminator.rs b/compiler/rustc_middle/src/ptrauth/discriminator.rs index 65310ea0de427..d94d6ce644d51 100644 --- a/compiler/rustc_middle/src/ptrauth/discriminator.rs +++ b/compiler/rustc_middle/src/ptrauth/discriminator.rs @@ -63,7 +63,8 @@ //! - `ptrauth_collect_fn_ptr_discriminators` //! //! - Low-level API -//! - `FnPtrTypeDiscriminatorInput` +//! - `FnPtrTypeDiscriminatorInput` - canonical function signature input for +//! discriminator computation; exposes the function ABI through `abi()`. //! - `compute_fn_ptr_type_discriminator` //! //! - Layout traversal @@ -149,15 +150,22 @@ impl<'tcx> FnPtrDiscriminatorSource<'tcx> for Ty<'tcx> { /// 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)) + let typing_env = ty::TypingEnv::fully_monomorphized(); + + match self.ty(tcx, typing_env).kind() { + ty::FnDef(def_id, args) => { + let sig = tcx + .instantiate_and_normalize_erasing_regions( + args.skip_binder(), + typing_env, + tcx.fn_sig(*def_id), + ) + .skip_binder(); + Some(FnPtrTypeDiscriminatorInput::from_sig(sig)) + } + // Closures, coroutines, etc. are never called via an `extern "C"` function pointer. + _ => None, + } } } /// Enables discriminator computation directly from instantiated function @@ -231,6 +239,10 @@ pub struct FnPtrTypeDiscriminatorInput<'tcx> { } impl<'tcx> FnPtrTypeDiscriminatorInput<'tcx> { + pub fn abi(&self) -> ExternAbi { + self.abi + } + fn from_sig(sig: ty::FnSig<'tcx>) -> Self { FnPtrTypeDiscriminatorInput { inputs: sig.inputs(), diff --git a/compiler/rustc_middle/src/ty/vtable.rs b/compiler/rustc_middle/src/ty/vtable.rs index 95176361eac3e..b5ae8e7292a5b 100644 --- a/compiler/rustc_middle/src/ty/vtable.rs +++ b/compiler/rustc_middle/src/ty/vtable.rs @@ -7,6 +7,7 @@ use rustc_type_ir::elaborate; use crate::mir::interpret::{ AllocId, AllocInit, Allocation, CTFE_ALLOC_SALT, Pointer, Scalar, alloc_range, }; +use crate::ptrauth::discriminator::FnPtrDiscriminatorSource; use crate::ty::{self, Instance, TraitRef, Ty, TyCtxt}; #[derive(Clone, Copy, PartialEq, StableHash)] @@ -86,18 +87,12 @@ pub(super) fn vtable_allocation_provider<'tcx>( key: (Ty<'tcx>, Option>), ) -> AllocId { let ptrauth_assert_not_c_abi_fn_ptr = |instance: ty::Instance<'tcx>| { - if tcx.sess.pointer_authentication_fn_ptr_type_discrimination() { - let sig = tcx - .instantiate_and_normalize_erasing_regions( - instance.args, - ty::TypingEnv::fully_monomorphized(), - tcx.fn_sig(instance.def_id()), - ) - .skip_binder(); - + if tcx.sess.pointer_authentication_fn_ptr_type_discrimination() + && let Some(input) = instance.discriminator_input(tcx) + { assert!( !matches!( - sig.abi(), + input.abi(), rustc_abi::ExternAbi::C { .. } | rustc_abi::ExternAbi::System { .. } ), "vtable entry unexpectedly has a C ABI function pointer type: {:?}", From c4fbf60b401a254e788a767b61c49de5073b502a Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Fri, 10 Jul 2026 13:20:29 +0000 Subject: [PATCH 08/12] [PAC] Function pointer type discrimination for transmutes Implement pointer authentication resigning for function pointer transmutes that differ in their discriminators. Resigning only happens for function pointers (their transparent wrappers and Option). Aggregates (even those containing function pointer members) are deliberately kept as opaque values with no resigning. --- compiler/rustc_codegen_ssa/src/mir/rvalue.rs | 318 ++++++++++++++++++- 1 file changed, 306 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index 344a4834862e4..1f75cfcf99019 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -1,8 +1,11 @@ use std::assert_matches; 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::{ + ptrauth_clone_discriminated_schema_for, ptrauth_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}; @@ -17,6 +20,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, @@ -91,6 +101,236 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { true } + /// Lowers a transmute of an SSA operand while preserving pointer authentication semantics. + /// When the source and destination are both, or transparently wrap, function pointer types + /// with different type discriminators, the resulting pointer is re-signed. + /// + /// Aggregates that contain a function pointer field are intentionally treated as an ordinary + /// opaque-values, for which no pointer resigning is performed. + fn ptrauth_codegen_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 src_semantic = self.ptrauth_canonicalize_fn_ptr_layout(operand.layout); + let dst_semantic = self.ptrauth_canonicalize_fn_ptr_layout(cast); + + // Neither side is a function pointer - early return. + if src_semantic.is_none() && dst_semantic.is_none() { + return val; + } + + let info = TransmuteInfo { + src_ty: src_semantic.map_or(operand.layout.ty, |(ty, _)| ty), + dst_ty: dst_semantic.map_or(cast.ty, |(ty, _)| ty), + }; + + let nullable = src_semantic.is_some_and(|(_, n)| n) || dst_semantic.is_some_and(|(_, n)| n); + + let ptr = if nullable { + self.ptrauth_resign_transmuted_nullable_fn_ptr(bx, ptr, info) + } else { + self.ptrauth_resign_transmuted_fn_ptr(bx, ptr, info) + }; + + OperandValue::Immediate(ptr) + } + + /// Applies pointer-authentication type discriminator correction for a function pointer value + /// being transmuted between two types. + /// + /// If the source and destination types have different type discriminator values, the pointer + /// must be resigned using `llvm.ptrauth.resign` intrinsic. + /// + /// A discriminator value of `0` is used to represent non-function-pointer + /// or "raw pointer" 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 type discriminator. + /// + /// `TransmuteInfo` carries the source and destination types used to compute type + /// discriminators. These may differ from the original operand layouts after transparent + /// wrapper or nullable normalization. + fn ptrauth_resign_transmuted_fn_ptr( + &mut self, + bx: &mut Bx, + val: Bx::Value, + info: TransmuteInfo<'tcx>, + ) -> Bx::Value { + // Resigning can only happen in the context of function pointer type discrimination. + assert!(self.cx.tcx().sess.pointer_authentication_fn_ptr_type_discrimination()); + + let tcx = bx.tcx(); + + let src_disc = ptrauth_compute_fn_ptr_type_discriminator_for(tcx, info.src_ty).unwrap_or(0); + let dst_disc = ptrauth_compute_fn_ptr_type_discriminator_for(tcx, info.dst_ty).unwrap_or(0); + + if src_disc == dst_disc { + return val; + } + + 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()) + } + + /// Resigns a nullable function pointer. + /// + /// `llvm.ptrauth.resign` performs an authenticate-and-resign operation on an already signed + /// pointer. A null function pointer carries no authentication signature, so it must bypass the + /// intrinsic and remain null. + fn ptrauth_resign_transmuted_nullable_fn_ptr( + &mut self, + bx: &mut Bx, + ptr: Bx::Value, + info: TransmuteInfo<'tcx>, + ) -> Bx::Value { + let ptrtoint = bx.ptrtoint(ptr, bx.type_isize()); + + // Fast path for compile time known null value. + if bx.const_to_opt_u128(ptrtoint, false) == Some(0) { + return bx.const_null(bx.type_ptr()); + } + + let pointer_align = bx.tcx().data_layout.pointer_align().abi; + let pointer_size = bx.tcx().data_layout.pointer_size(); + let result = bx.alloca(pointer_size, pointer_align); + + let null_bb = bx.append_sibling_block("ptrauth.null"); + let resign_bb = bx.append_sibling_block("ptrauth.resign"); + let end_bb = bx.append_sibling_block("ptrauth.end"); + + let is_null = bx.icmp(IntPredicate::IntEQ, ptrtoint, bx.const_usize(0)); + bx.cond_br(is_null, null_bb, resign_bb); + + bx.switch_to_block(null_bb); + bx.store(bx.const_null(bx.type_ptr()), result, pointer_align); + bx.br(end_bb); + + bx.switch_to_block(resign_bb); + let resigned = self.ptrauth_resign_transmuted_fn_ptr(bx, ptr, info); + bx.store(resigned, result, pointer_align); + bx.br(end_bb); + + bx.switch_to_block(end_bb); + bx.load(bx.type_ptr(), result, pointer_align) + } + + /// Returns the underlying function pointer type represented by `layout`. + /// + /// Transparent wrappers and `Option` are peeled until either a function pointer is reached + /// or a non-wrapper type is encountered. + /// + /// The returned boolean indicates whether an `Option` wrapper was seen, meaning the + /// function pointer uses a nullable representation. + fn ptrauth_canonicalize_fn_ptr_layout( + &self, + mut layout: TyAndLayout<'tcx>, + ) -> Option<(Ty<'tcx>, bool)> { + let mut nullable = false; + loop { + match layout.ty.kind() { + ty::FnPtr(..) | ty::FnDef(..) => { + return Some((layout.ty, nullable)); + } + + ty::Adt(def, _) if def.repr().transparent() => { + let Some((_, field_layout)) = layout.non_1zst_field(self.cx) else { + // Every field is a ZST - early exit. + return None; + }; + layout = field_layout; + } + + ty::Adt(def, args) + if self.cx.tcx().lang_items().option_type() == Some(def.did()) + // Only nullable-peel if `Option` genuinely niche-optimized to a single + // scalar, rejecting `Option>`. + && matches!(layout.backend_repr, abi::BackendRepr::Scalar(_)) => + { + nullable = true; + layout = self.cx.layout_of(args.type_at(0)); + } + + _ => return None, + } + } + } + + /// Applies pointer-authentication resign for a function pointer transmute and stores the + /// resigned value into the destination place. + /// + /// The source operand may be an SSA immediate, or may already reside in memory (its caller in + /// `ptrauth_codegen_transmute_place` receives the source as a place, not a register). + fn ptrauth_resign_fn_ptr( + &mut self, + bx: &mut Bx, + src: OperandRef<'tcx, Bx::Value>, + dst: PlaceRef<'tcx, Bx::Value>, + src_ty: Ty<'tcx>, + dst_ty: Ty<'tcx>, + nullable: bool, + ) { + let val = match src.val { + OperandValue::Immediate(v) => v, + + OperandValue::Ref(place) => bx.load_operand(place.with_type(src.layout)).immediate(), + + _ => { + bug!( + "unexpected operand representation for function pointer transmute: {:?}", + src.val + ); + } + }; + + let info = TransmuteInfo { src_ty, dst_ty }; + + let val = if nullable { + self.ptrauth_resign_transmuted_nullable_fn_ptr(bx, val, info) + } else { + self.ptrauth_resign_transmuted_fn_ptr(bx, val, info) + }; + + OperandRef { val: OperandValue::Immediate(val), layout: dst.layout, move_annotation: None } + .store_with_annotation(bx, dst); + } + + /// Applies pointer-authentication type discriminator correction when transmuting into a + /// memory-backed place. + /// + /// Resigning only happens when the transmute is directly between (or through a transparent + /// wrapper or `Option` around) extern "C" function pointer types. A transmute of an aggregate + /// that contains a function pointer field is deliberately lowered as an ordinary copy, with + /// no resigning. This matches how Clang lowers the analogous C code: a struct-level pointer + /// cast or struct copy does not re-sign a nested function-pointer field, and a real type + /// mismatch is instead caught by authentication failing at the point the pointer is actually + /// called. + fn ptrauth_codegen_transmute_place( + &mut self, + bx: &mut Bx, + src: OperandRef<'tcx, Bx::Value>, + dst: PlaceRef<'tcx, Bx::Value>, + ) { + if let (Some((src_ty, src_nullable)), Some((dst_ty, dst_nullable))) = ( + self.ptrauth_canonicalize_fn_ptr_layout(src.layout), + self.ptrauth_canonicalize_fn_ptr_layout(dst.layout), + ) { + self.ptrauth_resign_fn_ptr(bx, src, dst, src_ty, dst_ty, src_nullable || dst_nullable); + return; + } + + src.store_with_annotation(bx, dst.val.with_type(src.layout)); + } + fn is_entirely_uninit_const(&self, operand: &mir::Operand<'tcx>) -> bool { let mir::Operand::Constant(const_op) = operand else { return false }; self.eval_mir_constant(const_op).all_bytes_uninit(self.cx.tcx()) @@ -184,8 +424,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.ptrauth_codegen_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); } @@ -322,7 +579,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.ptrauth_codegen_transmute_place(bx, src, dst); + } else { + src.store_with_annotation(bx, dst.val.with_type(src.layout)); + } } } @@ -336,6 +598,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() { @@ -521,12 +793,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() { + ptrauth_clone_discriminated_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), } @@ -540,10 +818,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(), + /* ptrauth_schema */ None, ), ) } @@ -620,7 +908,11 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { }) } mir::CastKind::Transmute | mir::CastKind::BoxDerefTransmute | mir::CastKind::Subtype => { - self.codegen_transmute_operand(bx, operand, cast) + if self.cx.tcx().sess.pointer_authentication_fn_ptr_type_discrimination() { + self.ptrauth_codegen_transmute_operand(bx, operand, cast) + } else { + self.codegen_transmute_operand(bx, operand, cast) + } } }; OperandRef { val, layout: cast, move_annotation: None } @@ -765,8 +1057,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()); + // needs_thread_local_shim implies Windows/MSVC, for which pointer + // authentication is not yet supported. + assert!(!self.cx.tcx().sess.pointer_authentication()); + let fn_ptr = bx.get_fn_addr(instance, /* ptrauth_schema */ 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() { From 0527207daa37ddacf20de55f5cfe76f0ec20b26e Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Fri, 10 Jul 2026 13:26:56 +0000 Subject: [PATCH 09/12] [PAC] Propagate function pointer type discrimination through `get_fn_addr` call sites Fill in function pointer type discriminators logic across remaining `get_fn_addr` call sites and explicitly avoid applying it where discrimination is not meaningful. Some uses of `get_fn_addr` are intentionally left unsigned, including the EH personality function, entry wrappers, and compiler-generated Rust ABI shims. --- compiler/rustc_codegen_llvm/src/context.rs | 14 +++++- compiler/rustc_codegen_ssa/src/base.rs | 28 +++++++++-- compiler/rustc_codegen_ssa/src/common.rs | 20 ++++++-- compiler/rustc_codegen_ssa/src/mir/block.rs | 54 +++++++++++++++------ 4 files changed, 91 insertions(+), 25 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index c136fb6bbde8a..5456aa3090197 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -998,6 +998,18 @@ 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. + // It's backend's responsibility to apply ABI-specific personality signing + // when emitting the pointer in the object file. Some(def_id) if name.is_none() => self.get_fn_addr( ty::Instance::expect_resolve( tcx, @@ -1006,7 +1018,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 c870d1694d068..4aa858a5f3032 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -4,7 +4,7 @@ use std::time::{Duration, Instant}; use std::{cmp, iter}; use itertools::Itertools; -use rustc_abi::FIRST_VARIANT; +use rustc_abi::{ExternAbi, FIRST_VARIANT}; use rustc_ast::expand::allocator::{ ALLOC_ERROR_HANDLER, ALLOCATOR_METHODS, AllocatorKind, AllocatorMethod, AllocatorMethodInput, AllocatorTy, @@ -516,8 +516,18 @@ 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. + assert!( + !matches!( + cx.tcx().fn_sig(main_def_id).skip_binder().abi(), + ExternAbi::C { .. } | ExternAbi::System { .. } + ), + "entry wrapper assumes Rust ABI" + ); + let main_llfn = cx.get_fn_addr(instance, /* pointer_auth_schema */ None); let entry_fn = create_entry_fn::(cx, main_llfn, main_def_id, entry_type); return Some(entry_fn); @@ -578,8 +588,16 @@ 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()); + // Start instance doesn't require signing, as it uses Rust ABI, hence pass `None` to + // `get_fn_addr`. + assert!( + !matches!( + cx.tcx().fn_sig(start_instance.def_id()).skip_binder().abi(), + ExternAbi::C { .. } | ExternAbi::System { .. } + ), + "LangItem::Start unexpectedly uses the C/System ABI", + ); + 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 3ce0175d864ea..3254644796861 100644 --- a/compiler/rustc_codegen_ssa/src/common.rs +++ b/compiler/rustc_codegen_ssa/src/common.rs @@ -4,6 +4,7 @@ use rustc_crate_store::{DllCallingConvention, DllImport, DllImportSymbolType}; use rustc_hir::attrs::PeImportNameType; use rustc_hir::attrs::lang_items::LangItem; use rustc_middle::mir::interpret::{GlobalAlloc, PointerArithmetic, Scalar}; +use rustc_middle::ptrauth::ptrauth_clone_discriminated_schema_for; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::ty::{self, Instance, ScalarInt, TyCtxt}; use rustc_middle::{bug, span_bug}; @@ -118,11 +119,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. + ptrauth_clone_discriminated_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 afd9a88784c2f..a805c193707b2 100644 --- a/compiler/rustc_codegen_ssa/src/mir/block.rs +++ b/compiler/rustc_codegen_ssa/src/mir/block.rs @@ -13,6 +13,7 @@ use rustc_hir::attrs::lang_items::LangItem; use rustc_lint_defs::builtin::TAIL_CALL_TRACK_CALLER; use rustc_middle::mir::interpret::{CTFE_ALLOC_SALT, Scalar}; use rustc_middle::mir::{self, AssertKind, InlineAsmMacro, SwitchTargets, UnwindTerminateReason}; +use rustc_middle::ptrauth::ptrauth_clone_discriminated_schema_for; use rustc_middle::ty::layout::{HasTyCtxt, LayoutOf, TyAndLayout, ValidityRequirement}; use rustc_middle::ty::print::{with_no_trimmed_paths, with_no_visible_paths}; use rustc_middle::ty::{self, Instance, Ty, TypeVisitableExt}; @@ -692,12 +693,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() { + ptrauth_clone_discriminated_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 @@ -1109,14 +1121,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() { + ptrauth_clone_discriminated_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), } @@ -1430,7 +1446,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() { + ptrauth_clone_discriminated_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"), From a8625a16abd6af52c758541cc1b5b5394d974ec4 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Fri, 10 Jul 2026 13:36:07 +0000 Subject: [PATCH 10/12] [PAC] Minicore updates to support fn ptr type discriminator tests --- tests/auxiliary/minicore.rs | 55 +++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index 04564049dbed2..44c1fb5c01231 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -113,6 +113,8 @@ impl Copy for [T; N] {} pub struct PhantomData; impl Copy for PhantomData {} +#[rustc_diagnostic_item = "Option"] +#[lang = "Option"] pub enum Option { None, Some(T), @@ -250,11 +252,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 } } @@ -301,18 +310,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) {} @@ -349,7 +370,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); } @@ -360,7 +381,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] @@ -379,6 +400,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 { @@ -406,9 +436,10 @@ pub mod num { #[lang = "c_void"] #[repr(u8)] +#[allow(non_camel_case_types)] pub enum c_void { - __variant1, - __variant2, + Variant1, + Variant2, } #[rustc_builtin_macro(pattern_type)] From 6ebd6fdb35df43d6b58c4bce383a4db2129d9a94 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Fri, 10 Jul 2026 13:58:20 +0000 Subject: [PATCH 11/12] [PAC] Function pointer type discrimination tests And update to the main pauthtest document: * list new tests * remove the need for patching `libc` as the changes to it already went in. Unfortunately `cc-rs` is held back by `compiler/rustc_llvm/Cargo.toml` which pins to an old version: `cc = "=1.2.16"` --- .../aarch64-unknown-linux-pauthtest.md | 53 +- .../pauth/pauth-attr-special-funcs.rs | 5 +- .../pauth-extern-c-direct-indirect-call.rs | 2 +- .../pauth/pauth-extern-weak-global.rs | 2 +- ...n-ptr-type-discrimination-deeply-nested.rs | 105 ++++ ...auth-fn-ptr-type-discrimination-encoder.rs | 531 ++++++++++++++++++ ...-type-discrimination-fn-ptr-return-type.rs | 44 ++ ...ptr-type-discrimination-option-callback.rs | 84 +++ ...n-ptr-type-discrimination-option-return.rs | 43 ++ ...pauth-fn-ptr-type-discrimination-option.rs | 36 ++ ...r-type-discrimination-recursive-statics.rs | 164 ++++++ ...fn-ptr-type-discrimination-running-test.rs | 304 ++++++++++ ...h-fn-ptr-type-discrimination-rust-array.rs | 66 +++ .../pauth-fn-ptr-type-discrimination-simd.rs | 141 +++++ ...n-ptr-type-discrimination-static-allocs.rs | 129 +++++ ...-ptr-type-discrimination-struct-members.rs | 419 ++++++++++++++ ...-fn-ptr-type-discrimination-struct-name.rs | 96 ++++ ...ptr-type-discrimination-transmute-basic.rs | 132 +++++ ...tr-type-discrimination-transmute-option.rs | 247 ++++++++ ...n-ptr-type-discrimination-transmute-raw.rs | 57 ++ ...n-ptr-type-discrimination-transmute-zst.rs | 90 +++ tests/codegen-llvm/pauth/pauth-init-fini.rs | 12 +- .../before_instcombine.check | 4 + .../before_instcombine_ty_disc.check | 4 + .../pauth-drop-terminator/full_ir.check | 3 + tests/run-make/pauth-drop-terminator/main.rs | 22 + tests/run-make/pauth-drop-terminator/rmake.rs | 61 ++ 27 files changed, 2832 insertions(+), 24 deletions(-) create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-deeply-nested.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-encoder.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-fn-ptr-return-type.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-callback.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-return.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-recursive-statics.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-running-test.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-rust-array.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-simd.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-static-allocs.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-struct-members.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-struct-name.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-basic.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-option.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-raw.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-zst.rs create mode 100644 tests/run-make/pauth-drop-terminator/before_instcombine.check create mode 100644 tests/run-make/pauth-drop-terminator/before_instcombine_ty_disc.check create mode 100644 tests/run-make/pauth-drop-terminator/full_ir.check create mode 100644 tests/run-make/pauth-drop-terminator/main.rs create mode 100644 tests/run-make/pauth-drop-terminator/rmake.rs 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 1118145a2097c..4e08a9be96643 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,20 @@ 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-recursive-statics.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-static-allocs.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 +475,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 * Unit tests for siphash function: * compiler/rustc_middle/src/ptrauth/llvm_siphash/tests.rs @@ -495,13 +497,26 @@ 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-recursive-statics.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-static-allocs.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/codegen-llvm/pauth/pauth-attr-special-funcs.rs b/tests/codegen-llvm/pauth/pauth-attr-special-funcs.rs index 2751494b9de7a..e7077944e3e9e 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({{.*}}) unnamed_addr [[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..c278eb4eec659 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-deeply-nested.rs @@ -0,0 +1,105 @@ +// 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 + +// Make sure that the compiler can see through chains of nested structs, both when used as globals, +// arguments and returns. + +#![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..62614c141d8d3 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-fn-ptr-return-type.rs @@ -0,0 +1,44 @@ +// 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 for functions returning a function +// pointer themselves. + +#![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..685157131ae7b --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-callback.rs @@ -0,0 +1,84 @@ +// 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 + +// Emulate NULL-able function argument with Option. Make sure that Option is treated +// as function pointer - encoded as P. + +#![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..b0a1b77948b49 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-return.rs @@ -0,0 +1,43 @@ +// 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 for optional returns. + +#![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..c137421b7a0d5 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option.rs @@ -0,0 +1,36 @@ +// 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 for optional variables. + +#![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-recursive-statics.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-recursive-statics.rs new file mode 100644 index 0000000000000..e07e0babbd880 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-recursive-statics.rs @@ -0,0 +1,164 @@ +// 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 + +// Tests function-pointer type discriminator generation for pointer authentication across nested +// static allocations, wrapper references, and padded structs. + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] +extern crate minicore; +use minicore::Sync; +use minicore::hint::black_box; + +extern "C" fn foo(_: f32) {} +extern "C" fn bar(_: i32) {} + +impl Sync for InnerA {} +impl Sync for InnerB {} +impl Sync for Outer {} + +// DISC-DAG: @[[INNER_A:[^ ]*T_INNER_A]] = constant ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 21613) +// DISC-DAG: @[[INNER_B:[^ ]*T_INNER_B]] = constant ptr ptrauth (ptr @{{.*}}bar, i32 0, i64 2712) +// NO_DISC-DAG: @[[INNER_A:[^ ]*T_INNER_A]] = constant ptr ptrauth (ptr @{{.*}}foo, i32 0) +// NO_DISC-DAG: @[[INNER_B:[^ ]*T_INNER_B]] = constant ptr ptrauth (ptr @{{.*}}bar, i32 0) +// CHECK-DAG: @[[T_OUTER:[^ ]*T_OUTER]] = constant ptr @[[ALLOC:alloc_[0-9a-f]+]] +// CHECK-DAG: @[[ALLOC]] = private unnamed_addr constant <{ ptr, ptr }> <{ ptr @[[INNER_A]], ptr @[[INNER_B]] }> + +// DISC-DAG: @[[HAS_FN_PTR:[^ ]*T_HAS_FN_PTR]] = constant ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 21613) +// NO_DISC-DAG: @[[HAS_FN_PTR:[^ ]*T_HAS_FN_PTR]] = constant ptr ptrauth (ptr @{{.*}}foo, i32 0) +// CHECK-DAG: @[[WRAPPER_1:[^ ]*T_WRAPPER_1]] = constant <{ [8 x i8], ptr }> <{ [8 x i8] {{.*}}, ptr @[[HAS_FN_PTR]] }> +// CHECK-DAG: @[[WRAPPER_2_ALLOC:alloc_[0-9a-f]+]] = private unnamed_addr constant <{ [4 x i8], [4 x i8], ptr }> <{ [4 x i8] {{.*}}, [4 x i8] {{.*}}, ptr @[[WRAPPER_1]] }> +// CHECK-DAG: @[[WRAPPER_2:[^ ]*T_WRAPPER_2]] = constant ptr @[[WRAPPER_2_ALLOC]] + +// DISC-DAG: @[[PADDED_INNER:[^ ]*T_PADDED_INNER]] = constant <{ [2 x i8], [6 x i8], ptr, [1 x i8], [7 x i8], ptr }> <{ [2 x i8] {{.*}}, [6 x i8] {{.*}}, ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 21613), [1 x i8] {{.*}}, [7 x i8] {{.*}}, ptr ptrauth (ptr @{{.*}}bar, i32 0, i64 2712) }> +// NO_DISC-DAG: @[[PADDED_INNER:[^ ]*T_PADDED_INNER]] = constant <{ [2 x i8], [6 x i8], ptr, [1 x i8], [7 x i8], ptr }> <{ [2 x i8] {{.*}}, [6 x i8] {{.*}}, ptr ptrauth (ptr @{{.*}}foo, i32 0), [1 x i8] {{.*}}, [7 x i8] {{.*}}, ptr ptrauth (ptr @{{.*}}bar, i32 0) }> +// CHECK-DAG: @[[PADDED_OUTER:[^ ]*T_PADDED_OUTER]] = constant ptr @[[PADDED_ALLOC:alloc_[0-9a-f]+]] +// CHECK-DAG: @[[PADDED_ALLOC]] = private unnamed_addr constant <{ [1 x i8], [7 x i8], ptr, [4 x i8], [4 x i8] }> <{ [1 x i8] {{.*}}, [7 x i8] {{.*}}, ptr @[[PADDED_INNER]], [4 x i8] {{.*}}, [4 x i8] {{.*}} }> + +#[repr(C)] +struct InnerA { + f: extern "C" fn(f32), +} + +#[repr(C)] +struct InnerB { + g: extern "C" fn(i32), +} + +#[repr(C)] +struct Outer { + a: &'static InnerA, + b: &'static InnerB, +} + +#[used] +static T_INNER_A: InnerA = InnerA { f: foo }; + +#[used] +static T_INNER_B: InnerB = InnerB { g: bar }; + +#[used] +static T_OUTER: &Outer = &Outer { a: &T_INNER_A, b: &T_INNER_B }; + +// CHECK-LABEL: test_1_two_inners +pub fn test_1_two_inners() { + // DISC: call void {{.*}}(float {{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 21613) ] + // NO_DISC: call void {{.*}}(float {{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + black_box((T_INNER_A.f))(0.12f32); + + // DISC: call void {{.*}}(i32 {{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 2712) ] + // NO_DISC: call void {{.*}}(i32 {{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + black_box((T_INNER_B.g))(22); + + // DISC: call void {{.*}}(float {{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 21613) ] + // NO_DISC: call void {{.*}}(float {{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + black_box((T_OUTER.a.f))(0.32f32); + + // DISC: call void {{.*}}(i32 {{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 2712) ] + // NO_DISC: call void {{.*}}(i32 {{.*}}) #{{.*}} [ "ptrauth"(i32 0, i64 0) ] + black_box((T_OUTER.b.g))(42); +} + +impl Sync for HasFnPtr {} +impl Sync for Wrapper1 {} +impl Sync for Wrapper2 {} + +#[repr(C)] +struct HasFnPtr { + f: extern "C" fn(_: f32), +} + +#[repr(C)] +struct Wrapper1 { + pad: u64, + p: &'static HasFnPtr, +} + +#[repr(C)] +struct Wrapper2 { + pad: u32, + w: &'static Wrapper1, +} + +#[used] +static T_HAS_FN_PTR: HasFnPtr = HasFnPtr { f: foo }; + +#[used] +static T_WRAPPER_1: Wrapper1 = Wrapper1 { pad: 42, p: &T_HAS_FN_PTR }; + +#[used] +static T_WRAPPER_2: &'static Wrapper2 = &Wrapper2 { pad: 7, w: &T_WRAPPER_1 }; + +pub fn test_2_nested_wrappers() { + // DISC: call void {{.*}}(float {{.*}}){{.*}}"ptrauth"(i32 0, i64 21613) + // NO_DISC: call void {{.*}}(float {{.*}}){{.*}}"ptrauth"(i32 0, i64 0) + black_box((T_HAS_FN_PTR.f))(0.22f32); + // DISC: call void {{.*}}(float {{.*}}){{.*}}"ptrauth"(i32 0, i64 21613) + // NO_DISC: call void {{.*}}(float {{.*}}){{.*}}"ptrauth"(i32 0, i64 0) + black_box((T_WRAPPER_1.p.f))(0.32f32); + // DISC: call void {{.*}}(float {{.*}}){{.*}}"ptrauth"(i32 0, i64 21613) + // NO_DISC: call void {{.*}}(float {{.*}}){{.*}}"ptrauth"(i32 0, i64 0) + black_box((T_WRAPPER_2.w.p.f))(0.42f32); +} + +impl Sync for PaddedInner {} +impl Sync for PaddedOuter {} + +#[repr(C)] +struct PaddedInner { + pad: u16, + f: extern "C" fn(f32), + pad_2: u8, + f_2: extern "C" fn(i32), +} + +#[repr(C)] +struct PaddedOuter { + pad_3: u8, + inner: &'static PaddedInner, + pad_4: u32, +} + +#[used] +static T_PADDED_INNER: PaddedInner = PaddedInner { pad: 0, f: foo, pad_2: 1, f_2: bar }; + +#[used] +static T_PADDED_OUTER: &PaddedOuter = &PaddedOuter { pad_3: 2, pad_4: 3, inner: &T_PADDED_INNER }; + +// CHECK-LABEL: test_3_padded_structs +pub fn test_3_padded_structs() { + // DISC: call void {{.*}}(float {{.*}}){{.*}}"ptrauth"(i32 0, i64 21613) + // NO_DISC: call void {{.*}}(float {{.*}}){{.*}}"ptrauth"(i32 0, i64 0) + black_box((T_PADDED_OUTER.inner.f))(0.32f32); + // DISC: call void {{.*}}(i32 {{.*}}){{.*}}"ptrauth"(i32 0, i64 2712) + // NO_DISC: call void {{.*}}(i32 {{.*}}){{.*}}"ptrauth"(i32 0, i64 0) + black_box((T_PADDED_OUTER.inner.f_2))(42); +} 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..7225f395acf84 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-rust-array.rs @@ -0,0 +1,66 @@ +// 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 + +// Make sure that signing/auth happens for every element of an array. + +#![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..84ce3656a01ef --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-simd.rs @@ -0,0 +1,141 @@ +// 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 + +// 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])); + } +} diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-static-allocs.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-static-allocs.rs new file mode 100644 index 0000000000000..97f9fb7d268ce --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-static-allocs.rs @@ -0,0 +1,129 @@ +// 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 pointer authentication generation in compile-time constants. Covers standalone function +// pointer constants, promoted temporaries, immutable and mutable statics, arrays of function +// pointers, and mixed structs containing function pointers with different signatures and +// discriminators. + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] +extern crate minicore; +use minicore::Sync; + +// DISC-DAG: @[[SCALAR_ALLOC:alloc_[0-9a-f]+]] = private unnamed_addr constant ptr ptrauth (ptr {{.*}}foo{{.*}}, i32 0, i64 18983) +// NO_DISC-DAG: @[[SCALAR_ALLOC:alloc_[0-9a-f]+]] = private unnamed_addr constant ptr ptrauth (ptr {{.*}}foo{{.*}}, i32 0) + +// DISC-DAG: @[[MIXED_ALLOC:alloc_[0-9a-f]+]] = private unnamed_addr constant <{ ptr, ptr, ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 18983), ptr ptrauth (ptr @{{.*}}foo_i32, i32 0, i64 2712), ptr ptrauth (ptr @{{.*}}foo_i64, i32 0, i64 2712), ptr ptrauth (ptr @{{.*}}foo_ret, i32 0, i64 42271) }> +// NO_DISC-DAG: @[[MIXED_ALLOC:alloc_[0-9a-f]+]] = private unnamed_addr constant <{ ptr, ptr, ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}foo, i32 0), ptr ptrauth (ptr @{{.*}}foo_i32, i32 0), ptr ptrauth (ptr @{{.*}}foo_i64, i32 0), ptr ptrauth (ptr @{{.*}}foo_ret, i32 0) }> + +// DISC-DAG: @[[STATIC_MIXED:.*STATIC_MIXED]] = constant <{ ptr, ptr, ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 18983), ptr ptrauth (ptr @{{.*}}foo_i32, i32 0, i64 2712), ptr ptrauth (ptr @{{.*}}foo_i64, i32 0, i64 2712), ptr ptrauth (ptr @{{.*}}foo_ret, i32 0, i64 42271) }> +// NO_DISC-DAG: @[[STATIC_MIXED:.*STATIC_MIXED]] = constant <{ ptr, ptr, ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}foo, i32 0), ptr ptrauth (ptr @{{.*}}foo_i32, i32 0), ptr ptrauth (ptr @{{.*}}foo_i64, i32 0), ptr ptrauth (ptr @{{.*}}foo_ret, i32 0) }> + +// DISC-DAG: @[[STATIC_TABLE:.*STATIC_TABLE]] = constant <{ ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 18983), ptr ptrauth (ptr @{{.*}}bar, i32 0, i64 18983) }> +// NO_DISC-DAG: @[[STATIC_TABLE:.*STATIC_TABLE]] = constant <{ ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}foo, i32 0), ptr ptrauth (ptr @{{.*}}bar, i32 0) }> + +// DISC-DAG: @[[MUT_TABLE:.*MUT_TABLE]] = global <{ ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}foo, i32 0, i64 18983), ptr ptrauth (ptr @{{.*}}bar, i32 0, i64 18983) }> +// NO_DISC-DAG: @[[MUT_TABLE:.*MUT_TABLE]] = global <{ ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}foo, i32 0), ptr ptrauth (ptr @{{.*}}bar, i32 0) }> + +extern "C" fn foo() {} +extern "C" fn foo_i32(_: i32) {} +extern "C" fn foo_i64(_: i64) {} +extern "C" fn foo_ret() -> i32 { + 0 +} +extern "C" fn bar() {} + +const F: extern "C" fn() = foo; + +// CHECK-LABEL: test_scalar +pub fn test_scalar() { + // DISC: call void ptrauth (ptr @{{.*}}foo{{.*}}, i32 0, i64 18983)() + // NO_DISC: call void ptrauth (ptr @{{.*}}foo{{.*}}, i32 0)() + let p: &'static extern "C" fn() = &F; + p(); +} + +#[repr(C)] +struct Mixed { + a: extern "C" fn(), + b: extern "C" fn(i32), + c: extern "C" fn(i64), + d: extern "C" fn() -> i32, +} + +impl Sync for Mixed {} + +// CHECK-LABEL: test_promoted_mixed +pub fn test_promoted_mixed() { + // CHECK: %{{.*}} = load ptr, ptr @[[MIXED_ALLOC]] + // DISC: call void %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 18983) ] + // NO_DISC: call void %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 0) ] + // CHECK: %{{.*}} = load ptr, ptr getelementptr inbounds (i8, ptr @[[MIXED_ALLOC]], i64 8), + // DISC: call void %{{.*}}(i32 1) #[[#]] [ "ptrauth"(i32 0, i64 2712) ] + // NO_DISC: call void %{{.*}}(i32 1) #[[#]] [ "ptrauth"(i32 0, i64 0) ] + // CHECK: %{{.*}} = load ptr, ptr getelementptr inbounds (i8, ptr @[[MIXED_ALLOC]], i64 16), + // DISC: call void %{{.*}}(i64 1) #[[#]] [ "ptrauth"(i32 0, i64 2712) ] + // NO_DISC: call void %{{.*}}(i64 1) #[[#]] [ "ptrauth"(i32 0, i64 0) ] + // CHECK: %{{.*}} = load ptr, ptr getelementptr inbounds (i8, ptr @[[MIXED_ALLOC]], i64 24), + // DISC: call i32 %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 42271) ] + // NO_DISC: call i32 %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 0) ] + let x: &'static Mixed = &Mixed { a: foo, b: foo_i32, c: foo_i64, d: foo_ret }; + + (x.a)(); + (x.b)(1); + (x.c)(1); + let _ = (x.d)(); +} + +#[used] +static STATIC_MIXED: Mixed = Mixed { a: foo, b: foo_i32, c: foo_i64, d: foo_ret }; + +// CHECK-LABEL: test_static_mixed +pub fn test_static_mixed() { + // CHECK: %{{.*}} = load ptr, ptr @[[STATIC_MIXED]] + // DISC: call void %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 18983) ] + // NO_DISC: call void %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 0) ] + // CHECK: %{{.*}} = load ptr, ptr getelementptr inbounds (i8, ptr @[[STATIC_MIXED]], i64 8), + // DISC: call void %{{.*}}(i32 1) #[[#]] [ "ptrauth"(i32 0, i64 2712) ] + // NO_DISC: call void %{{.*}}(i32 1) #[[#]] [ "ptrauth"(i32 0, i64 0) ] + (STATIC_MIXED.a)(); + (STATIC_MIXED.b)(1); +} + +#[used] +static STATIC_TABLE: [extern "C" fn(); 2] = [foo, bar]; + +// CHECK-LABEL: test_static_array +pub fn test_static_array() { + let p = &raw const STATIC_TABLE as *const extern "C" fn(); + + // CHECK: call ptr {{.*}}read_volatile{{.*}}(ptr @[[STATIC_TABLE]]) + // DISC: call void %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 18983) ] + // NO_DISC: call void %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 0) ] + unsafe { + (minicore::ptr::read_volatile(p))(); + } +} + +#[used] +static mut MUT_TABLE: [extern "C" fn(); 2] = [foo, bar]; + +// CHECK-LABEL: test_mut_static +pub unsafe fn test_mut_static() { + let p = &raw const MUT_TABLE as *const extern "C" fn(); + + // CHECK: call ptr {{.*}}read_volatile{{.*}}(ptr @[[MUT_TABLE]]) + // DISC: call void %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 18983) ] + // NO_DISC: call void %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 0) ] + (minicore::ptr::read_volatile(p))(); +} 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..f5fd6693e4513 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-struct-members.rs @@ -0,0 +1,419 @@ +// 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 + +// Check the signing of internal members of structs that are themselves function pointers. Note, +// that when transmuting structs, the resign will only happen for transparent wrappers around +// extern "C" function pointers. For all other aggregates (even though containing extern "C" +// function pointers) no resigning takes place. + +#![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) + +// DISC-DAG: @{{.*}}T_TUPLE = constant <{ ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}f, i32 0, i64 18983), ptr ptrauth (ptr @{{.*}}g, i32 0, i64 2712) }> +// NO_DISC-DAG: @{{.*}}T_TUPLE = constant <{ ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}f, i32 0), ptr ptrauth (ptr @{{.*}}g, i32 0) }> + +// DISC-DAG: @{{.*}}T_TUPLE_2 = constant <{ ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}m, i32 0, i64 21613), ptr ptrauth (ptr @{{.*}}n, i32 0, i64 7718) }> +// NO_DISC-DAG: @{{.*}}T_TUPLE_2 = constant <{ ptr, ptr }> <{ ptr ptrauth (ptr @{{.*}}m, i32 0), ptr ptrauth (ptr @{{.*}}n, 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: store ptr ptrauth (ptr @{{.*}}f, i32 0, i64 18983), ptr + // 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-NOT: call i64 @llvm.ptrauth.resign + // DISC: call void %{{.*}}(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() { + // NO_DISC-NOT: call i64 @llvm.ptrauth.resign + + 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); + +type T0 = extern "C" fn(); +type T1 = extern "C" fn(i32); + +type T2 = extern "C" fn(f32); +type T3 = extern "C" fn(f64); + +#[repr(C)] +struct TupleSrc { + pair: (T0, T1), +} + +#[repr(C)] +struct TupleDst { + pair: (T2, T3), +} + +impl Sync for TupleSrc {} +impl Sync for TupleDst {} + +extern "C" fn m(f32: f32) {} +extern "C" fn n(f64: f64) {} +#[used] +static T_TUPLE: TupleSrc = TupleSrc { pair: (f, g) }; +#[used] +static T_TUPLE_2: TupleDst = TupleDst { pair: (m, n) }; + +#[inline(never)] +// Tuple test members +// CHECK-DAG: test_8_tuple_members +pub fn test_8_tuple_members() { + let src = unsafe { ptr::read_volatile(&T_TUPLE) }; + + // NO_DISC-NOT: call i64 @llvm.ptrauth.resign + let dst = TupleDst { + // DISC: call i64 @llvm.ptrauth.resign(i64 %1, i32 0, i64 18983, i32 0, i64 21613) + // DISC: call i64 @llvm.ptrauth.resign(i64 %3, i32 0, i64 2712, i32 0, i64 7718) + pair: (unsafe { mem::transmute::(src.pair.0) }, unsafe { + mem::transmute::(src.pair.1) + }), + }; + + unsafe { + ptr::read_volatile(&dst); + } + + // DISC: call void %{{.*}}(float 4.200000e-01) #[[#]] [ "ptrauth"(i32 0, i64 21613) ] + // DISC: call void %{{.*}}(double 4.242000e-01) #[[#]] [ "ptrauth"(i32 0, i64 7718) ] + // NO_DISC: call void %{{.*}}(float 4.200000e-01) #[[#]] [ "ptrauth"(i32 0, i64 0) ] + // NO_DISC: call void %{{.*}}(double 4.242000e-01) #[[#]] [ "ptrauth"(i32 0, i64 0) ] + (dst.pair.0)(0.42f32); + (dst.pair.1)(0.4242f64); +} + +#[inline(never)] +// Tuple test +// CHECK-DAG: test_9_tuple +pub fn test_9_tuple() { + let src = TupleSrc { pair: (f, g) }; + let _ = unsafe { ptr::read_volatile(&src) }; + // DISC: store ptr ptrauth (ptr @{{.*}}f, i32 0, i64 18983) + // DISC: store ptr ptrauth (ptr @{{.*}}g, i32 0, i64 2712) + // NO_DISC: store ptr ptrauth (ptr @{{.*}}f, i32 0) + // NO_DISC: store ptr ptrauth (ptr @{{.*}}g, i32 0) + // CHECK-NOT: call i64 @llvm.ptrauth.resign + let dst: TupleDst = unsafe { mem::transmute(src) }; + + // DISC: call void %{{.*}}(float 4.200000e-01) #[[#]] [ "ptrauth"(i32 0, i64 21613) ] + // DISC: call void %{{.*}}(double 4.242000e-01) #[[#]] [ "ptrauth"(i32 0, i64 7718) ] + // NO_DISC: call void %{{.*}}(float 4.200000e-01) #[[#]] [ "ptrauth"(i32 0, i64 0) ] + // NO_DISC: call void %{{.*}}(double 4.242000e-01) #[[#]] [ "ptrauth"(i32 0, i64 0) ] + (dst.pair.0)(0.42f32); + (dst.pair.1)(0.4242f64); +} 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..47c7ca205f887 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-struct-name.rs @@ -0,0 +1,96 @@ +// 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 + +// 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-fn-ptr-type-discrimination-transmute-basic.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-basic.rs new file mode 100644 index 0000000000000..3d08181f2af5a --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-basic.rs @@ -0,0 +1,132 @@ +// 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 + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] + +extern crate minicore; + +use minicore::hint::black_box; +use minicore::mem::transmute; + +// NO_DISC-NOT: llvm.ptrauth.resign + +extern "C" fn f_i32(x: i32) -> i32 { + x +} + +extern "C" fn f_void() {} + +// CHECK-LABEL-DAG: returns_fp +extern "C" fn returns_fp() -> extern "C" fn(i32) -> i32 { + // DISC: ret ptr ptrauth (ptr @{{.*}}f_i32, i32 0, i64 2981) + // NO_DISC: ret ptr ptrauth (ptr @{{.*}}f_i32, i32 0) + f_i32 +} + +// CHECK-LABEL-DAG: takes_fp +extern "C" fn takes_fp(f: extern "C" fn()) { + // DISC: call void %f() #[[#]] [ "ptrauth"(i32 0, i64 18983) ] + // NO_DISC: call void %f() #[[#]] [ "ptrauth"(i32 0, i64 0) ] + f(); +} + +// CHECK-LABEL-DAG: @test_call_after_transmute +pub fn test_call_after_transmute() { + unsafe { + let p: extern "C" fn(i32) -> i32 = f_i32; + + // DISC: [[TRANSMUTED:%.*]] = call i64 @llvm.ptrauth.resign(i64 ptrtoint (ptr ptrauth (ptr @{{.*}}f_i32, i32 0, i64 2981) to i64), i32 0, i64 2981, i32 0, i64 28450) + // DISC: [[INTTOPTR:%.*]] = inttoptr i64 [[TRANSMUTED]] to ptr + let q: extern "C" fn(f32) -> f32 = transmute(p); + // DISC: call float [[INTTOPTR]](float 1.000000e+00) #[[#]] [ "ptrauth"(i32 0, i64 28450) ] + + // NO_DISC: call float ptrauth (ptr @{{.*}}f_i32, i32 0)(float 1.000000e+00) #[[#]] [ "ptrauth"(i32 0, i64 0) ] + q(1.0); + } +} + +// CHECK-LABEL-DAG: @test_double_transmute +pub fn test_double_transmute() { + unsafe { + // DISC: [[TRANSMUTED_1:%.*]] = call i64 @llvm.ptrauth.resign(i64 ptrtoint (ptr ptrauth (ptr @{{.*}}f_void, i32 0, i64 18983) to i64), i32 0, i64 18983, i32 0, i64 2712) + let a: extern "C" fn() = f_void; + let b: extern "C" fn(i32) = transmute(a); + // DISC: [[INTTOPTR_1:%.*]] = inttoptr i64 [[TRANSMUTED_1]] to ptr + // DISC: [[PTRTOINT:%.*]] = ptrtoint ptr [[INTTOPTR_1]] to i64 + // DISC: [[TRANSMUTED_2:%.*]] = call i64 @llvm.ptrauth.resign(i64 [[PTRTOINT]], i32 0, i64 2712, i32 0, i64 18983) + let c: extern "C" fn() = transmute(b); + // DISC: [[INTTOPTR_2:%.*]] = inttoptr i64 [[TRANSMUTED_2]] to ptr + // DISC: call void [[INTTOPTR_2]]() #[[#]] [ "ptrauth"(i32 0, i64 18983) ] + // NO_DISC: call void ptrauth (ptr @{{.*}}f_void, i32 0)() #[[#]] [ "ptrauth"(i32 0, i64 0) ] + c(); + } +} + +// CHECK-LABEL-DAG: @test_returned_fp +pub fn test_returned_fp() { + unsafe { + let p = returns_fp(); + // 34128 here is the discriminator for returns_fp. The function pointer that returns_fp + // returns is discriminated with 2981 - that value should be used as input discriminator + // for resign below. + // DISC: [[P:%.*]] = call ptr ptrauth (ptr @{{.*}}returns_fp, i32 0, i64 34128)() #[[#]] [ "ptrauth"(i32 0, i64 34128) ] + // DISC: [[INTTOPTR_1:%.*]] = ptrtoint ptr [[P]] to i64 + // DISC: [[TRANSMUTED:%.*]] = call i64 @llvm.ptrauth.resign(i64 [[INTTOPTR_1]], i32 0, i64 2981, i32 0, i64 28450) + let q: extern "C" fn(f32) -> f32 = transmute(p); + // DISC: [[INTTOPTR_2:%.*]] = inttoptr i64 [[TRANSMUTED]] to ptr + // DISC: call float [[INTTOPTR_2]](float 2.000000e+00) #[[#]] [ "ptrauth"(i32 0, i64 28450) ] + // NO_DISC: [[PTR:%.*]] = call ptr ptrauth (ptr @{{.*}}returns_fp, i32 0)() #[[#]] [ "ptrauth"(i32 0, i64 0) ] + // NO_DISC: call float [[PTR]](float 2.000000e+00) #[[#]] [ "ptrauth"(i32 0, i64 0) ] + q(2.0); + } +} + +// CHECK-LABEL-DAG: @test_argument_transmute +pub fn test_argument_transmute() { + unsafe { + // DISC: [[TRANSMUTED:%.*]] = call i64 @llvm.ptrauth.resign(i64 ptrtoint (ptr ptrauth (ptr @{{.*}}f_i32, i32 0, i64 2981) to i64), i32 0, i64 2981, i32 0, i64 18983) + let p: extern "C" fn() = transmute(f_i32 as extern "C" fn(i32) -> i32); + // DISC: [[P:%.*]] = inttoptr i64 [[TRANSMUTED]] to ptr + // DISC: call void ptrauth (ptr @{{.*}}takes_fp, i32 0, i64 10942)(ptr [[P]]) #[[#]] [ "ptrauth"(i32 0, i64 10942) ] + // NO_DISC: call void ptrauth (ptr @{{.*}}takes_fp, i32 0)(ptr ptrauth (ptr @{{.*}}f_i32, i32 0)) #[[#]] [ "ptrauth"(i32 0, i64 0) ] + takes_fp(p); + } +} + +// CHECK-LABEL-DAG: @test_identity_transmute +pub fn test_identity_transmute() { + unsafe { + let p: extern "C" fn(i32) -> i32 = f_i32; + let q: extern "C" fn(i32) -> i32 = transmute(p); + // Expect transmutes to be optimised out. + // 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) ] + q(123); + } +} + +// CHECK-LABEL-DAG: @test_mutable_reassignment +pub fn test_mutable_reassignment() { + unsafe { + // DISC: store ptr ptrauth (ptr @{{.*}}f_i32, i32 0, i64 2981) + // NO_DISC: store ptr ptrauth (ptr @{{.*}}f_i32, i32 0), ptr %p + let mut p: extern "C" fn(i32) -> i32 = f_i32; + // DISC: call i64 @llvm.ptrauth.resign(i64 %{{.*}}, i32 0, i64 2981, i32 0, i64 18983) + let q: extern "C" fn() = transmute(p); + // DISC: call i64 @llvm.ptrauth.resign(i64 %{{.*}}, i32 0, i64 18983, i32 0, i64 2981) + p = transmute(q); + // DISC: call i32 %{{.*}}(i32 456) #[[#]] [ "ptrauth"(i32 0, i64 2981) ] + // NO_DISC: call i32 %{{.*}}(i32 456) #[[#]] [ "ptrauth"(i32 0, i64 0) ] + p(456); + } +} diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-option.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-option.rs new file mode 100644 index 0000000000000..b241ba8c1c1cb --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-option.rs @@ -0,0 +1,247 @@ +// 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 + +// Make sure that rustc can correctly handle resigning Option, this makes an interesting use +// case, as options are the only way of expressing nullable pointer + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] + +extern crate minicore; + +use minicore::Option; +use minicore::Option::{None, Some}; +use minicore::mem::transmute; + +// DISC-DAG: @{{.*}}F = internal constant ptr ptrauth (ptr @{{.*}}g, i32 0, i64 2712) +static F: extern "C" fn(i32) = g; + +extern "C" fn f() {} +extern "C" fn g(_: i32) {} + +// CHECK-LABEL-DAG: test_option +pub fn test_option() { + unsafe { + // DISC: [[PTR:%.*]] = icmp eq i64 ptrtoint (ptr ptrauth (ptr @{{.*}}g, i32 0, i64 2712) to i64), 0 + // DISC: br i1 [[PTR]], label %ptrauth.null, label %ptrauth.resign + + // DISC: ptrauth.null: + // DISC: store ptr null, ptr %{{.*}} + // DISC: br label %ptrauth.end + + // DISC: ptrauth.resign: + // DISC: [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 ptrtoint (ptr ptrauth (ptr {{.*}}g, i32 0, i64 2712) to i64), i32 0, i64 2712, i32 0, i64 18983) + // DISC: [[RESIGNED_PTR:%.*]] = inttoptr i64 [[RESIGNED]] to ptr + // DISC: store ptr [[RESIGNED_PTR]], ptr %{{.*}} + + // NO_DISC: store ptr ptrauth (ptr @{{.*}}g, i32 0), ptr %{{.*}} + let p: Option = transmute(Some(g as extern "C" fn(i32))); + + // DISC: bb1: + // DISC: [[FP:%.*]] = load ptr, ptr %{{.*}} + // DISC: call void [[FP]]() #{{.*}} [ "ptrauth"(i32 0, i64 18983) ] + if let Some(fp) = p { + // NO_DISC: call void %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 0) ] + fp(); + } + } +} + +// CHECK-LABEL-DAG: test_option_none +pub fn test_option_none() -> Option { + unsafe { + // CHECK: ret ptr null + let p: Option = transmute::, _>(None); + p + } +} + +pub fn test_option_runtime_none(x: bool) -> Option { + // The source Option construction. + // CHECK: bb2: + // CHECK: store ptr null, ptr %{{.*}} + + // CHECK: bb1: + // DISC: store ptr ptrauth (ptr @{{.*}}g, i32 0, i64 2712), ptr %{{.*}} + // NO_DISC: store ptr ptrauth (ptr @{{.*}}g, i32 0), ptr [[RES:%.*]] + + // The nullable transmute checks for None before resigning. + // DISC: [[PTR:%.*]] = icmp eq i64 %{{.*}}, 0 + // DISC: br i1 [[PTR]], label %ptrauth.null, label %ptrauth.resign + + // None path. + // DISC: ptrauth.null: + // DISC: store ptr null, ptr %{{.*}} + // DISC: br label %ptrauth.end + + // Some path. + // DISC: ptrauth.resign: + // DISC: [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 %{{.*}}, i32 0, i64 2712, i32 0, i64 18983) + // DISC: [[RESIGNED_PTR:%.*]] = inttoptr i64 [[RESIGNED]] to ptr + // DISC: store ptr [[RESIGNED_PTR]], ptr %{{.*}} + + // Result. + // DISC: ptrauth.end: + // DISC: ret ptr %{{.*}} + // NO_DISC: [[RETURN_VAL:%.*]] = load ptr, ptr [[RES]] + // NO_DISC: ret ptr [[RETURN_VAL]] + unsafe { + let p = if x { Some(g as extern "C" fn(i32)) } else { None }; + + transmute(p) + } +} + +// CHECK-LABEL-DAG: test_option_runtime +pub unsafe fn test_option_runtime(p: Option) -> Option { + // Check nullable discrimination. + // DISC: [[PTR:%.*]] = ptrtoint ptr %p to i64 + // DISC: [[IS_NULL:%.*]] = icmp eq i64 [[PTR]], 0 + // DISC: br i1 [[IS_NULL]], label %ptrauth.null, label %ptrauth.resign + + // Null path. + // DISC: ptrauth.null: + // DISC: store ptr null, ptr %{{.*}} + // DISC: br label %ptrauth.end + + // Non-null is resigned from fn(i32) to fn(). + // DISC: ptrauth.resign: + // DISC: [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 %{{.*}}, i32 0, i64 2712, i32 0, i64 18983) + // DISC: [[RESIGNED_PTR:%.*]] = inttoptr i64 [[RESIGNED]] to ptr + // DISC: store ptr [[RESIGNED_PTR]], ptr %{{.*}} + + // Return. + // DISC: ptrauth.end: + // DISC: [[RET:%.*]] = load ptr, ptr %{{.*}} + // DISC: ret ptr [[RET]] + // NO_DISC: ret ptr %{{.*}}p + transmute(p) +} + +#[repr(transparent)] +struct SrcOpt(Option); + +#[repr(transparent)] +struct DstOpt(Option); + +// CHECK-LABEL-DAG: test_transparent_wrapper_option_none +pub fn test_transparent_wrapper_option_none() -> DstOpt { + // CHECK: ret ptr null + unsafe { transmute(SrcOpt(None)) } +} + +// CHECK-LABEL-DAG: test_transparent_wrapper_option_runtime +pub fn test_transparent_wrapper_option_runtime(p: Option) -> DstOpt { + // Transparent wrapper should still reach the nullable fn pointer path. + // DISC: [[PTR:%.*]] = ptrtoint ptr %p to i64 + // DISC: [[IS_NULL:%.*]] = icmp eq i64 [[PTR]], 0 + // DISC: br i1 [[IS_NULL]], label %ptrauth.null, label %ptrauth.resign + + // DISC: ptrauth.null: + // DISC: store ptr null, ptr %{{.*}} + // DISC: br label %ptrauth.end + + // DISC: ptrauth.resign: + // DISC: [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 %{{.*}}, i32 0, i64 2712, i32 0, i64 18983) + // DISC: [[RESIGNED_PTR:%.*]] = inttoptr i64 [[RESIGNED]] to ptr + // DISC: store ptr [[RESIGNED_PTR]], ptr %{{.*}} + + // DISC: ptrauth.end: + // DISC: ret ptr %{{.*}} + + // NO_DISC: ret ptr %{{.*}}p + unsafe { transmute(SrcOpt(p)) } +} + +// CHECK-LABEL-DAG: test_transparent_wrapper_option_branch +pub fn test_transparent_wrapper_option_branch(x: bool) -> DstOpt { + // Source Option construction branching. + // DISC: br i1 %x, label %bb1, label %bb2 + + // None arm. + // DISC: bb2: + // DISC: store ptr null, ptr %{{.*}} + + // Some arm. + // DISC: bb1: + // DISC: store ptr ptrauth (ptr @{{.*}}g, i32 0, i64 2712), ptr %{{.*}} + + // Transparent wrapper must still produce nullable resign. + // DISC: [[IS_NULL:%.*]] = icmp eq i64 %{{.*}}, 0 + // DISC: br i1 [[IS_NULL]], label %ptrauth.null, label %ptrauth.resign + + // DISC: ptrauth.resign: + // DISC: call i64 @llvm.ptrauth.resign(i64 %{{.*}}, i64 2712, i32 0, i64 18983) + let p = if x { Some(g as extern "C" fn(i32)) } else { None }; + + unsafe { transmute(SrcOpt(p)) } +} + +#[repr(transparent)] +struct SrcOptRef(Option<&'static extern "C" fn(i32)>); + +#[repr(transparent)] +struct DstOptRef(Option<&'static extern "C" fn()>); + +// CHECK-LABEL-DAG: test_transparent_wrapper_option_static_none +pub fn test_transparent_wrapper_option_static_none() -> DstOptRef { + // DISC: ret ptr null + unsafe { transmute(SrcOptRef(None)) } +} + +// CHECK-LABEL-DAG: test_transparent_wrapper_option_static_some +pub fn test_transparent_wrapper_option_static_some() -> DstOptRef { + // The pointer being returned is a reference to a function pointer object, not the function + // pointer value itself. + // DISC-NOT: llvm.ptrauth.resign + // DISC: ret ptr @{{.*}}F + unsafe { transmute(SrcOptRef(Some(&F))) } +} + +#[repr(transparent)] +struct W1(T); + +// CHECK-LABEL-DAG: test_option_inside_wrapper +pub fn test_option_inside_wrapper(p: Option) -> W1> { + // DISC: [[NULL_CHECK:%.*]] = ptrtoint ptr %p to i64 + // DISC: [[IS_NULL:%.*]] = icmp eq i64 [[NULL_CHECK]], 0 + // DISC: br i1 [[IS_NULL]], label %ptrauth.null, label %ptrauth.resign + + // DISC: ptrauth.null: + // DISC: store ptr null, ptr %{{.*}} + // DISC: br label %ptrauth.end + + // DISC: ptrauth.resign: + // DISC: [[RESIGN_PTR:%.*]] = ptrtoint ptr %p to i64 + // DISC: [[RESIGNED:%.*]] = call i64 @llvm.ptrauth.resign(i64 [[RESIGN_PTR]], i32 0, i64 2712, i32 0, i64 18983) + + // DISC: ptrauth.end: + // DISC: ret ptr + unsafe { transmute(W1(p)) } +} + +// CHECK-LABEL-DAG: test_option_option +pub fn test_option_option() { + unsafe { + // No resign for nested Options. + // DISC: store ptr ptrauth (ptr @{{.*}}g, i32 0, i64 2712) + // NO_DISC: store ptr ptrauth (ptr @{{.*}}g, i32 0) + // CHECK-NOT: llvm.ptrauth.resign + let p: Option> = transmute(Some(Some(g as extern "C" fn(i32)))); + + if let Some(Some(fp)) = p { + // DISC: call void %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 18983) ] + // NO_DISC: call void %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 0) ] + fp(); + } + } +} diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-raw.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-raw.rs new file mode 100644 index 0000000000000..280fb6a8487fa --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-raw.rs @@ -0,0 +1,57 @@ +// 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 + +// Transmutes through raw pointers. Make sure we match clang's behavior of treating raw pointers as +// zero-discriminated. + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] + +extern crate minicore; + +use minicore::mem::transmute; +use minicore::ptr; + +extern "C" fn f() {} +extern "C" fn g(_: i32) {} + +// CHECK-LABEL-DAG: test_fn_ptr_raw_ptr_fn_ptr +pub fn test_fn_ptr_raw_ptr_fn_ptr() { + unsafe { + // DISC: call i64 @llvm.ptrauth.resign(i64 ptrtoint (ptr ptrauth (ptr @{{.*}}g, i32 0, i64 2712) to i64), i32 0, i64 2712, i32 0, i64 0) + let p: extern "C" fn(i32) = g; + let raw: *const () = transmute(p); + + // DISC: = call i64 @llvm.ptrauth.resign(i64 %{{.*}}, i32 0, i64 0, i32 0, i64 2712) + let q: extern "C" fn(i32) = transmute(raw); + + // DISC: call void %{{.*}}(i32 123) #[[#]] [ "ptrauth"(i32 0, i64 2712) ] + // NO_DISC: call void ptrauth (ptr @{{.*}}g, i32 0)(i32 123) #[[#]] [ "ptrauth"(i32 0, i64 0) ] + q(123); + } +} + +// CHECK-LABEL-DAG: test_round_trip_through_mutable_raw_ptr +pub fn test_round_trip_through_mutable_raw_ptr() { + unsafe { + // DISC: call i64 @llvm.ptrauth.resign(i64 ptrtoint (ptr ptrauth (ptr @{{.*}}f, i32 0, i64 18983) to i64), i32 0, i64 18983, i32 0, i64 0) + let p: extern "C" fn() = f; + let raw: *mut () = transmute(p); + + // DISC: call i64 @llvm.ptrauth.resign(i64 %{{.*}}, i32 0, i64 0, i32 0, i64 18983) + let q: extern "C" fn() = transmute(raw); + + // DISC: call void %{{.*}}() #[[#]] [ "ptrauth"(i32 0, i64 18983) ] + // NO_DISC: call void ptrauth (ptr @{{.*}}f, i32 0)() #[[#]] [ "ptrauth"(i32 0, i64 0) ] + q(); + } +} diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-zst.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-zst.rs new file mode 100644 index 0000000000000..ea5f9fa160745 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transmute-zst.rs @@ -0,0 +1,90 @@ +// 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 + +// `PhantomData` is 1-aligned/zero-sized for any `T`, so these are all valid `repr(transparent)` +// wrappers with the wrapped fn pointer *not* at field index 0. `WrappedN` wraps a *different* fn +// signature than `RootSrc.fN`, so a real resign is required. + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] + +extern crate minicore; + +use minicore::mem::transmute; +use minicore::{PhantomData, Sync, ptr}; + +#[repr(transparent)] +struct Wrapped0(PhantomData, extern "C" fn(i32)); + +#[repr(transparent)] +struct Wrapped1((), extern "C" fn(i64, i64)); + +#[repr(transparent)] +struct Wrapped2(PhantomData, PhantomData, extern "C" fn(i64, i64, f32)); + +#[repr(transparent)] +struct Wrapped3([(); 0], extern "C" fn()); + +pub struct RootSrc { + f0: extern "C" fn(), + f1: extern "C" fn(i32), + f2: extern "C" fn(i64, i64), + f3: extern "C" fn(i64, i64, f32), +} + +pub struct RootDst { + f0: Wrapped0, + f1: Wrapped1, + f2: Wrapped2, + f3: Wrapped3, +} + +impl Sync for RootSrc {} +impl Sync for RootDst {} + +#[no_mangle] +// CHECK-LABEL-DAG: test_transparent_nonzero_field +pub fn test_transparent_nonzero_field(src: RootSrc) -> RootDst { + // NO_DISC-NOT: call i64 @llvm.ptrauth.resign + 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 { 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 { transmute::(src.f1) }, + + // field 2: load -> resign(55265 -> 44485) -> store -- two leading 1-ZSTs this time + // 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 { 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 { transmute::(src.f3) }, + } +} 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(); +} From 693db911e230acfcb8a1fda364b56536130ad2cd Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Thu, 10 Sep 2026 14:18:28 +0000 Subject: [PATCH 12/12] [PAC] Tests for repr(transparent), const globals, enums, Option And small fixes. --- ...ptr-type-discrimination-const-aggregate.rs | 73 ++++++++++++ ...fn-ptr-type-discrimination-const-scalar.rs | 48 ++++++++ ...fn-ptr-type-discrimination-enum-payload.rs | 95 +++++++++++++++ ...ptr-type-discrimination-option-callback.rs | 29 +++-- ...r-type-discrimination-option-raw-vs-ref.rs | 104 +++++++++++++++++ ...r-type-discrimination-recursive-statics.rs | 2 + ...-fn-ptr-type-discrimination-struct-name.rs | 2 + ...type-discrimination-transparent-wrapper.rs | 110 ++++++++++++++++++ 8 files changed, 454 insertions(+), 9 deletions(-) create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-const-aggregate.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-const-scalar.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-enum-payload.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-raw-vs-ref.rs create mode 100644 tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transparent-wrapper.rs diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-const-aggregate.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-const-aggregate.rs new file mode 100644 index 0000000000000..32bc48e41e69a --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-const-aggregate.rs @@ -0,0 +1,73 @@ +// ignore-tidy-file-linelength +//@ add-minicore +// 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 +// +// Make sure that `OperandRef::from_const_alloc`'s `read_scalar` closure +// (compiler/rustc_codegen_ssa/src/mir/operand.rs), correctly signs function pointers with their +// discriminators. +// PAIR / PAIR_REV specifically exercise the `BackendRepr::ScalarPair` arm with the function +// pointer in each of the two field positions. And the offset used to key the discriminator +// lookup (0 for field `a`, `b_offset` for field `b`). +// TRIO forces `BackendRepr::Memory`. + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] +extern crate minicore; + +extern "C" { + fn f(ctx: *mut i32) -> i32; +} + +type FnPtr = unsafe extern "C" fn(*mut i32) -> i32; + +#[used] +// Baseline. +// DISC: @{{.*}}T_PTR = constant ptr ptrauth (ptr @{{.*}}f, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}T_PTR = constant ptr ptrauth (ptr @{{.*}}f, i32 0), align 8 +static T_PTR: FnPtr = f; + +// ScalarPair, function pointer at field `a` (local offset 0). +const PAIR: (FnPtr, i32) = (f, 1); + +#[repr(C)] +struct PairRev { + x: i32, + f: FnPtr, +} + +// ScalarPair, function pointer at field `b` +const PAIR_REV: PairRev = PairRev { x: 2, f }; + +// BackendRepr::Memory +// DISC: private unnamed_addr constant <{ ptr, [8 x i8] }> <{ ptr ptrauth (ptr @{{.*}}f, i32 0, i64 12410), [8 x i8] +// NO_DISC: private unnamed_addr constant <{ ptr, [8 x i8] }> <{ ptr ptrauth (ptr @{{.*}}f, i32 0), [8 x i8] +const TRIO: (FnPtr, i32, i32) = (f, 3, 4); + +// CHECK-LABEL: main +pub fn main() { + let mut x = 42i32; + unsafe { + // DISC: call i32 ptrauth (ptr @f, i32 0, i64 12410)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @f, i32 0)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_PTR(&mut x as *mut i32); + + // DISC: call i32 ptrauth (ptr @f, i32 0, i64 12410)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @f, i32 0)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = PAIR.0(&mut x as *mut i32); + + // DISC: call i32 ptrauth (ptr @f, i32 0, i64 12410)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @f, i32 0)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = (PAIR_REV.f)(&mut x as *mut i32); + + // DISC: call i32 %{{.*}}(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 %{{.*}}(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = TRIO.0(&mut x as *mut i32); + } +} diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-const-scalar.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-const-scalar.rs new file mode 100644 index 0000000000000..2b8e85ae57865 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-const-scalar.rs @@ -0,0 +1,48 @@ +// ignore-tidy-file-linelength +//@ add-minicore +// 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 +// +// Make sure that a function pointer materialized from a `const` item carries the same discriminator +// as the identical signature materialized from a `static`. + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] +extern crate minicore; + +extern "C" { + fn f(ctx: *mut i32) -> i32; +} + +type FnPtr = unsafe extern "C" fn(*mut i32) -> i32; + +#[used] +// DISC: @{{.*}}T_STATIC = constant ptr ptrauth (ptr @{{.*}}f, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}T_STATIC = constant ptr ptrauth (ptr @{{.*}}f, i32 0), align 8 +static T_STATIC: FnPtr = f; + +// `const`, not `static`: this is the exact case the fix addresses. A bare standalone function +// pointer constant has no separate global of its own - it's inlined as an LLVM constant operand +// directly at each use site, which is why the check below lives at the call site rather than on +// a symbol (unlike T_STATIC above). +const T_CONST: FnPtr = f; + +// CHECK-LABEL: main +pub fn main() { + let mut x = 42i32; + unsafe { + // DISC: call i32 ptrauth (ptr @f, i32 0, i64 12410)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @f, i32 0)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_STATIC(&mut x as *mut i32); + + // DISC: call i32 ptrauth (ptr @f, i32 0, i64 12410)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @f, i32 0)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_CONST(&mut x as *mut i32); + } +} diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-enum-payload.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-enum-payload.rs new file mode 100644 index 0000000000000..c21f49ebabd89 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-enum-payload.rs @@ -0,0 +1,95 @@ +// ignore-tidy-file-linelength +//@ add-minicore +// 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 + +// Emulate a NULL-able function argument with Option, and contrast it with a NULL-able data +// pointer via Option<*mut c_void>. +// +// Option is ABI-compatible with the bare fn pointer (fn pointers have the null-pointer +// niche), so it must be discriminated identically to it - encoded as 'P'. +// +// Option<*mut c_void> is NOT ABI-compatible with a bare *mut c_void: raw pointers have no spare +// niche (null is itself a valid raw pointer value), so it must NOT collapse to 'P'. It currently +// falls through to the generic enum-collapse path instead. + +// Discriminators: +// 18786: "Fi6OptionE", +// 12410: "FiPE". +// 2981: "FiiE". + +#![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 18786), 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 18786){{.*}} [ "ptrauth"(i32 0, i64 18786) ] + // 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 18786){{.*}} [ "ptrauth"(i32 0, i64 18786) ] + // 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-callback.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-callback.rs index 685157131ae7b..f319e2c4dde7b 100644 --- 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 @@ -8,8 +8,19 @@ //@ [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 -// Emulate NULL-able function argument with Option. Make sure that Option is treated -// as function pointer - encoded as P. +// Emulate a NULL-able function argument with Option, and contrast it with a data pointer via +// Option<*mut c_void>. +// +// Option is ABI-compatible with the bare fn pointer (fn pointers have the null-pointer +// niche), so it must be discriminated identically to it - encoded as 'P'. +// +// Option<*mut c_void> is NOT ABI-compatible with a bare *mut c_void: raw pointers have no spare +// niche (null is itself a valid raw pointer value), so it must NOT collapse to 'P'. +// +// Discriminators: +// 18786: "Fi6OptionE", +// 12410: "FiPE". +// 2981: "FiiE". #![feature(no_core, lang_items)] #![no_std] @@ -44,7 +55,7 @@ static T_OPT: FnOpt = f_opt; // 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 +// DISC: @{{.*}}G_OPT = constant ptr ptrauth (ptr @{{.*}}g_opt, i32 0, i64 18786), align 8 // NO_DISC: @{{.*}}G_OPT = constant ptr ptrauth (ptr @{{.*}}g_opt, i32 0), align 8 #[used] static G_OPT: DataOpt = g_opt; @@ -59,21 +70,21 @@ pub fn main() { 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) ] + // 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) ] + // 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) ] + // DISC: call i32 ptrauth (ptr @g_opt, i32 0, i64 18786){{.*}} [ "ptrauth"(i32 0, i64 18786) ] // 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) ] + // DISC: call i32 ptrauth (ptr @g_opt, i32 0, i64 18786){{.*}} [ "ptrauth"(i32 0, i64 18786) ] // NO_DISC: call i32 ptrauth (ptr @g_opt, i32 0){{.*}} [ "ptrauth"(i32 0, i64 0) ] let _ = G_OPT(None); diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-raw-vs-ref.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-raw-vs-ref.rs new file mode 100644 index 0000000000000..83ef29e63e677 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-option-raw-vs-ref.rs @@ -0,0 +1,104 @@ +// ignore-tidy-file-linelength +//@ add-minicore +// 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 + +// Regression test for `canonicalize_c_type` +// (compiler/rustc_middle/src/ptrauth/discriminator.rs). +// +// `Option<&T>` is ABI-compatible with `&T` because references have a null pointer niche, so it must +// receive the same discriminator as a pointer. +// +// `Option<*mut T>` is not ABI-compatible with a bare pointer: raw pointers do not have a null niche +// because null is a valid pointer value. It must therefore not receive the pointer discriminator. +// +// Discriminators: +// 18786: "Fi6OptionE" +// 12410: "FiPE" +// 2981: "FiiE" + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] +extern crate minicore; +use minicore::Option; +use minicore::Option::Some; + +extern "C" { + fn f_ptr(ctx: *mut i32) -> i32; + fn f_int(x: i32) -> i32; + fn f_ref(ctx: &i32) -> i32; + fn f_opt_ref(ctx: Option<&i32>) -> i32; + fn f_opt_raw(ctx: Option<*mut i32>) -> i32; +} + +type FnPtr = unsafe extern "C" fn(*mut i32) -> i32; +type FnInt = unsafe extern "C" fn(i32) -> i32; +type FnRef = unsafe extern "C" fn(&i32) -> i32; +type FnOptRef = unsafe extern "C" fn(Option<&i32>) -> i32; +type FnOptRaw = unsafe extern "C" fn(Option<*mut i32>) -> i32; + +#[used] +// Baseline: bare pointer argument. +// 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 +static T_PTR: FnPtr = f_ptr; + +#[used] +// Baseline: bare scalar-int argument. +// DISC: @{{.*}}T_INT = constant ptr ptrauth (ptr @{{.*}}f_int, i32 0, i64 2981), align 8 +// NO_DISC: @{{.*}}T_INT = constant ptr ptrauth (ptr @{{.*}}f_int, i32 0), align 8 +static T_INT: FnInt = f_int; + +const T_REF: FnRef = f_ref; + +// Option<&T> is niche-optimized (ABI-identical to &T), so it must match T_PTR/T_REF - NOT T_INT. +// Under the pre-fix code this failed: Option<&T> wasn't canonicalized at all and fell through +// to the enum path, so it wrongly matched T_INT instead. +const T_OPT_REF: FnOptRef = f_opt_ref; + +#[used] +// Option<*mut T> is NOT niche-optimized, so canonicalize_c_type must NOT unwrap it to a pointer - +// it must NOT match T_PTR/T_REF's [[PTR_DISC]] bucket. Under the pre-fix code this failed: +// Option<*mut T> was wrongly canonicalized straight to Pointer, so it wrongly matched T_PTR. +// +// Which non-pointer bucket it actually lands in is NOT this file's concern - that's determined +// by a separate piece of logic (the enum payload-collapse guard in `to_clang_disc_ty`), tested +// independently in pauth-fn-ptr-type-discrimination-enum-payload.rs. Asserting a specific value +// here would wrongly couple this test to that other fix. +// DISC-NOT: @{{.*}}T_OPT_RAW = constant ptr ptrauth (ptr @{{.*}}f_opt_raw, i32 0, i64 12410), align 8 +// DISC: @{{.*}}T_OPT_RAW = constant ptr ptrauth (ptr @{{.*}}f_opt_raw, i32 0, i64 18786), align 8 +// NO_DISC: @{{.*}}T_OPT_RAW = constant ptr ptrauth (ptr @{{.*}}f_opt_raw, i32 0), align 8 +static T_OPT_RAW: FnOptRaw = f_opt_raw; + +// CHECK-LABEL: main +pub fn main() { + let mut x = 42i32; + unsafe { + // 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 as *mut i32); + + // DISC: call i32 ptrauth (ptr @f_int, i32 0, i64 2981)(i32 %{{.*}}) {{.*}} [ "ptrauth"(i32 0, i64 2981) ] + // NO_DISC: call i32 ptrauth (ptr @f_int, i32 0)(i32 %{{.*}}) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_INT(x); + + // Call-site bundles must carry the same discriminators as the signing above. + // DISC: call i32 ptrauth (ptr @f_ref, i32 0, i64 12410)(ptr align 4 %x) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @f_ref, i32 0)(ptr align 4 %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_REF(&x); + + // DISC: call i32 ptrauth (ptr @f_opt_ref, i32 0, i64 12410)(ptr align 4 %x) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @f_opt_ref, i32 0)(ptr align 4 %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_OPT_REF(Some(&x)); + + // DISC: call i32 ptrauth (ptr @f_opt_raw, i32 0, i64 18786){{.*}} [ "ptrauth"(i32 0, i64 18786) ] + // NO_DISC: call i32 ptrauth (ptr @f_opt_raw, i32 0){{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_OPT_RAW(Some(&mut x as *mut i32)); + } +} diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-recursive-statics.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-recursive-statics.rs index e07e0babbd880..a8eab77e0b5ef 100644 --- a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-recursive-statics.rs +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-recursive-statics.rs @@ -6,6 +6,8 @@ //@ 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 // Tests function-pointer type discriminator generation for pointer authentication across nested // static allocations, wrapper references, and padded structs. 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 index 47c7ca205f887..8ff5bbca89395 100644 --- 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 @@ -6,6 +6,8 @@ //@ 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 // 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. diff --git a/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transparent-wrapper.rs b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transparent-wrapper.rs new file mode 100644 index 0000000000000..211584bb5c507 --- /dev/null +++ b/tests/codegen-llvm/pauth/pauth-fn-ptr-type-discrimination-transparent-wrapper.rs @@ -0,0 +1,110 @@ +// ignore-tidy-file-linelength +//@ add-minicore +// 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 +// +// Regression test for `#[repr(transparent)]` handling in `canonicalize_c_type` +// (compiler/rustc_middle/src/ptrauth/discriminator.rs). +// +// `#[repr(transparent)]` is Rust's explicit ABI guarantee that a wrapper struct has the exact size, +// alignment, and calling convention of its one significant (non-1-ZST) field. Clang's own encoder +// strips typedefs before encoding (`QT.getCanonicalType()`). +// +// Discriminators: +// 12410: "FiPE" +// 62802: "Fi14NotTransparentE" + +#![feature(no_core, lang_items)] +#![no_std] +#![no_core] +#![crate_type = "lib"] +extern crate minicore; +use minicore::Option; +use minicore::Option::Some; + +#[repr(transparent)] +pub struct Handle(*mut i32); + +// A same-shaped wrapper WITHOUT `#[repr(transparent)]` this must NOT collapse to the pointer bucket +pub struct NotTransparent(*mut i32); + +pub struct Marker; + +// A 1-ZST filler field placed before the significant field, to actually exercise `non_1zst_field`'s +// real search rather than something that would coincidentally also pass for a plain single-field +// wrapper. +#[repr(transparent)] +pub struct HandleWithMarker(Marker, *mut i32); + +// Composition: peeling the transparent wrapper must happen before the Option<&T> rule can see +// what's inside it. Exercises the fixpoint loop in canonicalize_c_type. +#[repr(transparent)] +pub struct OptRefWrapper<'a>(Option<&'a i32>); + +extern "C" { + fn f_ptr(x: *mut i32) -> i32; + fn f_handle(x: Handle) -> i32; + fn f_not_transparent(x: NotTransparent) -> i32; + fn f_handle_with_marker(x: HandleWithMarker) -> i32; + fn f_opt_ref_wrapped(x: OptRefWrapper) -> i32; +} + +type FnPtr = unsafe extern "C" fn(*mut i32) -> i32; +type FnHandle = unsafe extern "C" fn(Handle) -> i32; +type FnNotTransparent = unsafe extern "C" fn(NotTransparent) -> i32; +type FnHandleWithMarker = unsafe extern "C" fn(HandleWithMarker) -> i32; +type FnOptRefWrapped = unsafe extern "C" fn(OptRefWrapper) -> i32; + +#[used] +// Baseline: bare pointer argument. +// 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 +static T_PTR: FnPtr = f_ptr; + +#[used] +// DISC: @{{.*}}T_HANDLE = constant ptr ptrauth (ptr @{{.*}}f_handle, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}T_HANDLE = constant ptr ptrauth (ptr @{{.*}}f_handle, i32 0), align 8 +static T_HANDLE: FnHandle = f_handle; + +#[used] +// DISC-NOT: @{{.*}}T_NOT_TRANSPARENT = constant ptr ptrauth (ptr @{{.*}}f_not_transparent, i32 0, i64 12410), align 8 +// DISC: @{{.*}}T_NOT_TRANSPARENT = constant ptr ptrauth (ptr @{{.*}}f_not_transparent, i32 0, i64 62802), align 8 +// NO_DISC: @{{.*}}T_NOT_TRANSPARENT = constant ptr ptrauth (ptr @{{.*}}f_not_transparent, i32 0), align 8 +static T_NOT_TRANSPARENT: FnNotTransparent = f_not_transparent; + +#[used] +// DISC: @{{.*}}T_HANDLE_WITH_MARKER = constant ptr ptrauth (ptr @{{.*}}f_handle_with_marker, i32 0, i64 12410), align 8 +// NO_DISC: @{{.*}}T_HANDLE_WITH_MARKER = constant ptr ptrauth (ptr @{{.*}}f_handle_with_marker, i32 0), align 8 +static T_HANDLE_WITH_MARKER: FnHandleWithMarker = f_handle_with_marker; + +const T_OPT_REF_WRAPPED: FnOptRefWrapped = f_opt_ref_wrapped; + +// CHECK-LABEL: main +pub fn main() { + let mut x = 42i32; + unsafe { + // 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 as *mut i32); + + // DISC: call i32 ptrauth (ptr @f_handle, i32 0, i64 12410)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @f_handle, i32 0)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_HANDLE(Handle(&mut x as *mut i32)); + + // DISC: call i32 ptrauth (ptr @f_not_transparent, i32 0, i64 62802)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 62802) ] + // NO_DISC: call i32 ptrauth (ptr @f_not_transparent, i32 0)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_NOT_TRANSPARENT(NotTransparent(&mut x as *mut i32)); + + // DISC: call i32 ptrauth (ptr @f_handle_with_marker, i32 0, i64 12410)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @f_handle_with_marker, i32 0)(ptr %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_HANDLE_WITH_MARKER(HandleWithMarker(Marker, &mut x as *mut i32)); + + // DISC: call i32 ptrauth (ptr @f_opt_ref_wrapped, i32 0, i64 12410)(ptr align 4 %x) {{.*}} [ "ptrauth"(i32 0, i64 12410) ] + // NO_DISC: call i32 ptrauth (ptr @f_opt_ref_wrapped, i32 0)(ptr align 4 %x) {{.*}} [ "ptrauth"(i32 0, i64 0) ] + let _ = T_OPT_REF_WRAPPED(OptRefWrapper(Some(&x))); + } +}