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 let hash = enc.finish();
286
287 hash.into()
288}
289
290// Clang disc type.
291#[derive(Debug)]
292enum ClangDiscTy<'tcx> {
293 Int,
294 Float(&'tcx ty::FloatTy),
295 Bool,
296 Char,
297
298 // Pointer-like types in the C ABI sense.
299 // This includes:
300 // - raw pointers (`*const T`, `*mut T`)
301 // - Rust references (`&T`, `&mut T`)
302 // - function pointers
303 // All collapse to a single Clang-compatible 'P' node.
304 Pointer,
305
306 Array { elem: Ty<'tcx> },
307
308 // FIXME(jchlands) Decide if to support Complex types in future. Clang has
309 // dedicated node for this `Type::Complex`, Rust does not. So we could match
310 // against a Tuple(FP_TYPE, FP_TYPE).
311 // Complex(Ty<'tcx>),
312 Vector { bytes: u64 },
313
314 EnumLikeInt,
315 AdtName(String),
316 Opaque,
317 Void,
318}
319
320// Canonicalize Option-wrapped pointer types used to model C nullable pointers.
321//
322// Rust and Clang should compute identical discriminators for equivalent C APIs.
323// Clang does not distinguish nullable from non-nullable pointer types when
324// computing function pointer authentication discriminators, so
325// `Option<fn>` and `Option<*mut T>` are encoded identically to their
326// underlying pointer types.
327//
328// Although `Option<*mut T>` is not considered FFI-safe by Rust and triggers the
329// `improper_ctypes`/`improper_ctypes_definitions` lints, this is a warning
330// rather than a hard error. Canonicalizing it here preserves Clang-compatible
331// discriminator computation.
332//
333// Please see the following tests for sample use cases:
334// pauth-fn-ptr-type-discrimination-option-callback.rs,
335// pauth-fn-ptr-type-discrimination-option-return.rs and pauth-fn-ptr-type-discrimination-option.rs
336fn canonicalize_c_type<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
337 if let ty::Adt(def, args) = ty.kind()
338 && tcx.is_diagnostic_item(sym::Option, def.did())
339 {
340 let inner = args.type_at(0);
341
342 match inner.kind() {
343 ty::FnPtr(..) | ty::RawPtr(..) => return inner,
344 _ => {}
345 }
346 }
347
348 ty
349}
350
351/// Lowers a Rust type into a Clang-compatible discriminator type.
352///
353/// This is not a full semantic translation of Rust types. It is a lossy mapping
354/// that intentionally matches Clang's function pointer authentication encoding
355/// rules where Rust has a direct language-level equivalent.
356///
357/// In particular C `_Complex`, without a canonical Rust equivalent, is not
358/// recognized. This avoids introducing heuristics for user-defined
359/// representations that may vary across codebases.
360///
361/// Important invariants:
362/// - All pointer-like types (Rust refs, raw pointers, fn pointers) collapse to
363/// `Pointer`.
364/// - Struct/union types are encoded using name only, not layout.
365/// - Enums are treated as integers.
366/// - SIMD types are encoded only by total byte size (no lane semantics).
367/// - No attempt is made to recognize user-defined representations of C
368/// `_Complex` types.
369/// This must remain in sync with Clang's `encodeTypeForFunctionPointerAuth`.
370fn to_clang_disc_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> ClangDiscTy<'tcx> {
371 let ty = canonicalize_c_type(tcx, ty);
372 match ty.kind() {
373 // C void / Rust ()
374 _ if ty.is_unit() => ClangDiscTy::Void,
375
376 // scalars
377 ty::Bool => ClangDiscTy::Bool,
378 ty::Char => ClangDiscTy::Char,
379
380 ty::Int(_) | ty::Uint(_) => ClangDiscTy::Int,
381 ty::Float(f) => ClangDiscTy::Float(f),
382
383 // everything pointer-like collapses
384 ty::RawPtr(..) | ty::Ref(..) | ty::FnPtr(..) | ty::Dynamic(..) | ty::Slice(_) | ty::Str => {
385 ClangDiscTy::Pointer
386 }
387
388 // arrays ignore size
389 ty::Array(elem, _) => ClangDiscTy::Array { elem: *elem },
390
391 // enums to integer collapse
392 ty::Adt(def, _) if def.is_enum() => ClangDiscTy::EnumLikeInt,
393 // simd vectors
394 ty::Adt(def, args) if def.repr().simd() => {
395 // Clang encodes SIMD vectors by their total size
396 let input = ty::PseudoCanonicalInput {
397 typing_env: ty::TypingEnv::fully_monomorphized(),
398 value: ty,
399 };
400
401 let Ok(layout) = tcx.layout_of(input) else {
402 tcx.dcx().delayed_bug("could not compute SIMD layout");
403 return ClangDiscTy::Opaque;
404 };
405
406 let bytes = layout.size.bytes();
407
408 ClangDiscTy::Vector { bytes }
409 }
410 // structs/unions to name-based identity
411 ty::Adt(def, _) => {
412 let name = tcx.item_name(def.did()).to_string();
413 ClangDiscTy::AdtName(name)
414 }
415
416 ty::Foreign(_) => ClangDiscTy::Opaque,
417
418 _ => ClangDiscTy::Opaque,
419 }
420}
421
422// Encoder
423struct PtrauthEncoder {
424 buf: Vec<u8>,
425}
426
427impl PtrauthEncoder {
428 fn new() -> Self {
429 Self { buf: Vec::new() }
430 }
431
432 fn push(&mut self, b: u8) {
433 self.buf.push(b);
434 }
435
436 fn push_str(&mut self, s: &str) {
437 self.buf.extend_from_slice(s.as_bytes());
438 }
439
440 fn finish(&self) -> u16 {
441 llvm_pointer_auth_stable_siphash(&self.buf)
442 }
443}
444
445/// Encodes a ClangDiscTy into the discriminator byte stream.
446///
447/// This format is intended to be bit-for-bit compatible with Clang's
448/// `encodeTypeForFunctionPointerAuth`.
449fn encode_ty<'tcx>(enc: &mut PtrauthEncoder, tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) {
450 let cty = to_clang_disc_ty(tcx, ty);
451
452 match cty {
453 // scalars
454 ClangDiscTy::Bool | ClangDiscTy::Char | ClangDiscTy::Int => enc.push(b'i'),
455
456 ClangDiscTy::Float(f) => match f.bit_width() {
457 16 => enc.push_str("Dh"),
458 32 => enc.push(b'f'),
459 64 => enc.push(b'd'),
460 128 => enc.push(b'g'),
461 _ => enc.push(b'?'),
462 },
463
464 ClangDiscTy::Void => enc.push(b'v'),
465
466 // pointer boundary (NO RECURSION)
467 ClangDiscTy::Pointer => enc.push(b'P'),
468
469 // arrays ignore size
470 ClangDiscTy::Array { elem } => {
471 enc.push(b'A');
472 encode_ty(enc, tcx, elem);
473 }
474
475 // enums collapse
476 ClangDiscTy::EnumLikeInt => enc.push(b'i'),
477
478 // ADT identity
479 ClangDiscTy::AdtName(name) => {
480 enc.push_str(&name.len().to_string());
481 enc.push_str(&name);
482 }
483
484 ClangDiscTy::Opaque => enc.push(b'?'),
485
486 ClangDiscTy::Vector { bytes } => {
487 enc.push_str("Dv");
488 enc.push_str(&bytes.to_string());
489 }
490 }
491}