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