Skip to content

Commit ef6b34d

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 951b670 commit ef6b34d

2 files changed

Lines changed: 166 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
@@ -350,6 +350,7 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> {
350350
alloc.inner(),
351351
IsStatic::No,
352352
IsInitOrFini::No,
353+
None,
353354
);
354355
let alloc = alloc.inner();
355356
let value = match alloc.mutability {
@@ -387,6 +388,7 @@ impl<'ll, 'tcx> ConstCodegenMethods for CodegenCx<'ll, 'tcx> {
387388
alloc.inner(),
388389
IsStatic::No,
389390
IsInitOrFini::No,
391+
None,
390392
);
391393
self.static_addr_of_impl(init, alloc.inner().align, None)
392394
}

compiler/rustc_codegen_llvm/src/consts.rs

Lines changed: 164 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::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) = 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+
fn_ptr_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,24 @@ 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 = fn_ptr_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.
269+
if let (Some(schema), Some(discr)) = (schema.as_mut(), discr)
270+
&& is_init_fini == IsInitOrFini::No
271+
{
272+
schema.constant_discriminator = *discr as u16;
273+
}
132274
llvals.push(cx.scalar_to_backend_with_pac(
133275
InterpScalar::from_pointer(Pointer::new(prov, Size::from_bytes(ptr_offset)), &cx.tcx),
134276
Scalar::Initialized {
@@ -160,6 +302,15 @@ fn codegen_static_initializer<'ll, 'tcx>(
160302
cx: &CodegenCx<'ll, 'tcx>,
161303
def_id: DefId,
162304
) -> Result<(&'ll Value, ConstAllocation<'tcx>), ErrorHandled> {
305+
let fn_ptr_discriminators = if cx.sess().pointer_authentication_fn_ptr_type_discrimination() {
306+
let instance = Instance::mono(cx.tcx, def_id);
307+
let ty = instance.ty(cx.tcx, cx.typing_env());
308+
309+
Some(collect_fn_ptr_discriminators(cx.tcx, cx.typing_env(), ty))
310+
} else {
311+
None
312+
};
313+
163314
let alloc = cx.tcx.eval_static_initializer(def_id)?;
164315
let attrs = cx.tcx.codegen_fn_attrs(def_id);
165316
// FIXME(jchlanda) Decide if this could be better served by `ctor` crate. See the discussion
@@ -175,7 +326,16 @@ fn codegen_static_initializer<'ll, 'tcx>(
175326
}
176327
})
177328
.unwrap_or(IsInitOrFini::No);
178-
Ok((const_alloc_to_llvm(cx, alloc.inner(), IsStatic::Yes, is_in_init_fini), alloc))
329+
Ok((
330+
const_alloc_to_llvm(
331+
cx,
332+
alloc.inner(),
333+
IsStatic::Yes,
334+
is_in_init_fini,
335+
fn_ptr_discriminators.as_ref(),
336+
),
337+
alloc,
338+
))
179339
}
180340

181341
fn set_global_alignment<'ll>(cx: &CodegenCx<'ll, '_>, gv: &'ll Value, mut align: Align) {
@@ -837,7 +997,7 @@ impl<'ll> StaticCodegenMethods for CodegenCx<'ll, '_> {
837997
fn static_addr_of(&self, alloc: ConstAllocation<'_>, kind: Option<&str>) -> &'ll Value {
838998
// FIXME: should we cache `const_alloc_to_llvm` to avoid repeating this for the
839999
// same `ConstAllocation`?
840-
let cv = const_alloc_to_llvm(self, alloc.inner(), IsStatic::No, IsInitOrFini::No);
1000+
let cv = const_alloc_to_llvm(self, alloc.inner(), IsStatic::No, IsInitOrFini::No, None);
8411001

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

0 commit comments

Comments
 (0)