rustc_middle/ptrauth/discriminator.rs
1//! Function pointer type discrimination for pointer authentication.
2
3//! This module implements Rust's equivalent of Clang's function pointer type
4//! discriminator computation used in pointer authentication.
5//!
6//! Compatibility with Clang is a primary goal. The discriminator produced for a
7//! given external "C" function type must match the value computed by Clang so that
8//! function pointers can be exchanged safely between Rust and C code while
9//! preserving pointer authentication semantics.
10//!
11//! The implementation mirrors Clang's behavior in
12//! `ASTContext::encodeTypeForFunctionPointerAuth`, ensuring that identical
13//! C-compatible function types produce identical discriminators. See:
14//! <https://clang.llvm.org/doxygen/ASTContext_8cpp.html#abb1375e068e807917527842d05cadea3>.
15//!
16//! ## Overview
17//!
18//! The computation is structured into three conceptual stages:
19//!
20//! ### 1. Type normalization and lowering
21//! Rust types are converted into a language-independent representation
22//! (`ClangDiscTy`) that mirrors the type categories used by Clang when computing
23//! function pointer discriminators. This includes canonicalization such as
24//! treating all pointer-like types uniformly and mapping Rust constructs onto
25//! their closest C equivalents.
26//! One notable exception is C `_Complex`. Rust has no corresponding native type,
27//! so there is no canonical Rust representation to map onto Clang's `_Complex`
28//! type category. Rather than infer one (for example, by treating `(f32, f32)`
29//! or `(f64, f64)` as complex numbers), this implementation leaves such
30//! representation choices to users and does not provide dedicated `_Complex`
31//! encoding.
32//!
33//! ### 2. Type encoding
34//! The lowered representation is serialized into a byte stream using rules
35//! intended to match Clang's implementation in:
36//! `encodeTypeForFunctionPointerAuth`. The resulting encoding describes the
37//! function signature in a target-independent form suitable for hashing.
38//!
39//! ### 3. Discriminator hashing
40//! The encoded byte stream is hashed using LLVM's stable SipHash-2-4 based
41//! discriminator algorithm. The implementation here is a direct translation
42//! of LLVM/Clang's logic and must remain bit-for-bit compatible. See:
43//! <https://github.com/llvm/llvm-project/blob/main/third-party/siphash/include/siphash/SipHash.h>.
44//! Defined in `llvm_siphash.rs`.
45//!
46//! ## Module structure
47//!
48//! - High-level API
49//! - `FnPtrDiscriminatorSource`
50//! - `ptrauth_compute_fn_ptr_type_discriminator_for`
51//! - `ptrauth_clone_discriminated_schema_for`
52//!
53//! - Low-level API
54//! - `FnPtrTypeDiscriminatorInput`
55//! - `compute_fn_ptr_type_discriminator`
56//!
57//! - Signature extraction
58//! - `extract_fn_ptr_type`
59//!
60//! - Clang-compatible type model
61//! - `ClangDiscTy`
62//! - `canonicalize_c_type`
63//! - `to_clang_disc_ty`
64//!
65//! - Encoding
66//! - `PtrauthEncoder`
67//! - `encode_ty`
68//!
69//! ## Compatibility requirements
70//!
71//! Any changes to the encoding or hashing logic should be validated against Clang's
72//! discriminator computation. Divergence from Clang will result in incompatible
73//! pointer authentication values across language boundaries.
74//!
75//! This implementation intentionally approximates Clang's behavior for extern "C"
76//! function types only. It does NOT attempt to model full type system rules.
77
78use rustc_abi::ExternAbi;
79use rustc_middle::ty::{self, Instance, Ty, TyCtxt, Unnormalized};
80use rustc_session::PointerAuthSchema;
81use rustc_span::sym;
82
83use crate::ptrauth::llvm_siphash::llvm_pointer_auth_stable_siphash;
84
85/// Types that can serve as a source for function pointer type discrimination.
86///
87/// This trait abstracts over the different compiler representations from which
88/// a function signature can be obtained. Implementations construct the
89/// canonical `FnPtrTypeDiscriminatorInput` consumed by the discriminator
90/// computation.
91///
92/// This is intended primarily for ergonomic use at call sites, allowing code
93/// to compute discriminators directly from an `Instance`, `Ty`, or `FnSig`
94/// without manually constructing the intermediate representation.
95pub trait FnPtrDiscriminatorSource<'tcx>: Sized {
96 fn discriminator_input(self, tcx: TyCtxt<'tcx>) -> Option<FnPtrTypeDiscriminatorInput<'tcx>>;
97}
98
99/// Enables discriminator computation directly from Rust function types.
100///
101/// Accepts both:
102/// - `FnPtr`: actual function pointer types
103/// - `FnDef`: function items
104///
105/// FnDef is only accepted for convenience; the discriminator is still computed
106/// from the instantiated function signature.
107impl<'tcx> FnPtrDiscriminatorSource<'tcx> for Ty<'tcx> {
108 fn discriminator_input(self, tcx: TyCtxt<'tcx>) -> Option<FnPtrTypeDiscriminatorInput<'tcx>> {
109 let ty = extract_fn_ptr_type(tcx, self)?;
110
111 match ty.kind() {
112 ty::FnPtr(sig, header) => {
113 let sig = sig.skip_binder();
114 Some(FnPtrTypeDiscriminatorInput::from_sig_tys(sig, header))
115 }
116
117 ty::FnDef(def_id, args) => {
118 let sig = tcx.fn_sig(*def_id).instantiate(tcx, args.skip_binder()).skip_binder();
119
120 Some(FnPtrTypeDiscriminatorInput::from_sig(sig))
121 }
122
123 _ => None,
124 }
125 }
126}
127/// Enables discriminator computation directly from monomorphized function
128/// instances.
129///
130/// The instance's signature is instantiated using its generic arguments and
131/// normalized before constructing the canonical discriminator input.
132impl<'tcx> FnPtrDiscriminatorSource<'tcx> for Instance<'tcx> {
133 fn discriminator_input(self, tcx: TyCtxt<'tcx>) -> Option<FnPtrTypeDiscriminatorInput<'tcx>> {
134 let sig = tcx
135 .instantiate_and_normalize_erasing_regions(
136 self.args,
137 ty::TypingEnv::fully_monomorphized(),
138 tcx.fn_sig(self.def_id()),
139 )
140 .skip_binder();
141
142 Some(FnPtrTypeDiscriminatorInput::from_sig(sig))
143 }
144}
145/// Enables discriminator computation directly from instantiated function
146/// signatures.
147///
148/// The signature is assumed to already be instantiated and normalized.
149impl<'tcx> FnPtrDiscriminatorSource<'tcx> for ty::FnSig<'tcx> {
150 fn discriminator_input(self, _: TyCtxt<'tcx>) -> Option<FnPtrTypeDiscriminatorInput<'tcx>> {
151 Some(FnPtrTypeDiscriminatorInput::from_sig(self))
152 }
153}
154
155/// Computes the function pointer type discriminator directly from a supported
156/// source.
157///
158/// This is a convenience wrapper around
159/// `FnPtrDiscriminatorSource::discriminator_input` and
160/// `compute_fn_ptr_type_discriminator`.
161///
162/// Returns `None` if the supplied source does not represent a function pointer
163/// type (for example, a non-function `Ty`).
164pub fn ptrauth_compute_fn_ptr_type_discriminator_for<'tcx, S>(
165 tcx: TyCtxt<'tcx>,
166 source: S,
167) -> Option<u16>
168where
169 S: FnPtrDiscriminatorSource<'tcx>,
170{
171 let input = source.discriminator_input(tcx)?;
172 Some(compute_fn_ptr_type_discriminator(tcx, &input))
173}
174
175/// Clones a pointer authentication schema and updates its constant
176/// discriminator.
177///
178/// If `schema` is `Some`, the function computes a function pointer type
179/// discriminator from `source` and stores it in the cloned schema's
180/// `constant_discriminator` field.
181///
182/// If no discriminator can be computed (for example, because `source` does not
183/// represent a function pointer type), the schema is returned unchanged.
184///
185/// This is intended as a convenience helper for code generation sites that need
186/// to attach function pointer type discrimination to a generic schema before
187/// calling `get_fn_addr`.
188pub fn ptrauth_clone_discriminated_schema_for<'tcx, S>(
189 tcx: TyCtxt<'tcx>,
190 mut schema: Option<PointerAuthSchema>,
191 source: S,
192) -> Option<PointerAuthSchema>
193where
194 S: FnPtrDiscriminatorSource<'tcx>,
195{
196 if let Some(ref mut s) = schema {
197 if let Some(disc) = ptrauth_compute_fn_ptr_type_discriminator_for(tcx, source) {
198 s.constant_discriminator = disc;
199 }
200 }
201
202 schema
203}
204
205/// Canonical representation of a function signature used for pointer
206/// authentication discriminator generation.
207#[derive(Debug)]
208pub struct FnPtrTypeDiscriminatorInput<'tcx> {
209 inputs: &'tcx [Ty<'tcx>],
210 output: Ty<'tcx>,
211 abi: ExternAbi,
212 c_variadic: bool,
213}
214
215impl<'tcx> FnPtrTypeDiscriminatorInput<'tcx> {
216 fn from_sig(sig: ty::FnSig<'tcx>) -> Self {
217 FnPtrTypeDiscriminatorInput {
218 inputs: sig.inputs(),
219 output: sig.output(),
220 abi: sig.abi(),
221 c_variadic: sig.c_variadic(),
222 }
223 }
224
225 fn from_sig_tys(sig: ty::FnSigTys<TyCtxt<'tcx>>, header: &ty::FnHeader<TyCtxt<'tcx>>) -> Self {
226 FnPtrTypeDiscriminatorInput {
227 inputs: sig.inputs(),
228 output: sig.output(),
229 abi: header.abi(),
230 c_variadic: header.c_variadic(),
231 }
232 }
233}
234
235/// Unwraps optional function pointers and normalizes the type.
236///
237/// Only `Option<fn*>` is supported for nullability modeling, matching C ABI
238/// null pointer conventions.
239fn extract_fn_ptr_type<'tcx>(tcx: TyCtxt<'tcx>, mut ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
240 ty = tcx.normalize_erasing_regions(ty::TypingEnv::fully_monomorphized(), Unnormalized::new(ty));
241
242 loop {
243 match ty.kind() {
244 ty::Adt(def, args) if tcx.lang_items().option_type() == Some(def.did()) => {
245 ty = args.type_at(0);
246 continue;
247 }
248
249 ty::FnPtr(..) | ty::FnDef(..) => {
250 return Some(ty);
251 }
252
253 _ => return None,
254 }
255 }
256}
257
258/// Computes the Clang-compatible function pointer type discriminator.
259///
260/// This is the low-level discriminator computation routine operating on an
261/// already constructed `FnPtrTypeDiscriminatorInput`.
262fn compute_fn_ptr_type_discriminator<'tcx>(
263 tcx: TyCtxt<'tcx>,
264 input: &FnPtrTypeDiscriminatorInput<'tcx>,
265) -> u16 {
266 if !matches!(input.abi, ExternAbi::C { .. } | ExternAbi::System { .. }) {
267 return 0;
268 }
269
270 let mut enc = PtrauthEncoder::new();
271 enc.push(b'F');
272
273 encode_ty(&mut enc, tcx, input.output);
274
275 for &arg in input.inputs {
276 encode_ty(&mut enc, tcx, arg);
277 }
278
279 if input.c_variadic {
280 enc.push(b'z');
281 }
282
283 enc.push(b'E');
284
285 enc.finish()
286}
287
288// Clang disc type.
289#[derive(Debug)]
290enum ClangDiscTy<'tcx> {
291 Int,
292 Float(&'tcx ty::FloatTy),
293 Bool,
294 Char,
295
296 // Pointer-like types in the C ABI sense.
297 // This includes:
298 // - raw pointers (`*const T`, `*mut T`)
299 // - Rust references (`&T`, `&mut T`)
300 // - function pointers
301 // All collapse to a single Clang-compatible 'P' node.
302 Pointer,
303
304 Array { elem: Ty<'tcx> },
305
306 // FIXME(jchlands) Decide if to support Complex types in future. Clang has
307 // dedicated node for this `Type::Complex`, Rust does not. So we could match
308 // against a Tuple(FP_TYPE, FP_TYPE).
309 // Complex(Ty<'tcx>),
310 Vector { bytes: u64 },
311
312 EnumLikeInt,
313 AdtName(String),
314 Opaque,
315 Void,
316}
317
318// Canonicalize Option-wrapped pointer types used to model C nullable pointers.
319//
320// Rust and Clang should compute identical discriminators for equivalent C APIs.
321// Clang does not distinguish nullable from non-nullable pointer types when
322// computing function pointer authentication discriminators, so
323// `Option<fn>` and `Option<*mut T>` are encoded identically to their
324// underlying pointer types.
325//
326// Although `Option<*mut T>` is not considered FFI-safe by Rust and triggers the
327// `improper_ctypes`/`improper_ctypes_definitions` lints, this is a warning
328// rather than a hard error. Canonicalizing it here preserves Clang-compatible
329// discriminator computation.
330//
331// Please see the following tests for sample use cases:
332// pauth-fn-ptr-type-discrimination-option-callback.rs,
333// pauth-fn-ptr-type-discrimination-option-return.rs and pauth-fn-ptr-type-discrimination-option.rs
334fn canonicalize_c_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
335 if let ty::Adt(def, args) = ty.kind()
336 && tcx.is_diagnostic_item(sym::Option, def.did())
337 {
338 let inner = args.type_at(0);
339
340 match inner.kind() {
341 ty::FnPtr(..) | ty::RawPtr(..) => return inner,
342 _ => {}
343 }
344 }
345
346 ty
347}
348
349/// Lowers a Rust type into a Clang-compatible discriminator type.
350///
351/// This is not a full semantic translation of Rust types. It is a lossy mapping
352/// that intentionally matches Clang's function pointer authentication encoding
353/// rules where Rust has a direct language-level equivalent.
354///
355/// In particular C `_Complex`, without a canonical Rust equivalent, is not
356/// recognized. This avoids introducing heuristics for user-defined
357/// representations that may vary across codebases.
358///
359/// Important invariants:
360/// - All pointer-like types (Rust refs, raw pointers, fn pointers) collapse to
361/// `Pointer`.
362/// - Struct/union types are encoded using name only, not layout.
363/// - Enums are treated as integers.
364/// - SIMD types are encoded only by total byte size (no lane semantics).
365/// - No attempt is made to recognize user-defined representations of C
366/// `_Complex` types.
367/// This must remain in sync with Clang's `encodeTypeForFunctionPointerAuth`.
368fn to_clang_disc_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ClangDiscTy<'tcx> {
369 let ty = canonicalize_c_type(tcx, ty);
370 match ty.kind() {
371 // C void / Rust ()
372 _ if ty.is_unit() => ClangDiscTy::Void,
373
374 // scalars
375 ty::Bool => ClangDiscTy::Bool,
376 ty::Char => ClangDiscTy::Char,
377
378 ty::Int(_) | ty::Uint(_) => ClangDiscTy::Int,
379 ty::Float(f) => ClangDiscTy::Float(f),
380
381 // everything pointer-like collapses
382 ty::RawPtr(..) | ty::Ref(..) | ty::FnPtr(..) | ty::Dynamic(..) | ty::Slice(_) | ty::Str => {
383 ClangDiscTy::Pointer
384 }
385
386 // arrays ignore size
387 ty::Array(elem, _) => ClangDiscTy::Array { elem: *elem },
388
389 // enums to integer collapse
390 ty::Adt(def, _) if def.is_enum() => ClangDiscTy::EnumLikeInt,
391 // simd vectors
392 ty::Adt(def, args) if def.repr().simd() => {
393 // Clang encodes SIMD vectors by their total size
394 let input = ty::PseudoCanonicalInput {
395 typing_env: ty::TypingEnv::fully_monomorphized(),
396 value: ty,
397 };
398
399 let Ok(layout) = tcx.layout_of(input) else {
400 tcx.dcx().delayed_bug("could not compute SIMD layout");
401 return ClangDiscTy::Opaque;
402 };
403
404 let bytes = layout.size.bytes();
405
406 ClangDiscTy::Vector { bytes }
407 }
408 // structs/unions to name-based identity
409 ty::Adt(def, _) => {
410 let name = tcx.item_name(def.did()).to_string();
411 ClangDiscTy::AdtName(name)
412 }
413
414 ty::Foreign(_) => ClangDiscTy::Opaque,
415
416 _ => ClangDiscTy::Opaque,
417 }
418}
419
420// Encoder
421struct PtrauthEncoder {
422 buf: Vec<u8>,
423}
424
425impl PtrauthEncoder {
426 fn new() -> Self {
427 Self { buf: Vec::new() }
428 }
429
430 fn push(&mut self, b: u8) {
431 self.buf.push(b);
432 }
433
434 fn push_str(&mut self, s: &str) {
435 self.buf.extend_from_slice(s.as_bytes());
436 }
437
438 fn finish(&self) -> u16 {
439 llvm_pointer_auth_stable_siphash(&self.buf)
440 }
441}
442
443/// Encodes a ClangDiscTy into the discriminator byte stream.
444///
445/// This format is intended to be bit-for-bit compatible with Clang's
446/// `encodeTypeForFunctionPointerAuth`.
447fn encode_ty<'tcx>(enc: &mut PtrauthEncoder, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) {
448 let cty = to_clang_disc_ty(tcx, ty);
449
450 match cty {
451 // scalars
452 ClangDiscTy::Bool | ClangDiscTy::Char | ClangDiscTy::Int => enc.push(b'i'),
453
454 ClangDiscTy::Float(f) => match f.bit_width() {
455 16 => enc.push_str("Dh"),
456 32 => enc.push(b'f'),
457 64 => enc.push(b'd'),
458 128 => enc.push(b'g'),
459 _ => enc.push(b'?'),
460 },
461
462 ClangDiscTy::Void => enc.push(b'v'),
463
464 // pointer boundary (NO RECURSION)
465 ClangDiscTy::Pointer => enc.push(b'P'),
466
467 // arrays ignore size
468 ClangDiscTy::Array { elem } => {
469 enc.push(b'A');
470 encode_ty(enc, tcx, elem);
471 }
472
473 // enums collapse
474 ClangDiscTy::EnumLikeInt => enc.push(b'i'),
475
476 // ADT identity
477 ClangDiscTy::AdtName(name) => {
478 enc.push_str(&name.len().to_string());
479 enc.push_str(&name);
480 }
481
482 ClangDiscTy::Opaque => enc.push(b'?'),
483
484 ClangDiscTy::Vector { bytes } => {
485 enc.push_str("Dv");
486 enc.push_str(&bytes.to_string());
487 }
488 }
489}