Skip to main content

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(#[automatically_derived]
impl<'tcx> ::core::fmt::Debug for FnPtrTypeDiscriminatorInput<'tcx> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field4_finish(f,
            "FnPtrTypeDiscriminatorInput", "inputs", &self.inputs, "output",
            &self.output, "abi", &self.abi, "c_variadic", &&self.c_variadic)
    }
}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 !#[allow(non_exhaustive_omitted_patterns)] match input.abi {
    ExternAbi::C { .. } | ExternAbi::System { .. } => true,
    _ => false,
}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(#[automatically_derived]
impl<'tcx> ::core::fmt::Debug for ClangDiscTy<'tcx> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            ClangDiscTy::Int => ::core::fmt::Formatter::write_str(f, "Int"),
            ClangDiscTy::Float(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Float",
                    &__self_0),
            ClangDiscTy::Bool => ::core::fmt::Formatter::write_str(f, "Bool"),
            ClangDiscTy::Char => ::core::fmt::Formatter::write_str(f, "Char"),
            ClangDiscTy::Pointer =>
                ::core::fmt::Formatter::write_str(f, "Pointer"),
            ClangDiscTy::Array { elem: __self_0 } =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f, "Array",
                    "elem", &__self_0),
            ClangDiscTy::Vector { bytes: __self_0 } =>
                ::core::fmt::Formatter::debug_struct_field1_finish(f,
                    "Vector", "bytes", &__self_0),
            ClangDiscTy::EnumLikeInt =>
                ::core::fmt::Formatter::write_str(f, "EnumLikeInt"),
            ClangDiscTy::AdtName(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "AdtName", &__self_0),
            ClangDiscTy::Opaque =>
                ::core::fmt::Formatter::write_str(f, "Opaque"),
            ClangDiscTy::Void => ::core::fmt::Formatter::write_str(f, "Void"),
        }
    }
}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}