Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ea84409

Browse files
committedAug 30, 2024·
interpret: reset padding during validation
1 parent dde15ce commit ea84409

20 files changed

+497
-39
lines changed
 

‎compiler/rustc_const_eval/src/const_eval/machine.rs

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
use std::borrow::Borrow;
1+
use std::borrow::{Borrow, Cow};
22
use std::fmt;
33
use std::hash::Hash;
44
use std::ops::ControlFlow;
55

66
use rustc_ast::Mutability;
7-
use rustc_data_structures::fx::{FxIndexMap, IndexEntry};
7+
use rustc_data_structures::fx::{FxHashMap, FxIndexMap, IndexEntry};
88
use rustc_hir::def_id::{DefId, LocalDefId};
99
use rustc_hir::{self as hir, LangItem, CRATE_HIR_ID};
1010
use rustc_middle::mir::AssertMessage;
1111
use rustc_middle::query::TyCtxtAt;
1212
use rustc_middle::ty::layout::{FnAbiOf, TyAndLayout};
13-
use rustc_middle::ty::{self, TyCtxt};
13+
use rustc_middle::ty::{self, Ty, TyCtxt};
1414
use rustc_middle::{bug, mir};
1515
use rustc_span::symbol::{sym, Symbol};
1616
use rustc_span::Span;
@@ -24,8 +24,8 @@ use crate::fluent_generated as fluent;
2424
use crate::interpret::{
2525
self, compile_time_machine, err_ub, throw_exhaust, throw_inval, throw_ub_custom, throw_unsup,
2626
throw_unsup_format, AllocId, AllocRange, ConstAllocation, CtfeProvenance, FnArg, Frame,
27-
GlobalAlloc, ImmTy, InterpCx, InterpResult, MPlaceTy, OpTy, Pointer, PointerArithmetic, Scalar,
28-
StackPopCleanup,
27+
GlobalAlloc, ImmTy, InterpCx, InterpResult, MPlaceTy, OpTy, Pointer, PointerArithmetic,
28+
RangeSet, Scalar, StackPopCleanup,
2929
};
3030

3131
/// When hitting this many interpreted terminators we emit a deny by default lint
@@ -65,6 +65,9 @@ pub struct CompileTimeMachine<'tcx> {
6565
/// storing the result in the given `AllocId`.
6666
/// Used to prevent reads from a static's base allocation, as that may allow for self-initialization loops.
6767
pub(crate) static_root_ids: Option<(AllocId, LocalDefId)>,
68+
69+
/// A cache of "data range" computations for unions (i.e., the offsets of non-padding bytes).
70+
union_data_ranges: FxHashMap<Ty<'tcx>, RangeSet>,
6871
}
6972

7073
#[derive(Copy, Clone)]
@@ -99,6 +102,7 @@ impl<'tcx> CompileTimeMachine<'tcx> {
99102
can_access_mut_global,
100103
check_alignment,
101104
static_root_ids: None,
105+
union_data_ranges: FxHashMap::default(),
102106
}
103107
}
104108
}
@@ -766,6 +770,19 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
766770
}
767771
Ok(())
768772
}
773+
774+
fn cached_union_data_range<'e>(
775+
ecx: &'e mut InterpCx<'tcx, Self>,
776+
ty: Ty<'tcx>,
777+
compute_range: impl FnOnce() -> RangeSet,
778+
) -> Cow<'e, RangeSet> {
779+
if ecx.tcx.sess.opts.unstable_opts.extra_const_ub_checks {
780+
Cow::Borrowed(ecx.machine.union_data_ranges.entry(ty).or_insert_with(compute_range))
781+
} else {
782+
// Don't bother caching, we're only doing one validation at the end anyway.
783+
Cow::Owned(compute_range())
784+
}
785+
}
769786
}
770787

771788
// Please do not add any code below the above `Machine` trait impl. I (oli-obk) plan more cleanups

