Skip to content

Commit 307a0aa

Browse files
committed
Implemented padding randomization for -Z randomize-layout
1 parent 9b9ea86 commit 307a0aa

File tree

6 files changed

+180
-48
lines changed

6 files changed

+180
-48
lines changed

compiler/rustc_abi/src/layout.rs

+65-19
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
};
99

1010
#[cfg(feature = "randomize")]
11-
use rand::{seq::SliceRandom, SeedableRng};
11+
use rand::{seq::SliceRandom, Rng, SeedableRng};
1212
#[cfg(feature = "randomize")]
1313
use rand_xoshiro::Xoshiro128StarStar;
1414

@@ -61,18 +61,30 @@ pub trait LayoutCalculator {
6161
}
6262
}
6363

64-
fn univariant<'a, V: Idx, F: Deref<Target = &'a LayoutS<V>> + Debug>(
64+
fn univariant<'a, V, F, N>(
6565
&self,
6666
dl: &TargetDataLayout,
6767
fields: &[F],
6868
repr: &ReprOptions,
6969
kind: StructKind,
70-
) -> Option<LayoutS<V>> {
70+
option_niche_guaranteed: N,
71+
) -> Option<LayoutS<V>>
72+
where
73+
V: Idx,
74+
F: Deref<Target = &'a LayoutS<V>> + Debug,
75+
N: Fn(&Self) -> bool + Copy,
76+
{
7177
let pack = repr.pack;
7278
let mut align = if pack.is_some() { dl.i8_align } else { dl.aggregate_align };
7379
let mut inverse_memory_index: Vec<u32> = (0..fields.len() as u32).collect();
74-
let optimize = !repr.inhibit_struct_field_reordering_opt();
75-
if optimize {
80+
81+
// `ReprOptions.layout_seed` is a deterministic seed that we can use to
82+
// randomize field ordering with
83+
#[cfg(feature = "randomize")]
84+
let mut rng = Xoshiro128StarStar::seed_from_u64(repr.field_shuffle_seed);
85+
86+
let can_optimize = !repr.inhibit_struct_field_reordering_opt();
87+
if can_optimize {
7688
let end =
7789
if let StructKind::MaybeUnsized = kind { fields.len() - 1 } else { fields.len() };
7890
let optimizing = &mut inverse_memory_index[..end];
@@ -94,16 +106,11 @@ pub trait LayoutCalculator {
94106
// the field ordering to try and catch some code making assumptions about layouts
95107
// we don't guarantee
96108
if repr.can_randomize_type_layout() && cfg!(feature = "randomize") {
109+
// Shuffle the ordering of the fields
97110
#[cfg(feature = "randomize")]
98-
{
99-
// `ReprOptions.layout_seed` is a deterministic seed that we can use to
100-
// randomize field ordering with
101-
let mut rng = Xoshiro128StarStar::seed_from_u64(repr.field_shuffle_seed);
111+
optimizing.shuffle(&mut rng);
102112

103-
// Shuffle the ordering of the fields
104-
optimizing.shuffle(&mut rng);
105-
}
106-
// Otherwise we just leave things alone and actually optimize the type's fields
113+
// Otherwise we just leave things alone and actually optimize the type's fields
107114
} else {
108115
match kind {
109116
StructKind::AlwaysSized | StructKind::MaybeUnsized => {
@@ -173,6 +180,32 @@ pub trait LayoutCalculator {
173180
offset = offset.align_to(field_align.abi);
174181
align = align.max(field_align);
175182

183+
// If `-Z randomize-layout` is enabled, we pad each field by a multiple of its alignment
184+
// If layout randomization is disabled, we don't pad it by anything and if it is
185+
// we multiply the field's alignment by anything from zero to the user provided
186+
// maximum multiple (defaults to three)
187+
//
188+
// When `-Z randomize-layout` is enabled that doesn't necessarily mean we can
189+
// go ham on every type that at first glance looks valid for layout optimization.
190+
// `Option` specifically has layout guarantees when it has specific `T` substitutions,
191+
// such as `Option<NonNull<_>>` or `Option<NonZeroUsize>` both being exactly one `usize`
192+
// large. As such, we have to ensure that the type doesn't guarantee niche optimization
193+
// with the current payload
194+
#[cfg(feature = "randomize")]
195+
if repr.can_randomize_type_layout() && !option_niche_guaranteed(self) {
196+
let align_bytes = field_align.abi.bytes();
197+
let random_padding = align_bytes
198+
.checked_mul(rng.gen_range(0..=repr.random_padding_max_factor as u64))
199+
.unwrap_or(align_bytes);
200+
201+
// Attempt to add our extra padding, defaulting to the type's alignment
202+
if let Some(randomized_offset) =
203+
offset.checked_add(Size::from_bytes(random_padding), dl)
204+
{
205+
offset = randomized_offset;
206+
}
207+
}
208+
176209
debug!("univariant offset: {:?} field: {:#?}", offset, field);
177210
offsets[i as usize] = offset;
178211

@@ -199,7 +232,7 @@ pub trait LayoutCalculator {
199232
// Field 5 would be the first element, so memory_index is i:
200233
// Note: if we didn't optimize, it's already right.
201234
let memory_index =
202-
if optimize { invert_mapping(&inverse_memory_index) } else { inverse_memory_index };
235+
if can_optimize { invert_mapping(&inverse_memory_index) } else { inverse_memory_index };
203236
let size = min_size.align_to(align.abi);
204237
let mut abi = Abi::Aggregate { sized };
205238
// Unpack newtype ABIs and find scalar pairs.
@@ -216,7 +249,7 @@ pub trait LayoutCalculator {
216249
match field.abi {
217250
// For plain scalars, or vectors of them, we can't unpack
218251
// newtypes for `#[repr(C)]`, as that affects C ABIs.
219-
Abi::Scalar(_) | Abi::Vector { .. } if optimize => {
252+
Abi::Scalar(_) | Abi::Vector { .. } if can_optimize => {
220253
abi = field.abi;
221254
}
222255
// But scalar pairs are Rust-specific and get
@@ -290,7 +323,7 @@ pub trait LayoutCalculator {
290323
}
291324
}
292325

293-
fn layout_of_struct_or_enum<'a, V: Idx, F: Deref<Target = &'a LayoutS<V>> + Debug>(
326+
fn layout_of_struct_or_enum<'a, V, F, N>(
294327
&self,
295328
repr: &ReprOptions,
296329
variants: &IndexVec<V, Vec<F>>,
@@ -301,7 +334,13 @@ pub trait LayoutCalculator {
301334
discriminants: impl Iterator<Item = (V, i128)>,
302335
niche_optimize_enum: bool,
303336
always_sized: bool,
304-
) -> Option<LayoutS<V>> {
337+
option_niche_guaranteed: N,
338+
) -> Option<LayoutS<V>>
339+
where
340+
V: Idx,
341+
F: Deref<Target = &'a LayoutS<V>> + Debug,
342+
N: Fn(&Self) -> bool + Copy,
343+
{
305344
let dl = self.current_data_layout();
306345
let dl = dl.borrow();
307346

@@ -354,7 +393,7 @@ pub trait LayoutCalculator {
354393
if !always_sized { StructKind::MaybeUnsized } else { StructKind::AlwaysSized }
355394
};
356395

357-
let mut st = self.univariant(dl, &variants[v], repr, kind)?;
396+
let mut st = self.univariant(dl, &variants[v], repr, kind, option_niche_guaranteed)?;
358397
st.variants = Variants::Single { index: v };
359398

360399
if is_unsafe_cell {
@@ -457,7 +496,13 @@ pub trait LayoutCalculator {
457496
let mut variant_layouts = variants
458497
.iter_enumerated()
459498
.map(|(j, v)| {
460-
let mut st = self.univariant(dl, v, repr, StructKind::AlwaysSized)?;
499+
let mut st = self.univariant(
500+
dl,
501+
v,
502+
repr,
503+
StructKind::AlwaysSized,
504+
option_niche_guaranteed,
505+
)?;
461506
st.variants = Variants::Single { index: j };
462507

463508
align = align.max(st.align);
@@ -650,6 +695,7 @@ pub trait LayoutCalculator {
650695
field_layouts,
651696
repr,
652697
StructKind::Prefixed(min_ity.size(), prefix_align),
698+
option_niche_guaranteed,
653699
)?;
654700
st.variants = Variants::Single { index: i };
655701
// Find the first field we can't move later

compiler/rustc_abi/src/lib.rs

+5-2
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ pub struct ReprOptions {
8383
/// Everything's a tradeoff, a `u64` seed should be sufficient for our
8484
/// purposes (primarily `-Z randomize-layout`)
8585
pub field_shuffle_seed: u64,
86+
pub random_padding_max_factor: u8,
8687
}
8788

8889
impl ReprOptions {
@@ -139,8 +140,10 @@ impl ReprOptions {
139140
/// Returns `true` if this type is valid for reordering and `-Z randomize-layout`
140141
/// was enabled for its declaration crate
141142
pub fn can_randomize_type_layout(&self) -> bool {
142-
!self.inhibit_struct_field_reordering_opt()
143-
&& self.flags.contains(ReprFlags::RANDOMIZE_LAYOUT)
143+
self.flags.contains(ReprFlags::RANDOMIZE_LAYOUT)
144+
&& !self.flags.contains(ReprFlags::IS_TRANSPARENT)
145+
&& !self.inhibit_struct_field_reordering_opt()
146+
&& !self.inhibit_enum_layout_opt()
144147
}
145148

146149
/// Returns `true` if this `#[repr()]` should inhibit union ABI optimisations.

compiler/rustc_middle/src/ty/mod.rs

+18-23
Original file line numberDiff line numberDiff line change
@@ -224,11 +224,7 @@ pub struct MainDefinition {
224224

225225
impl MainDefinition {
226226
pub fn opt_fn_def_id(self) -> Option<DefId> {
227-
if let Res::Def(DefKind::Fn, def_id) = self.res {
228-
Some(def_id)
229-
} else {
230-
None
231-
}
227+
if let Res::Def(DefKind::Fn, def_id) = self.res { Some(def_id) } else { None }
232228
}
233229
}
234230

@@ -957,19 +953,11 @@ impl<'tcx> Term<'tcx> {
957953
}
958954

959955
pub fn ty(&self) -> Option<Ty<'tcx>> {
960-
if let TermKind::Ty(ty) = self.unpack() {
961-
Some(ty)
962-
} else {
963-
None
964-
}
956+
if let TermKind::Ty(ty) = self.unpack() { Some(ty) } else { None }
965957
}
966958

967959
pub fn ct(&self) -> Option<Const<'tcx>> {
968-
if let TermKind::Const(c) = self.unpack() {
969-
Some(c)
970-
} else {
971-
None
972-
}
960+
if let TermKind::Const(c) = self.unpack() { Some(c) } else { None }
973961
}
974962

975963
pub fn into_arg(self) -> GenericArg<'tcx> {
@@ -1002,8 +990,8 @@ impl<'tcx> TermKind<'tcx> {
1002990
}
1003991
TermKind::Const(ct) => {
1004992
// Ensure we can use the tag bits.
1005-
assert_eq!(mem::align_of_val(&*ct.0 .0) & TAG_MASK, 0);
1006-
(CONST_TAG, ct.0 .0 as *const ty::ConstS<'tcx> as usize)
993+
assert_eq!(mem::align_of_val(&*ct.0.0) & TAG_MASK, 0);
994+
(CONST_TAG, ct.0.0 as *const ty::ConstS<'tcx> as usize)
1007995
}
1008996
};
1009997

@@ -1471,11 +1459,7 @@ impl WithOptConstParam<LocalDefId> {
14711459
}
14721460

14731461
pub fn def_id_for_type_of(self) -> DefId {
1474-
if let Some(did) = self.const_param_did {
1475-
did
1476-
} else {
1477-
self.did.to_def_id()
1478-
}
1462+
if let Some(did) = self.const_param_did { did } else { self.did.to_def_id() }
14791463
}
14801464
}
14811465

@@ -2053,6 +2037,10 @@ impl<'tcx> TyCtxt<'tcx> {
20532037
.wrapping_add(field_shuffle_seed >> 2);
20542038
}
20552039

2040+
// Sets the maximum layout padding multiple, defaults to three
2041+
let random_padding_max_factor =
2042+
self.sess.opts.unstable_opts.layout_random_padding_max_factor.unwrap_or(3);
2043+
20562044
for attr in self.get_attrs(did, sym::repr) {
20572045
for r in attr::parse_repr_attr(&self.sess, attr) {
20582046
flags.insert(match r {
@@ -2108,7 +2096,14 @@ impl<'tcx> TyCtxt<'tcx> {
21082096
flags.insert(ReprFlags::IS_LINEAR);
21092097
}
21102098

2111-
ReprOptions { int: size, align: max_align, pack: min_pack, flags, field_shuffle_seed }
2099+
ReprOptions {
2100+
int: size,
2101+
align: max_align,
2102+
pack: min_pack,
2103+
flags,
2104+
field_shuffle_seed,
2105+
random_padding_max_factor,
2106+
}
21122107
}
21132108

21142109
/// Look up the name of a definition across crates. This does not look at HIR.

compiler/rustc_session/src/config.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2826,6 +2826,7 @@ pub(crate) mod dep_tracking {
28262826
lint::Level,
28272827
WasiExecModel,
28282828
u32,
2829+
u8,
28292830
RelocModel,
28302831
CodeModel,
28312832
TlsModel,

compiler/rustc_session/src/options.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -1364,8 +1364,9 @@ options! {
13641364
"insert function instrument code for mcount-based tracing (default: no)"),
13651365
keep_hygiene_data: bool = (false, parse_bool, [UNTRACKED],
13661366
"keep hygiene data after analysis (default: no)"),
1367-
layout_seed: Option<u64> = (None, parse_opt_number, [TRACKED],
1368-
"seed layout randomization"),
1367+
layout_random_padding_max_factor: Option<u8> = (None, parse_opt_number, [TRACKED],
1368+
"set the maximum field alignment padding multiple when using 'randomize-layout' (default: 3)"),
1369+
layout_seed: Option<u64> = (None, parse_opt_number, [TRACKED], "seed layout randomization"),
13691370
link_native_libraries: bool = (true, parse_bool, [UNTRACKED],
13701371
"link native libraries in the linker invocation (default: yes)"),
13711372
link_only: bool = (false, parse_bool, [TRACKED],

0 commit comments

Comments
 (0)