Skip to content

Commit f77edea

Browse files
committed
[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.
1 parent af72799 commit f77edea

2 files changed

Lines changed: 173 additions & 4 deletions

File tree

compiler/rustc_codegen_llvm/src/common.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,7 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> {
355355
alloc.inner(),
356356
IsStatic::No,
357357
IsInitOrFini::No,
358+
None,
358359
);
359360
let alloc = alloc.inner();
360361
let value = match alloc.mutability {
@@ -394,6 +395,7 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> {
394395
alloc.inner(),
395396
IsStatic::No,
396397
IsInitOrFini::No,
398+
None,
397399
);
398400
self.static_addr_of_impl(init, alloc.inner().align, None)
399401
}

compiler/rustc_codegen_llvm/src/consts.rs

Lines changed: 171 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::ops::Range;
33
use rustc_abi::{Align, ExternAbi, HasDataLayout, Primitive, Scalar, Size, WrappingRange};
44
use rustc_codegen_ssa::common;
55
use rustc_codegen_ssa::traits::*;
6+
use rustc_data_structures::fx::FxHashMap;
67
use rustc_hir::LangItem;
78
use rustc_hir::attrs::Linkage;
89
use rustc_hir::def::DefKind;
@@ -13,8 +14,9 @@ use rustc_middle::mir::interpret::{
1314
read_target_uint,
1415
};
1516
use rustc_middle::mono::MonoItem;
17+
use rustc_middle::ptrauth::ptrauth_compute_fn_ptr_type_discriminator_for;
1618
use rustc_middle::ty::layout::{HasTypingEnv, LayoutOf};
17-
use rustc_middle::ty::{self, Instance};
19+
use rustc_middle::ty::{self, Instance, Ty, TyCtxt};
1820
use rustc_middle::{bug, span_bug};
1921
use rustc_span::Symbol;
2022
use rustc_target::spec::Arch;
@@ -37,11 +39,141 @@ pub(crate) enum IsInitOrFini {
3739
Yes,
3840
No,
3941
}
42+
43+
/// Maps offsets within a static allocation to the function pointer
44+
/// discriminator that should be applied when authenticating the relocation
45+
/// emitted at that offset.
46+
///
47+
/// Offsets are relative to the beginning of the allocation.
48+
pub(crate) struct FnPtrDiscriminatorsAtOffset {
49+
map: FxHashMap<Size, u64>,
50+
}
51+
52+
/// Recursively walks a type layout and records the offsets of all extern "C"
53+
/// function pointer fields together with their computed type discriminators.
54+
///
55+
/// Traversal currently supports:
56+
/// - direct function pointers
57+
/// - transparent wrappers
58+
/// - structs
59+
/// - tuples
60+
/// - arrays
61+
///
62+
/// Offsets are accumulated relative to the containing object.
63+
fn collect_fn_ptr_discriminators<'tcx>(
64+
tcx: TyCtxt<'tcx>,
65+
typing_env: ty::TypingEnv<'tcx>,
66+
ty: Ty<'tcx>,
67+
) -> FnPtrDiscriminatorsAtOffset {
68+
let mut map = FxHashMap::default();
69+
70+
collect_fn_ptr_discriminators_inner(tcx, typing_env, ty, Size::ZERO, &mut map);
71+
72+
FnPtrDiscriminatorsAtOffset { map }
73+
}
74+
75+
fn collect_fn_ptr_discriminators_inner<'tcx>(
76+
tcx: TyCtxt<'tcx>,
77+
typing_env: ty::TypingEnv<'tcx>,
78+
ty: Ty<'tcx>,
79+
base_offset: Size,
80+
map: &mut FxHashMap<Size, u64>,
81+
) {
82+
// Direct function pointer.
83+
if let Some(disc) = ptrauth_compute_fn_ptr_type_discriminator_for(tcx, ty) {
84+
map.insert(base_offset, disc.into());
85+
86+
return;
87+
}
88+
89+
match ty.kind() {
90+
ty::Adt(def, args) if def.is_struct() => {
91+
let Ok(layout) = tcx.layout_of(typing_env.as_query_input(ty)) else {
92+
return;
93+
};
94+
95+
let variant = def.non_enum_variant();
96+
97+
for (idx, field_def) in variant.fields.iter_enumerated() {
98+
let field_ty = tcx.normalize_erasing_regions(typing_env, field_def.ty(tcx, args));
99+
100+
let field_offset = layout.fields.offset(idx.into());
101+
102+
collect_fn_ptr_discriminators_inner(
103+
tcx,
104+
typing_env,
105+
field_ty,
106+
base_offset + field_offset,
107+
map,
108+
);
109+
}
110+
}
111+
ty::Tuple(fields) => {
112+
let Ok(layout) = tcx.layout_of(typing_env.as_query_input(ty)) else {
113+
return;
114+
};
115+
116+
for (idx, field_ty) in fields.iter().enumerate() {
117+
let field_offset = layout.fields.offset(idx);
118+
119+
collect_fn_ptr_discriminators_inner(
120+
tcx,
121+
typing_env,
122+
field_ty,
123+
base_offset + field_offset,
124+
map,
125+
);
126+
}
127+
}
128+
ty::Array(elem_ty, len) => {
129+
let count = match len.try_to_target_usize(tcx) {
130+
Some(v) => v,
131+
None => return,
132+
};
133+
134+
let Ok(elem_layout) = tcx.layout_of(typing_env.as_query_input(*elem_ty)) else {
135+
return;
136+
};
137+
138+
let stride = elem_layout.size;
139+
140+
// Collect discriminator of one element, so we don't have to recompute it for all the
141+
// elements in the array.
142+
let mut elem_map = FxHashMap::default();
143+
144+
collect_fn_ptr_discriminators_inner(
145+
tcx,
146+
typing_env,
147+
*elem_ty,
148+
Size::ZERO,
149+
&mut elem_map,
150+
);
151+
152+
// SAFETY: We immediately collect into a Vec and sort by offset.
153+
// The HashMap iteration order is irrelevant and must not affect determinism.
154+
#[allow(rustc::potential_query_instability)]
155+
let mut entries: Vec<(Size, u64)> = elem_map.into_iter().collect();
156+
entries.sort_unstable_by_key(|(offset, _)| *offset);
157+
158+
// Replicate for every array slot.
159+
for i in 0..count {
160+
let elem_base = base_offset + stride * i;
161+
162+
for (inner_offset, discr) in entries.iter().copied() {
163+
map.insert(elem_base + inner_offset, discr);
164+
}
165+
}
166+
}
167+
_ => {}
168+
}
169+
}
170+
40171
pub(crate) fn const_alloc_to_llvm<'ll>(
41172
cx: &CodegenCx<'ll, '_>,
42173
alloc: &Allocation,
43174
is_static: IsStatic,
44175
is_init_fini: IsInitOrFini,
176+
ptrauth_discriminators: Option<&FnPtrDiscriminatorsAtOffset>,
45177
) -> &'ll Value {
46178
// We expect that callers of const_alloc_to_llvm will instead directly codegen a pointer or
47179
// integer for any &ZST where the ZST is a constant (i.e. not a static). We should never be
@@ -121,14 +253,25 @@ pub(crate) fn const_alloc_to_llvm<'ll>(
121253
as u64;
122254

