Skip to content

Commit 310649b

Browse files
committed
Auto merge of #851 - RalfJung:intrptrcast-by-default, r=oli-obk
enable Intrptrcast by default As laid out in #785: we change Miri to always have an RNG, seeded per default with 0. Then we adjust everything to remove dead code and dead tests. r? @oli-obk Cc @christianpoveda
2 parents b269bb0 + 758d88b commit 310649b

35 files changed

+135
-297
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,10 @@ With this, you should now have a working development setup! See
262262

263263
Several `-Z` flags are relevant for Miri:
264264

265-
* `-Zmiri-seed=<hex>` is a custom `-Z` flag added by Miri. It enables the
266-
interpreted program to seed an RNG with system entropy. Miri will keep an RNG
267-
on its own that is seeded with the given seed, and use that to generate the
268-
"system entropy" that seeds the RNG(s) in the interpreted program.
265+
* `-Zmiri-seed=<hex>` is a custom `-Z` flag added by Miri. It configures the
266+
seed of the RNG that Miri uses to resolve non-determinism. This RNG is used
267+
to pick base addresses for allocations, and when the interpreted program
268+
requests system entropy. The default seed is 0.
269269
**NOTE**: This entropy is not good enough for cryptographic use! Do not
270270
generate secret keys in Miri or perform other kinds of cryptographic
271271
operations that rely on proper random numbers.

src/eval.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub fn create_ecx<'mir, 'tcx: 'mir>(
3434
tcx.at(syntax::source_map::DUMMY_SP),
3535
ty::ParamEnv::reveal_all(),
3636
Evaluator::new(),
37-
MemoryExtra::new(config.seed.map(StdRng::seed_from_u64), config.validate),
37+
MemoryExtra::new(StdRng::seed_from_u64(config.seed.unwrap_or(0)), config.validate),
3838
);
3939

4040
let main_instance = ty::Instance::mono(ecx.tcx.tcx, main_id);

src/helpers.rs

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -88,25 +88,12 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriEvalContextExt<'mir, 'tcx
8888
None => return Ok(()), // zero-sized access
8989
};
9090

91-
let data = match &mut this.memory_mut().extra.rng {
92-
Some(rng) => {
93-
let mut rng = rng.borrow_mut();
94-
let mut data = vec![0; len];
95-
rng.fill_bytes(&mut data);
96-
data
97-
}
98-
None => {
99-
return err!(Unimplemented(
100-
"miri does not support gathering system entropy in deterministic mode!
101-
Use '-Zmiri-seed=<seed>' to enable random number generation.
102-
WARNING: Miri does *not* generate cryptographically secure entropy -
103-
do not use Miri to run any program that needs secure random number generation".to_owned(),
104-
));
105-
}
106-
};
91+
let rng = this.memory_mut().extra.rng.get_mut();
92+
let mut data = vec![0; len];
93+
rng.fill_bytes(&mut data);
94+
10795
let tcx = &{this.tcx.tcx};
108-
this.memory_mut().get_mut(ptr.alloc_id)?
109-
.write_bytes(tcx, ptr, &data)
96+
this.memory_mut().get_mut(ptr.alloc_id)?.write_bytes(tcx, ptr, &data)
11097
}
11198

11299
/// Visits the memory covered by `place`, sensitive to freezing: the 3rd parameter

src/intptrcast.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ impl<'mir, 'tcx> GlobalState {
4242
int: u64,
4343
memory: &Memory<'mir, 'tcx, Evaluator<'tcx>>,
4444
) -> InterpResult<'tcx, Pointer<Tag>> {
45+
if int == 0 {
46+
return err!(InvalidNullPointerUsage);
47+
}
48+
4549
let global_state = memory.extra.intptrcast.borrow();
4650