‎compiler/rustc_const_eval/src/interpret/machine.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use rustc_apfloat::{Float, FloatConvert};
1010
use rustc_ast::{InlineAsmOptions, InlineAsmTemplatePiece};
1111
use rustc_middle::query::TyCtxtAt;
1212
use rustc_middle::ty::layout::TyAndLayout;
13+
use rustc_middle::ty::Ty;
1314
use rustc_middle::{mir, ty};
1415
use rustc_span::def_id::DefId;
1516
use rustc_span::Span;
@@ -19,7 +20,7 @@ use rustc_target::spec::abi::Abi as CallAbi;
1920
use super::{
2021
throw_unsup, throw_unsup_format, AllocBytes, AllocId, AllocKind, AllocRange, Allocation,
2122
ConstAllocation, CtfeProvenance, FnArg, Frame, ImmTy, InterpCx, InterpResult, MPlaceTy,
22-
MemoryKind, Misalignment, OpTy, PlaceTy, Pointer, Provenance, CTFE_ALLOC_SALT,
23+
MemoryKind, Misalignment, OpTy, PlaceTy, Pointer, Provenance, RangeSet, CTFE_ALLOC_SALT,
2324
};
2425

2526
/// Data returned by [`Machine::after_stack_pop`], and consumed by
@@ -588,6 +589,15 @@ pub trait Machine<'tcx>: Sized {
588589
ecx: &InterpCx<'tcx, Self>,
589590
instance: Option<ty::Instance<'tcx>>,
590591
) -> usize;
592+
593+
fn cached_union_data_range<'e>(
594+
_ecx: &'e mut InterpCx<'tcx, Self>,
595+
_ty: Ty<'tcx>,
596+
compute_range: impl FnOnce() -> RangeSet,
597+
) -> Cow<'e, RangeSet> {
598+
// Default to no caching.
599+
Cow::Owned(compute_range())
600+
}
591601
}
592602

593603
/// A lot of the flexibility above is just needed for `Miri`, but all "compile-time" machines

‎compiler/rustc_const_eval/src/interpret/memory.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1136,8 +1136,17 @@ impl<'tcx, 'a, Prov: Provenance, Extra, Bytes: AllocBytes>
11361136
self.write_scalar(alloc_range(offset, self.tcx.data_layout().pointer_size), val)
11371137
}
11381138

1139+
/// Mark the given sub-range (relative to this allocation reference) as uninitialized.
1140+
pub fn write_uninit(&mut self, range: AllocRange) -> InterpResult<'tcx> {
1141+
let range = self.range.subrange(range);
1142+
Ok(self
1143+
.alloc
1144+
.write_uninit(&self.tcx, range)
1145+
.map_err(|e| e.to_interp_error(self.alloc_id))?)
1146+
}
1147+
11391148
/// Mark the entire referenced range as uninitialized
1140-
pub fn write_uninit(&mut self) -> InterpResult<'tcx> {
1149+
pub fn write_uninit_full(&mut self) -> InterpResult<'tcx> {
11411150
Ok(self
11421151
.alloc
11431152
.write_uninit(&self.tcx, self.range)

‎compiler/rustc_const_eval/src/interpret/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ use self::place::{MemPlace, Place};
3939
pub use self::projection::{OffsetMode, Projectable};
4040
pub use self::stack::{Frame, FrameInfo, LocalState, StackPopCleanup, StackPopInfo};
4141
pub(crate) use self::util::create_static_alloc;
42-
pub use self::validity::{CtfeValidationMode, RefTracking};
42+
pub use self::validity::{CtfeValidationMode, RangeSet, RefTracking};
4343
pub use self::visitor::ValueVisitor;

‎compiler/rustc_const_eval/src/interpret/place.rs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -602,10 +602,11 @@ where
602602

603603
if M::enforce_validity(self, dest.layout()) {
604604
// Data got changed, better make sure it matches the type!
605+
// Also needed to reset padding.
605606
self.validate_operand(
606607
&dest.to_place(),
607608
M::enforce_validity_recursively(self, dest.layout()),
608-
/*reset_provenance*/ true,
609+
/*reset_provenance_and_padding*/ true,
609610
)?;
610611
}
611612

@@ -701,9 +702,11 @@ where
701702
// fields do not match the `ScalarPair` components.
702703

703704
alloc.write_scalar(alloc_range(Size::ZERO, a_val.size()), a_val)?;
704-
alloc.write_scalar(alloc_range(b_offset, b_val.size()), b_val)
705+
alloc.write_scalar(alloc_range(b_offset, b_val.size()), b_val)?;
706+
// We don't have to reset padding here, `write_immediate` will anyway do a validation run.
707+
Ok(())
705708
}
706-
Immediate::Uninit => alloc.write_uninit(),
709+
Immediate::Uninit => alloc.write_uninit_full(),
707710
}
708711
}
709712

