From 4c1b0acc0e713eb4ae922c0b29aad755fb48bb25 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Fri, 10 Jul 2026 11:13:30 +0000 Subject: [PATCH 1/2] [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 5e051596686725a69600e813785fc02dab68a924 Mon Sep 17 00:00:00 2001 From: Jakub Chlanda Date: Mon, 20 Jul 2026 11:44:28 +0000 Subject: [PATCH 2/2] [PAC] Add unit tests for sip hash --- .../llvm_siphash/llvm_siphash_vectors.rs | 68 +++++++++++++++++++ .../{llvm_siphash.rs => llvm_siphash/mod.rs} | 19 ++++-- .../src/ptrauth/llvm_siphash/tests.rs | 55 +++++++++++++++ .../aarch64-unknown-linux-pauthtest.md | 8 ++- 4 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 compiler/rustc_middle/src/ptrauth/llvm_siphash/llvm_siphash_vectors.rs rename compiler/rustc_middle/src/ptrauth/{llvm_siphash.rs => llvm_siphash/mod.rs} (91%) create mode 100644 compiler/rustc_middle/src/ptrauth/llvm_siphash/tests.rs diff --git a/compiler/rustc_middle/src/ptrauth/llvm_siphash/llvm_siphash_vectors.rs b/compiler/rustc_middle/src/ptrauth/llvm_siphash/llvm_siphash_vectors.rs new file mode 100644 index 0000000000000..3dc799f07b27d --- /dev/null +++ b/compiler/rustc_middle/src/ptrauth/llvm_siphash/llvm_siphash_vectors.rs @@ -0,0 +1,68 @@ +pub(super) const TEST_KEY: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]; + +pub(super) const EXPECTED64: [[u8; 8]; 64] = [ + [0x31, 0x0e, 0x0e, 0xdd, 0x47, 0xdb, 0x6f, 0x72], + [0xfd, 0x67, 0xdc, 0x93, 0xc5, 0x39, 0xf8, 0x74], + [0x5a, 0x4f, 0xa9, 0xd9, 0x09, 0x80, 0x6c, 0x0d], + [0x2d, 0x7e, 0xfb, 0xd7, 0x96, 0x66, 0x67, 0x85], + [0xb7, 0x87, 0x71, 0x27, 0xe0, 0x94, 0x27, 0xcf], + [0x8d, 0xa6, 0x99, 0xcd, 0x64, 0x55, 0x76, 0x18], + [0xce, 0xe3, 0xfe, 0x58, 0x6e, 0x46, 0xc9, 0xcb], + [0x37, 0xd1, 0x01, 0x8b, 0xf5, 0x00, 0x02, 0xab], + [0x62, 0x24, 0x93, 0x9a, 0x79, 0xf5, 0xf5, 0x93], + [0xb0, 0xe4, 0xa9, 0x0b, 0xdf, 0x82, 0x00, 0x9e], + [0xf3, 0xb9, 0xdd, 0x94, 0xc5, 0xbb, 0x5d, 0x7a], + [0xa7, 0xad, 0x6b, 0x22, 0x46, 0x2f, 0xb3, 0xf4], + [0xfb, 0xe5, 0x0e, 0x86, 0xbc, 0x8f, 0x1e, 0x75], + [0x90, 0x3d, 0x84, 0xc0, 0x27, 0x56, 0xea, 0x14], + [0xee, 0xf2, 0x7a, 0x8e, 0x90, 0xca, 0x23, 0xf7], + [0xe5, 0x45, 0xbe, 0x49, 0x61, 0xca, 0x29, 0xa1], + [0xdb, 0x9b, 0xc2, 0x57, 0x7f, 0xcc, 0x2a, 0x3f], + [0x94, 0x47, 0xbe, 0x2c, 0xf5, 0xe9, 0x9a, 0x69], + [0x9c, 0xd3, 0x8d, 0x96, 0xf0, 0xb3, 0xc1, 0x4b], + [0xbd, 0x61, 0x79, 0xa7, 0x1d, 0xc9, 0x6d, 0xbb], + [0x98, 0xee, 0xa2, 0x1a, 0xf2, 0x5c, 0xd6, 0xbe], + [0xc7, 0x67, 0x3b, 0x2e, 0xb0, 0xcb, 0xf2, 0xd0], + [0x88, 0x3e, 0xa3, 0xe3, 0x95, 0x67, 0x53, 0x93], + [0xc8, 0xce, 0x5c, 0xcd, 0x8c, 0x03, 0x0c, 0xa8], + [0x94, 0xaf, 0x49, 0xf6, 0xc6, 0x50, 0xad, 0xb8], + [0xea, 0xb8, 0x85, 0x8a, 0xde, 0x92, 0xe1, 0xbc], + [0xf3, 0x15, 0xbb, 0x5b, 0xb8, 0x35, 0xd8, 0x17], + [0xad, 0xcf, 0x6b, 0x07, 0x63, 0x61, 0x2e, 0x2f], + [0xa5, 0xc9, 0x1d, 0xa7, 0xac, 0xaa, 0x4d, 0xde], + [0x71, 0x65, 0x95, 0x87, 0x66, 0x50, 0xa2, 0xa6], + [0x28, 0xef, 0x49, 0x5c, 0x53, 0xa3, 0x87, 0xad], + [0x42, 0xc3, 0x41, 0xd8, 0xfa, 0x92, 0xd8, 0x32], + [0xce, 0x7c, 0xf2, 0x72, 0x2f, 0x51, 0x27, 0x71], + [0xe3, 0x78, 0x59, 0xf9, 0x46, 0x23, 0xf3, 0xa7], + [0x38, 0x12, 0x05, 0xbb, 0x1a, 0xb0, 0xe0, 0x12], + [0xae, 0x97, 0xa1, 0x0f, 0xd4, 0x34, 0xe0, 0x15], + [0xb4, 0xa3, 0x15, 0x08, 0xbe, 0xff, 0x4d, 0x31], + [0x81, 0x39, 0x62, 0x29, 0xf0, 0x90, 0x79, 0x02], + [0x4d, 0x0c, 0xf4, 0x9e, 0xe5, 0xd4, 0xdc, 0xca], + [0x5c, 0x73, 0x33, 0x6a, 0x76, 0xd8, 0xbf, 0x9a], + [0xd0, 0xa7, 0x04, 0x53, 0x6b, 0xa9, 0x3e, 0x0e], + [0x92, 0x59, 0x58, 0xfc, 0xd6, 0x42, 0x0c, 0xad], + [0xa9, 0x15, 0xc2, 0x9b, 0xc8, 0x06, 0x73, 0x18], + [0x95, 0x2b, 0x79, 0xf3, 0xbc, 0x0a, 0xa6, 0xd4], + [0xf2, 0x1d, 0xf2, 0xe4, 0x1d, 0x45, 0x35, 0xf9], + [0x87, 0x57, 0x75, 0x19, 0x04, 0x8f, 0x53, 0xa9], + [0x10, 0xa5, 0x6c, 0xf5, 0xdf, 0xcd, 0x9a, 0xdb], + [0xeb, 0x75, 0x09, 0x5c, 0xcd, 0x98, 0x6c, 0xd0], + [0x51, 0xa9, 0xcb, 0x9e, 0xcb, 0xa3, 0x12, 0xe6], + [0x96, 0xaf, 0xad, 0xfc, 0x2c, 0xe6, 0x66, 0xc7], + [0x72, 0xfe, 0x52, 0x97, 0x5a, 0x43, 0x64, 0xee], + [0x5a, 0x16, 0x45, 0xb2, 0x76, 0xd5, 0x92, 0xa1], + [0xb2, 0x74, 0xcb, 0x8e, 0xbf, 0x87, 0x87, 0x0a], + [0x6f, 0x9b, 0xb4, 0x20, 0x3d, 0xe7, 0xb3, 0x81], + [0xea, 0xec, 0xb2, 0xa3, 0x0b, 0x22, 0xa8, 0x7f], + [0x99, 0x24, 0xa4, 0x3c, 0xc1, 0x31, 0x57, 0x24], + [0xbd, 0x83, 0x8d, 0x3a, 0xaf, 0xbf, 0x8d, 0xb7], + [0x0b, 0x1a, 0x2a, 0x32, 0x65, 0xd5, 0x1a, 0xea], + [0x13, 0x50, 0x79, 0xa3, 0x23, 0x1c, 0xe6, 0x60], + [0x93, 0x2b, 0x28, 0x46, 0xe4, 0xd7, 0x06, 0x66], + [0xe1, 0x91, 0x5f, 0x5c, 0xb1, 0xec, 0xa4, 0x6c], + [0xf3, 0x25, 0x96, 0x5c, 0xa1, 0x6d, 0x62, 0x9f], + [0x57, 0x5f, 0xf2, 0x8e, 0x60, 0x38, 0x1b, 0xe5], + [0x72, 0x45, 0x06, 0xeb, 0x4c, 0x32, 0x8a, 0x95], +]; diff --git a/compiler/rustc_middle/src/ptrauth/llvm_siphash.rs b/compiler/rustc_middle/src/ptrauth/llvm_siphash/mod.rs similarity index 91% rename from compiler/rustc_middle/src/ptrauth/llvm_siphash.rs rename to compiler/rustc_middle/src/ptrauth/llvm_siphash/mod.rs index 68aeac11a0429..a835325e3c82c 100644 --- a/compiler/rustc_middle/src/ptrauth/llvm_siphash.rs +++ b/compiler/rustc_middle/src/ptrauth/llvm_siphash/mod.rs @@ -1,4 +1,4 @@ -// LLVM SipHash-2-4 +// LLVM SipHash-2-4 (64 bit version) pub fn llvm_pointer_auth_stable_siphash(data: &[u8]) -> u16 { let raw = llvm_siphash_2_4_64(data); @@ -91,10 +91,9 @@ fn load_u64_partial(bytes: &[u8]) -> 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]); +fn siphash_2_4_64_with_key(data: &[u8], key: &[u8; 16]) -> u64 { + let k0 = u64_from_le(&key[0..8]); + let k1 = u64_from_le(&key[8..16]); let mut v0: u64 = 0x736f6d6570736575 ^ k0; let mut v1: u64 = 0x646f72616e646f6d ^ k1; @@ -140,3 +139,13 @@ fn llvm_siphash_2_4_64(data: &[u8]) -> u64 { v0 ^ v1 ^ v2 ^ v3 } +// LLVM siphash<2,4> 64-bit output +fn llvm_siphash_2_4_64(data: &[u8]) -> u64 { + siphash_2_4_64_with_key(data, &SIPHASH_KEY) +} + +#[cfg(test)] +mod llvm_siphash_vectors; + +#[cfg(test)] +mod tests; diff --git a/compiler/rustc_middle/src/ptrauth/llvm_siphash/tests.rs b/compiler/rustc_middle/src/ptrauth/llvm_siphash/tests.rs new file mode 100644 index 0000000000000..6c2f2bf903749 --- /dev/null +++ b/compiler/rustc_middle/src/ptrauth/llvm_siphash/tests.rs @@ -0,0 +1,55 @@ +use llvm_siphash_vectors::{EXPECTED64, TEST_KEY}; + +use super::*; + +// These tests mirror the SipHash tests from LLVM's Support/SipHashTest.cpp. +// The implementation here is a faithful Rust translation of the LLVM 64-bit +// SipHash-2-4 implementation used for pointer authentication discriminators. +// +// TEST_KEY and EXPECTED64 are copied from the upstream SipHash reference +// vectors. Keeping these values identical ensures that the Rust implementation +// produces the same SipHash output as LLVM. +// +// Run with: +// x.py test compiler/rustc_middle --test-args siphash + +#[test] +fn siphash24_reference_vectors_64() { + // Validate the SipHash-2-4 implementation against the upstream SipHash + // reference vectors. + // + // Each input is the byte sequence [0, 1, ..., len-1] and is hashed using + // the reference key TEST_KEY ([0, 1, ..., 15]). The expected values are + // compared byte-for-byte with the LLVM reference implementation output + // (EXPECTED64). + // + // This test verifies the correctness of the SipHash primitive itself, + // independently of the pointer authentication discriminator logic. + let mut input = [0u8; 64]; + + for len in 0..EXPECTED64.len() { + input[len] = len as u8; + + let hash = siphash_2_4_64_with_key(&input[..len], &TEST_KEY); + + assert_eq!(hash.to_le_bytes(), EXPECTED64[len]); + } +} + +#[test] +fn pointer_auth_stable_siphash() { + // Validate the rustc pointer-authentication SipHash wrapper against the + // values used by the LLVM implementation. + // + // The returned value is a 16-bit discriminator derived from the stable + // SipHash-2-4 output. + assert_eq!(llvm_pointer_auth_stable_siphash(b""), 0xE793); + assert_eq!(llvm_pointer_auth_stable_siphash(b"strlen"), 0xF468); + assert_eq!(llvm_pointer_auth_stable_siphash(b"_ZN1 ind; f"), 0x2D15); + assert_eq!(llvm_pointer_auth_stable_siphash(b"isa"), 0x6AE1); + assert_eq!(llvm_pointer_auth_stable_siphash(b"objc_class:superclass"), 0xB5AB,); + assert_eq!(llvm_pointer_auth_stable_siphash(b"block_descriptor"), 0xC0BB,); + assert_eq!(llvm_pointer_auth_stable_siphash(b"method_list_t"), 0xC310,); + assert_eq!(llvm_pointer_auth_stable_siphash(b"_Zptrkvttf"), 1); + assert_eq!(llvm_pointer_auth_stable_siphash(b"_Zaflhllod"), 0xFFFF); +} 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..a0a03f917c947 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 @@ -474,10 +474,12 @@ The following categories are supported (all present in tree): * invalid_target_pointer_authentication.rs * type_discrimination_not_supported_pointer_authentication.rs * incompatible_pauth.rs +* Unit tests for siphash function: + * compiler/rustc_middle/src/ptrauth/llvm_siphash/tests.rs All tests from `assembly-llvm`, `codegen-llvm`, `codegen-units`, `coverage`, -`crashes`, `incremental`, `library`, `mir-opt`, `run-make`, `ui` and -`ui-fulldeps` subsets are expected to pass. +`crashes`, `incremental`, `library`, `mir-opt`, `run-make`, `rust_middle` `ui` +and `ui-fulldeps` subsets are expected to pass. Command to run all passing tests (with tests added by this target explicitly named for convenience): @@ -485,7 +487,7 @@ named for convenience): ```sh x.py test --target aarch64-unknown-linux-pauthtest --force-rerun assembly-llvm \ codegen-llvm codegen-units coverage crashes incremental library mir-opt \ - run-make ui ui-fulldeps \ + run-make rust_middle ui ui-fulldeps \ tests/assembly-llvm/pauth-basic.rs \ tests/codegen-llvm/pauth/pauth-attr-cli-flags.rs \ tests/codegen-llvm/pauth/pauth-attr-special-funcs.rs \