123255
let address_space = cx.tcx.global_alloc(prov.alloc_id()).address_space(cx);
124-
let schema = if cx.sess().pointer_authentication() {
256+
let mut schema = if cx.sess().pointer_authentication() {
125257
match is_init_fini {
126258
IsInitOrFini::Yes => cx.sess().pointer_authentication_init_fini(),
127259
IsInitOrFini::No => cx.sess().pointer_authentication_functions(),
128260
}
129261
} else {
130262
None
131263
};
264+
let discr = ptrauth_discriminators
265+
.as_ref()
266+
.and_then(|m| m.map.get(&Size::from_bytes(offset as u64)));
267+
268+
// Init/fini entries must not participate in function pointer type discrimination, they use
269+
// a dedicated constant value (ptrauth_string_discriminator("init_fini") which is: 0xd9d4).
270+
if let (Some(schema), Some(discr)) = (schema.as_mut(), discr)
271+
&& is_init_fini == IsInitOrFini::No
272+
{
273+
schema.constant_discriminator = *discr as u16;
274+
}
132275
llvals.push(cx.scalar_to_backend_with_pac(
133276
InterpScalar::from_pointer(Pointer::new(prov, Size::from_bytes(ptr_offset)), &cx.tcx),
134277
Scalar::Initialized {
@@ -160,6 +303,15 @@ fn codegen_static_initializer<'ll, 'tcx>(
160303
cx: &CodegenCx<'ll, 'tcx>,
161304
def_id: DefId,
162305
) -> Result<(&'ll Value, ConstAllocation<'tcx>), ErrorHandled> {
306+
let ptrauth_discriminators = if cx.sess().pointer_authentication_fn_ptr_type_discrimination() {
307+
let instance = Instance::mono(cx.tcx, def_id);
308+
let ty = instance.ty(cx.tcx, cx.typing_env());
309+
310+
Some(collect_fn_ptr_discriminators(cx.tcx, cx.typing_env(), ty))
311+
} else {
312+
None
313+
};
314+
163315
let alloc = cx.tcx.eval_static_initializer(def_id)?;
164316
let attrs = cx.tcx.codegen_fn_attrs(def_id);
165317
// FIXME(jchlanda) Decide if this could be better served by `ctor` crate. See the discussion
@@ -175,7 +327,16 @@ fn codegen_static_initializer<'ll, 'tcx>(
175327
}
176328
})
177329
.unwrap_or(IsInitOrFini::No);
178-
Ok((const_alloc_to_llvm(cx, alloc.inner(), IsStatic::Yes, is_in_init_fini), alloc))
330+
Ok((
331+
const_alloc_to_llvm(
332+
cx,
333+
alloc.inner(),
334+
IsStatic::Yes,
335+
is_in_init_fini,
336+
ptrauth_discriminators.as_ref(),
337+
),
338+
alloc,
339+
))
179340
}
180341

181342
fn set_global_alignment<'ll>(cx: &CodegenCx<'ll, '_>, gv: &'ll Value, mut align: Align) {
@@ -837,7 +998,13 @@ impl<'ll> StaticCodegenMethods for CodegenCx<'ll, '_> {
837998
fn static_addr_of(&self, alloc: ConstAllocation<'_>, kind: Option<&str>) -> &'ll Value {
838999
// FIXME: should we cache `const_alloc_to_llvm` to avoid repeating this for the
8391000
// same `ConstAllocation`?
840-
let cv = const_alloc_to_llvm(self, alloc.inner(), IsStatic::No, IsInitOrFini::No);
1001+
// FIXME(jchlanda): Add support for pointer authentication type discrimination.
1002+
// `static_addr_of` only receives a `ConstAllocation`, so it does not have the type
1003+
// information needed to compute function pointer type discriminators. We'll likely need
1004+
// to either compute the discriminator map at callers that still know the Rust type, or
1005+
// extend this API to accept the required type information. See
1006+
// `codegen_static_initializer` for an example of how the discriminator map is computed.
1007+
let cv = const_alloc_to_llvm(self, alloc.inner(), IsStatic::No, IsInitOrFini::No, None);
8411008

8421009
let gv = self.static_addr_of_impl(cv, alloc.inner().align, kind);
8431010
// static_addr_of_impl returns the bare global variable, which might not be in the default

0 commit comments

Comments
 (0)