@@ -720,7 +723,7 @@ where
720723
// Zero-sized access
721724
return Ok(());
722725
};
723-
alloc.write_uninit()?;
726+
alloc.write_uninit_full()?;
724727
}
725728
}
726729
Ok(())
@@ -812,17 +815,17 @@ where
812815
// Given that there were two typed copies, we have to ensure this is valid at both types,
813816
// and we have to ensure this loses provenance and padding according to both types.
814817
// But if the types are identical, we only do one pass.
815-
if src.layout().ty != dest.layout().ty {
818+
if allow_transmute && src.layout().ty != dest.layout().ty {
816819
self.validate_operand(
817820
&dest.transmute(src.layout(), self)?,
818821
M::enforce_validity_recursively(self, src.layout()),
819-
/*reset_provenance*/ true,
822+
/*reset_provenance_and_padding*/ true,
820823
)?;
821824
}
822825
self.validate_operand(
823826
&dest,
824827
M::enforce_validity_recursively(self, dest.layout()),
825-
/*reset_provenance*/ true,
828+
/*reset_provenance_and_padding*/ true,
826829
)?;
827830
}
828831

‎compiler/rustc_const_eval/src/interpret/validity.rs

Lines changed: 246 additions & 18 deletions
Large diffs are not rendered by default.

‎compiler/rustc_const_eval/src/util/check_validity_requirement.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ fn might_permit_raw_init_strict<'tcx>(
6969
.validate_operand(
7070
&allocated.into(),
7171
/*recursive*/ false,
72-
/*reset_provenance*/ false,
72+
/*reset_provenance_and_padding*/ false,
7373
)
7474
.is_ok())
7575
}

‎src/tools/miri/src/machine.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! Global machine state as well as implementation of the interpreter engine
22
//! `Machine` trait.
33
4+
use std::borrow::Cow;
45
use std::cell::RefCell;
56
use std::collections::hash_map::Entry;
67
use std::fmt;
@@ -571,6 +572,9 @@ pub struct MiriMachine<'tcx> {
571572
/// Invariant: the promised alignment will never be less than the native alignment of the
572573
/// allocation.
573574
pub(crate) symbolic_alignment: RefCell<FxHashMap<AllocId, (Size, Align)>>,
575+
576+
/// A cache of "data range" computations for unions (i.e., the offsets of non-padding bytes).
577+
union_data_ranges: FxHashMap<Ty<'tcx>, RangeSet>,
574578
}
575579

576580
impl<'tcx> MiriMachine<'tcx> {
@@ -713,6 +717,7 @@ impl<'tcx> MiriMachine<'tcx> {
713717
allocation_spans: RefCell::new(FxHashMap::default()),
714718
const_cache: RefCell::new(FxHashMap::default()),
715719
symbolic_alignment: RefCell::new(FxHashMap::default()),
720+
union_data_ranges: FxHashMap::default(),
716721
}
717722
}
718723

@@ -825,6 +830,7 @@ impl VisitProvenance for MiriMachine<'_> {
825830
allocation_spans: _,
826831
const_cache: _,
827832
symbolic_alignment: _,
833+
union_data_ranges: _,
828834
} = self;
829835