4751
match global_state.int_to_ptr_map.binary_search_by_key(&int, |(addr, _)| *addr) {
@@ -86,12 +90,13 @@ impl<'mir, 'tcx> GlobalState {
8690
// This allocation does not have a base address yet, pick one.
8791
// Leave some space to the previous allocation, to give it some chance to be less aligned.
8892
let slack = {
89-
let mut rng = memory.extra.rng.as_ref().unwrap().borrow_mut();
93+
let mut rng = memory.extra.rng.borrow_mut();
9094
// This means that `(global_state.next_base_addr + slack) % 16` is uniformly distributed.
9195
rng.gen_range(0, 16)
9296
};
9397
// From next_base_addr + slack, round up to adjust for alignment.
94-
let base_addr = Self::align_addr(global_state.next_base_addr + slack, align.bytes());
98+
let base_addr = global_state.next_base_addr.checked_add(slack).unwrap();
99+
let base_addr = Self::align_addr(base_addr, align.bytes());
95100
entry.insert(base_addr);
96101
trace!(
97102
"Assigning base address {:#x} to allocation {:?} (slack: {}, align: {})",
@@ -100,7 +105,7 @@ impl<'mir, 'tcx> GlobalState {
100105

101106
// Remember next base address. If this allocation is zero-sized, leave a gap
102107
// of at least 1 to avoid two allocations having the same base address.
103-
global_state.next_base_addr = base_addr + max(size.bytes(), 1);
108+
global_state.next_base_addr = base_addr.checked_add(max(size.bytes(), 1)).unwrap();
104109
// Given that `next_base_addr` increases in each allocation, pushing the
105110
// corresponding tuple keeps `int_to_ptr_map` sorted
106111
global_state.int_to_ptr_map.push((base_addr, ptr.alloc_id));
@@ -120,7 +125,7 @@ impl<'mir, 'tcx> GlobalState {
120125
fn align_addr(addr: u64, align: u64) -> u64 {
121126
match addr % align {
122127
0 => addr,
123-
rem => addr + align - rem
128+
rem => addr.checked_add(align).unwrap() - rem
124129
}
125130
}
126131
}

src/machine.rs

Lines changed: 8 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -57,20 +57,19 @@ pub struct MemoryExtra {
5757
pub stacked_borrows: stacked_borrows::MemoryExtra,
5858
pub intptrcast: intptrcast::MemoryExtra,
5959

60-
/// The random number generator to use if Miri is running in non-deterministic mode and to
61-
/// enable intptrcast
62-
pub(crate) rng: Option<RefCell<StdRng>>,
60+
/// The random number generator used for resolving non-determinism.
61+
pub(crate) rng: RefCell<StdRng>,
6362

6463
/// Whether to enforce the validity invariant.
6564
pub(crate) validate: bool,
6665
}
6766

6867
impl MemoryExtra {
69-
pub fn new(rng: Option<StdRng>, validate: bool) -> Self {
68+
pub fn new(rng: StdRng, validate: bool) -> Self {
7069
MemoryExtra {
7170
stacked_borrows: Default::default(),
7271
intptrcast: Default::default(),
73-
rng: rng.map(RefCell::new),
72+
rng: RefCell::new(rng),
7473
validate,
7574
}
7675
}
@@ -353,28 +352,20 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for Evaluator<'tcx> {
353352
Ok(ecx.memory().extra.stacked_borrows.borrow_mut().end_call(extra))
354353
}
355354

355+
#[inline(always)]
356356
fn int_to_ptr(
357357
memory: &Memory<'mir, 'tcx, Self>,
358358
int: u64,
359359
) -> InterpResult<'tcx, Pointer<Self::PointerTag>> {
360-
if int == 0 {
361-
err!(InvalidNullPointerUsage)
362-
} else if memory.extra.rng.is_none() {
363-
err!(ReadBytesAsPointer)
364-
} else {
365-
intptrcast::GlobalState::int_to_ptr(int, memory)
366-
}
360+
intptrcast::GlobalState::int_to_ptr(int, memory)
367361
}
368362

363+
#[inline(always)]
369364
fn ptr_to_int(
370365
memory: &Memory<'mir, 'tcx, Self>,
371366
ptr: Pointer<Self::PointerTag>,
372367
) -> InterpResult<'tcx, u64> {
373-
if memory.extra.rng.is_none() {
374-
err!(ReadPointerAsBytes)
375-
} else {
376-
intptrcast::GlobalState::ptr_to_int(ptr, memory)
377-
}
368+
intptrcast::GlobalState::ptr_to_int(ptr, memory)
378369
}
379370
}
380371

src/operator.rs

Lines changed: 7 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ impl<'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'mir, 'tcx> {
5656

5757
trace!("ptr_op: {:?} {:?} {:?}", *left, bin_op, *right);
5858

59-
// If intptrcast is enabled, treat everything of integer *type* at integer *value*.
60-
if self.memory().extra.rng.is_some() && left.layout.ty.is_integral() {
59+
// Treat everything of integer *type* at integer *value*.
60+
if left.layout.ty.is_integral() {
6161
// This is actually an integer operation, so dispatch back to the core engine.
6262
// TODO: Once intptrcast is the default, librustc_mir should never even call us
6363
// for integer types.
@@ -188,104 +188,11 @@ impl<'mir, 'tcx> EvalContextExt<'tcx> for super::MiriEvalContext<'mir, 'tcx> {
188188
right: Scalar<Tag>,
189189
) -> InterpResult<'tcx, bool> {
190190
let size = self.pointer_size();
191-
if self.memory().extra.rng.is_some() {
192-
// Just compare the integers.
193-
// TODO: Do we really want to *always* do that, even when comparing two live in-bounds pointers?
194-
let left = self.force_bits(left, size)?;
195-
let right = self.force_bits(right, size)?;
196-
return Ok(left == right);
197-
}
198-
Ok(match (left, right) {
199-
(Scalar::Raw { .. }, Scalar::Raw { .. }) =>
200-
left.to_bits(size)? == right.to_bits(size)?,
201-
(Scalar::Ptr(left), Scalar::Ptr(right)) => {
202-
// Comparison illegal if one of them is out-of-bounds, *unless* they
203-
// are in the same allocation.
204-
if left.alloc_id == right.alloc_id {
205-
left.offset == right.offset
206-
} else {
207-
// Make sure both pointers are in-bounds.
208-
// This accepts one-past-the end. Thus, there is still technically
209-
// some non-determinism that we do not fully rule out when two
210-
// allocations sit right next to each other. The C/C++ standards are
211-
// somewhat fuzzy about this case, so pragmatically speaking I think
212-
// for now this check is "good enough".
213-
// FIXME: Once we support intptrcast, we could try to fix these holes.
214-
// Dead allocations in miri cannot overlap with live allocations, but
215-
// on read hardware this can easily happen. Thus for comparisons we require
216-
// both pointers to be live.
217-
if self.pointer_inbounds(left).is_ok() && self.pointer_inbounds(right).is_ok() {
218-
// Two in-bounds (and hence live) pointers in different allocations are different.
219-
false
220-
} else {
221-
return err!(InvalidPointerMath);
222-
}
223-
}
224-
}
225-
// Comparing ptr and integer.
226-
(Scalar::Ptr(ptr), Scalar::Raw { data, size }) |
227-
(Scalar::Raw { data, size }, Scalar::Ptr(ptr)) => {
228-
assert_eq!(size as u64, self.pointer_size().bytes());
229-
let bits = data as u64;
230-
231-
// Case I: Comparing real pointers with "small" integers.
232-
// Really we should only do this for NULL, but pragmatically speaking on non-bare-metal systems,
233-
// an allocation will never be at the very bottom of the address space.
234-
// Such comparisons can arise when comparing empty slices, which sometimes are "fake"
235-
// integer pointers (okay because the slice is empty) and sometimes point into a
236-
// real allocation.
237-
// The most common source of such integer pointers is `NonNull::dangling()`, which
238-
// equals the type's alignment. i128 might have an alignment of 16 bytes, but few types have
239-
// alignment 32 or higher, hence the limit of 32.
240-
// FIXME: Once we support intptrcast, we could try to fix these holes.
241-
if bits < 32 {
242-
// Test if the pointer can be different from NULL or not.
243-
// We assume that pointers that are not NULL are also not "small".
244-
if !self.memory().ptr_may_be_null(ptr) {
245-
return Ok(false);
246-
}
247-
}
248-
249-
let (alloc_size, alloc_align) = self.memory()
250-
.get_size_and_align(ptr.alloc_id, AllocCheck::MaybeDead)
251-
.expect("alloc info with MaybeDead cannot fail");
252-
253-
// Case II: Alignment gives it away
254-
if ptr.offset.bytes() % alloc_align.bytes() == 0 {
255-
// The offset maintains the allocation alignment, so we know `base+offset`
256-
// is aligned by `alloc_align`.
257-
// FIXME: We could be even more general, e.g., offset 2 into a 4-aligned
258-
// allocation cannot equal 3.
259-
if bits % alloc_align.bytes() != 0 {
260-
// The integer is *not* aligned. So they cannot be equal.
261-
return Ok(false);
262-
}
263-
}
264-
// Case III: The integer is too big, and the allocation goes on a bit
265-
// without wrapping around the address space.
266-
{
267-
// Compute the highest address at which this allocation could live.
268-
// Substract one more, because it must be possible to add the size
269-
// to the base address without overflowing; that is, the very last address
270-
// of the address space is never dereferencable (but it can be in-bounds, i.e.,
271-
// one-past-the-end).
272-
let max_base_addr =
273-
((1u128 << self.pointer_size().bits())
274-
- u128::from(alloc_size.bytes())
275-
- 1
276-
) as u64;
277-
if let Some(max_addr) = max_base_addr.checked_add(ptr.offset.bytes()) {
278-
if bits > max_addr {
279-
// The integer is too big, this cannot possibly be equal.
280-
return Ok(false)
281-
}
282-
}
283-
}
284-
285-
// None of the supported cases.
286-
return err!(InvalidPointerMath);
287-
}
288-
})
191+
// Just compare the integers.
192+
// TODO: Do we really want to *always* do that, even when comparing two live in-bounds pointers?
193+
let left = self.force_bits(left, size)?;
194+
let right = self.force_bits(right, size)?;
195+
Ok(left == right)
289196
}
290197

291198
fn ptr_int_arithmetic(

tests/compile-fail/cast_box_int_to_fn_ptr.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
fn main() {
55
let b = Box::new(42);
66
let g = unsafe {
7-
std::mem::transmute::<&usize, &fn(i32)>(&b)
7+
std::mem::transmute::<&Box<usize>, &fn(i32)>(&b)
88
};
99

10-
(*g)(42) //~ ERROR a memory access tried to interpret some bytes as a pointer
10+
(*g)(42) //~ ERROR tried to treat a memory pointer as a function pointer
1111
}

tests/compile-fail/cast_int_to_fn_ptr.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@ fn main() {
66
std::mem::transmute::<usize, fn(i32)>(42)
77
};
88

9-
g(42) //~ ERROR a memory access tried to interpret some bytes as a pointer
9+
g(42) //~ ERROR dangling pointer was dereferenced
1010
}

tests/compile-fail/getrandom.rs

Lines changed: 0 additions & 13 deletions
This file was deleted.

tests/compile-fail/intptrcast_alignment_check.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Validation makes this fail in the wrong place
2-
// compile-flags: -Zmiri-disable-validation -Zmiri-seed=0000000000000000
2+
// compile-flags: -Zmiri-disable-validation
33

44
// Even with intptrcast and without validation, we want to be *sure* to catch bugs
55
// that arise from pointers being insufficiently aligned. The only way to achieve

tests/compile-fail/intptrcast_cast_int_to_fn_ptr.rs

Lines changed: 0 additions & 10 deletions
This file was deleted.

tests/compile-fail/intptrcast_null_pointer_deref.rs

Lines changed: 0 additions & 6 deletions
This file was deleted.

tests/compile-fail/intptrcast_wild_pointer_deref.rs

Lines changed: 0 additions & 7 deletions
This file was deleted.

tests/compile-fail/pointer_byte_read_1.rs

Lines changed: 0 additions & 7 deletions
This file was deleted.

tests/compile-fail/ptr_bitops1.rs

Lines changed: 0 additions & 7 deletions
This file was deleted.

tests/compile-fail/ptr_bitops2.rs

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests/compile-fail/ptr_eq_dangling.rs

Lines changed: 0 additions & 10 deletions
This file was deleted.

tests/compile-fail/ptr_eq_integer.rs

Lines changed: 0 additions & 6 deletions
This file was deleted.

tests/compile-fail/ptr_eq_out_of_bounds.rs

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)