830836
threads.visit_provenance(visit);
@@ -1602,4 +1608,12 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
16021608
ecx.machine.rng.borrow_mut().gen::<usize>() % ADDRS_PER_ANON_GLOBAL
16031609
}
16041610
}
1611+
1612+
fn cached_union_data_range<'e>(
1613+
ecx: &'e mut InterpCx<'tcx, Self>,
1614+
ty: Ty<'tcx>,
1615+
compute_range: impl FnOnce() -> RangeSet,
1616+
) -> Cow<'e, RangeSet> {
1617+
Cow::Borrowed(ecx.machine.union_data_ranges.entry(ty).or_insert_with(compute_range))
1618+
}
16051619
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use std::mem;
2+
3+
#[allow(unused)]
4+
enum E {
5+
None,
6+
Some(&'static (), &'static ()),
7+
}
8+
9+
fn main() { unsafe {
10+
let p: E = mem::transmute((0usize, 0usize)); // The copy when `E` is returned from `transmute` should destroy padding.
11+
// This is a `None`, so everything but the discriminant is padding.
12+
assert!(matches!(p, E::None));
13+
14+
// Turns out the discriminant is (currently) stored
15+
// in the 2nd pointer, so the first half is padding.
16+
let c = &p as *const _ as *const u8;
17+
let _val = *c.add(0); // Get the padding byte.
18+
//~^ERROR: uninitialized
19+
} }
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
2+
--> $DIR/padding-enum.rs:LL:CC
3+
|
4+
LL | let _val = *c.add(0); // Get the padding byte.
5+
| ^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at $DIR/padding-enum.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to 1 previous error
15+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#![feature(core_intrinsics)]
2+
3+
use std::mem::{self, MaybeUninit};
4+
5+
fn main() {
6+
// This constructs a `(usize, bool)` pair: 9 bytes initialized, the rest not.
7+
// Ensure that these 9 bytes are indeed initialized, and the rest is indeed not.
8+
// This should be the case even if we write into previously initialized storage.
9+
let mut x: MaybeUninit<Box<[u8]>> = MaybeUninit::zeroed();
10+
let z = std::intrinsics::add_with_overflow(0usize, 0usize);
11+
unsafe { x.as_mut_ptr().cast::<(usize, bool)>().write(z) };
12+
// Now read this bytewise. There should be (`ptr_size + 1`) def bytes followed by
13+
// (`ptr_size - 1`) undef bytes (the padding after the bool) in there.
14+
let z: *const u8 = &x as *const _ as *const _;
15+
let first_undef = mem::size_of::<usize>() as isize + 1;
16+
for i in 0..first_undef {
17+
let byte = unsafe { *z.offset(i) };
18+
assert_eq!(byte, 0);
19+
}
20+
let v = unsafe { *z.offset(first_undef) };
21+
//~^ ERROR: uninitialized
22+
if v == 0 {
23+
println!("it is zero");
24+
}
25+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
2+
--> $DIR/padding-pair.rs:LL:CC
3+
|
4+
LL | let v = unsafe { *z.offset(first_undef) };
5+
| ^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at $DIR/padding-pair.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to 1 previous error
15+
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#[repr(C)]
2+
#[derive(Debug, Copy, Clone)]
3+
struct Foo {
4+
val16: u16,
5+
// Padding bytes go here!
6+
val32: u32,
7+
}
8+
9+
#[repr(C)]
10+
#[derive(Debug, Copy, Clone)]
11+
struct Bar {
12+
bytes: [u8; 8],
13+
}
14+
15+
#[repr(C)]
16+
union FooBar {
17+
foo: Foo,
18+
bar: Bar,
19+
}
20+
21+
pub fn main() {
22+
// Initialize as u8 to ensure padding bytes are zeroed.
23+
let mut foobar = FooBar { bar: Bar { bytes: [0u8; 8] } };
24+
// Reading either field is ok.
25+
let _val = unsafe { (foobar.foo, foobar.bar) };
26+
// Does this assignment copy the uninitialized padding bytes
27+
// over the initialized padding bytes? miri doesn't seem to think so.
28+
foobar.foo = Foo { val16: 1, val32: 2 };
29+
// This resets the padding to uninit.
30+
let _val = unsafe { (foobar.foo, foobar.bar) };
31+
//~^ ERROR: uninitialized
32+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: constructing invalid value at .bytes[2]: encountered uninitialized memory, but expected an integer
2+
--> $DIR/padding-struct-assign.rs:LL:CC
3+
|
4+
LL | let _val = unsafe { (foobar.foo, foobar.bar) };
5+
| ^^^^^^^^^^ constructing invalid value at .bytes[2]: encountered uninitialized memory, but expected an integer
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at $DIR/padding-struct-assign.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to 1 previous error
15+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
use std::mem;
2+
3+
#[repr(C)]
4+
struct Pair(u8, u16);
5+
6+
fn main() { unsafe {
7+
let p: Pair = mem::transmute(0u32); // The copy when `Pair` is returned from `transmute` should destroy padding.
8+
let c = &p as *const _ as *const u8;
9+
let _val = *c.add(1); // Get the padding byte.
10+
//~^ERROR: uninitialized
11+
} }
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
2+
--> $DIR/padding-struct.rs:LL:CC
3+
|
4+
LL | let _val = *c.add(1); // Get the padding byte.
5+
| ^^^^^^^^^ using uninitialized data, but this operation requires initialized memory
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at $DIR/padding-struct.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to 1 previous error
15+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use std::mem;
2+
3+
#[allow(unused)]
4+
#[repr(C)]
5+
union U {
6+
field: (u8, u16),
7+
}
8+
9+
fn main() { unsafe {
10+
let p: U = mem::transmute(0u32); // The copy when `U` is returned from `transmute` should destroy padding.
11+
let c = &p as *const _ as *const [u8; 4];
12+
let _val = *c; // Read the entire thing, definitely contains the padding byte.
13+
//~^ERROR: uninitialized
14+
} }
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: Undefined Behavior: constructing invalid value at [1]: encountered uninitialized memory, but expected an integer
2+
--> $DIR/padding-union.rs:LL:CC
3+
|
4+
LL | let _val = *c; // Read the entire thing, definitely contains the padding byte.
5+
| ^^ constructing invalid value at [1]: encountered uninitialized memory, but expected an integer
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `main` at $DIR/padding-union.rs:LL:CC
11+
12+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
13+
14+
error: aborting due to 1 previous error
15+

‎src/tools/miri/tests/fail/transmute-pair-uninit.rs renamed to ‎src/tools/miri/tests/fail/uninit/transmute-pair-uninit.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
#![feature(core_intrinsics)]
22

3-
use std::mem;
3+
use std::mem::{self, MaybeUninit};
44

55
fn main() {
6-
let x: Option<Box<[u8]>> = unsafe {
6+
// This constructs a `(usize, bool)` pair: 9 bytes initialized, the rest not.
7+
// Ensure that these 9 bytes are indeed initialized, and the rest is indeed not.
8+
let x: MaybeUninit<Box<[u8]>> = unsafe {
79
let z = std::intrinsics::add_with_overflow(0usize, 0usize);
8-
std::mem::transmute::<(usize, bool), Option<Box<[u8]>>>(z)
10+
std::mem::transmute::<(usize, bool), MaybeUninit<Box<[u8]>>>(z)
911
};
10-
let y = &x;
1112
// Now read this bytewise. There should be (`ptr_size + 1`) def bytes followed by
1213
// (`ptr_size - 1`) undef bytes (the padding after the bool) in there.
13-
let z: *const u8 = y as *const _ as *const _;
14+
let z: *const u8 = &x as *const _ as *const _;
1415
let first_undef = mem::size_of::<usize>() as isize + 1;
1516
for i in 0..first_undef {
1617
let byte = unsafe { *z.offset(i) };

0 commit comments

Comments
 (0)
Please sign in to comment.