Skip to content

Commit 7e08576

Browse files
committed
Auto merge of #62081 - RalfJung:miri-pointer-checks, r=oli-obk
Refactor miri pointer checks Centralize bounds, alignment and NULL checking for memory accesses in one function: `memory.check_ptr_access`. That function also takes care of converting a `Scalar` to a `Pointer`, should that be needed. Not all accesses need that though: if the access has size 0, `None` is returned. Everyone accessing memory based on a `Scalar` should use this method to get the `Pointer` they need. All operations on the `Allocation` work on `Pointer` inputs and expect all the checks to have happened (and will ICE if the bounds are violated). The operations on `Memory` work on `Scalar` inputs and do the checks themselves. The only other public method to check pointers is `memory.ptr_may_be_null`, which is needed in a few places. No need for `check_align` or similar methods. That makes the public API surface much easier to use and harder to mis-use. This should be largely no-functional-change, except that ZST accesses to a "true" pointer that is dangling or out-of-bounds are now considered UB. This is to be conservative wrt. whatever LLVM might be doing. While I am at it, this also removes the assumption that the vtable part of a `dyn Trait`-fat-pointer is a `Pointer` (as opposed to a pointer cast to an integer, stored as raw bits). r? @oli-obk
2 parents bbbb3e5 + 7e83028 commit 7e08576

File tree

12 files changed

+292
-253
lines changed

12 files changed

+292
-253
lines changed

src/librustc/mir/interpret/allocation.rs

+52-81
Original file line numberDiff line numberDiff line change
@@ -6,44 +6,13 @@ use super::{
66

77
use crate::ty::layout::{Size, Align};
88
use syntax::ast::Mutability;
9-
use std::{iter, fmt::{self, Display}};
9+
use std::iter;
1010
use crate::mir;
11-
use std::ops::{Deref, DerefMut};
11+
use std::ops::{Range, Deref, DerefMut};
1212
use rustc_data_structures::sorted_map::SortedMap;
13-
use rustc_macros::HashStable;
1413
use rustc_target::abi::HasDataLayout;
1514
use std::borrow::Cow;
1615

17-
/// Used by `check_bounds` to indicate whether the pointer needs to be just inbounds
18-
/// or also inbounds of a *live* allocation.
19-
#[derive(Debug, Copy, Clone, RustcEncodable, RustcDecodable, HashStable)]
20-
pub enum InboundsCheck {
21-
Live,
22-
MaybeDead,
23-
}
24-
25-
/// Used by `check_in_alloc` to indicate context of check
26-
#[derive(Debug, Copy, Clone, RustcEncodable, RustcDecodable, HashStable)]
27-
pub enum CheckInAllocMsg {
28-
MemoryAccessTest,
29-
NullPointerTest,
30-
PointerArithmeticTest,
31-
InboundsTest,
32-
}
33-
34-
impl Display for CheckInAllocMsg {
35-
/// When this is printed as an error the context looks like this
36-
/// "{test name} failed: pointer must be in-bounds at offset..."
37-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38-
write!(f, "{}", match *self {
39-
CheckInAllocMsg::MemoryAccessTest => "Memory access",
40-
CheckInAllocMsg::NullPointerTest => "Null pointer test",
41-
CheckInAllocMsg::PointerArithmeticTest => "Pointer arithmetic",
42-
CheckInAllocMsg::InboundsTest => "Inbounds test",
43-
})
44-
}
45-
}
46-
4716
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, RustcEncodable, RustcDecodable)]
4817
pub struct Allocation<Tag=(),Extra=()> {
4918
/// The actual bytes of the allocation.
@@ -146,54 +115,48 @@ impl<Tag> Allocation<Tag> {
146115

147116
impl<'tcx> ::serialize::UseSpecializedDecodable for &'tcx Allocation {}
148117

149-
/// Alignment and bounds checks
150-
impl<'tcx, Tag, Extra> Allocation<Tag, Extra> {
151-
/// Checks if the pointer is "in-bounds". Notice that a pointer pointing at the end
152-
/// of an allocation (i.e., at the first *inaccessible* location) *is* considered
153-
/// in-bounds! This follows C's/LLVM's rules.
154-
/// If you want to check bounds before doing a memory access, better use `check_bounds`.
155-
fn check_bounds_ptr(
156-
&self,
157-
ptr: Pointer<Tag>,
158-
msg: CheckInAllocMsg,
159-
) -> InterpResult<'tcx> {
160-
let allocation_size = self.bytes.len() as u64;
161-
ptr.check_in_alloc(Size::from_bytes(allocation_size), msg)
162-
}
163-
164-
/// Checks if the memory range beginning at `ptr` and of size `Size` is "in-bounds".
165-
#[inline(always)]
166-
pub fn check_bounds(
118+
/// Byte accessors
119+
impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
120+
/// Just a small local helper function to avoid a bit of code repetition.
121+
/// Returns the range of this allocation that was meant.
122+
#[inline]
123+
fn check_bounds(
167124
&self,
168-
cx: &impl HasDataLayout,
169-
ptr: Pointer<Tag>,
170-
size: Size,
171-
msg: CheckInAllocMsg,
172-
) -> InterpResult<'tcx> {
173-
// if ptr.offset is in bounds, then so is ptr (because offset checks for overflow)
174-
self.check_bounds_ptr(ptr.offset(size, cx)?, msg)
125+
offset: Size,
126+
size: Size
127+
) -> Range<usize> {
128+
let end = offset + size; // this does overflow checking
129+
assert_eq!(
130+
end.bytes() as usize as u64, end.bytes(),
131+
"cannot handle this access on this host architecture"
132+
);
133+
let end = end.bytes() as usize;
134+
assert!(
135+
end <= self.bytes.len(),
136+
"Out-of-bounds access at offset {}, size {} in allocation of size {}",
137+
offset.bytes(), size.bytes(), self.bytes.len()
138+
);
139+
(offset.bytes() as usize)..end
175140
}
176-
}
177141

178-
/// Byte accessors
179-
impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
180142
/// The last argument controls whether we error out when there are undefined
181143
/// or pointer bytes. You should never call this, call `get_bytes` or
182144
/// `get_bytes_with_undef_and_ptr` instead,
183145
///
184146
/// This function also guarantees that the resulting pointer will remain stable
185147
/// even when new allocations are pushed to the `HashMap`. `copy_repeatedly` relies
186148
/// on that.
149+
///
150+
/// It is the caller's responsibility to check bounds and alignment beforehand.
187151
fn get_bytes_internal(
188152
&self,
189153
cx: &impl HasDataLayout,
190154
ptr: Pointer<Tag>,
191155
size: Size,
192156
check_defined_and_ptr: bool,
193-
msg: CheckInAllocMsg,
194157
) -> InterpResult<'tcx, &[u8]>
195158
{
196-
self.check_bounds(cx, ptr, size, msg)?;
159+
let range = self.check_bounds(ptr.offset, size);
197160

198161
if check_defined_and_ptr {
199162
self.check_defined(ptr, size)?;
@@ -205,12 +168,13 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
205168

206169
AllocationExtra::memory_read(self, ptr, size)?;
207170

208-
assert_eq!(ptr.offset.bytes() as usize as u64, ptr.offset.bytes());
209-
assert_eq!(size.bytes() as usize as u64, size.bytes());
210-
let offset = ptr.offset.bytes() as usize;
211-
Ok(&self.bytes[offset..offset + size.bytes() as usize])
171+
Ok(&self.bytes[range])
212172
}
213173

174+
/// Check that these bytes are initialized and not pointer bytes, and then return them
175+
/// as a slice.
176+
///
177+
/// It is the caller's responsibility to check bounds and alignment beforehand.
214178
#[inline]
215179
pub fn get_bytes(
216180
&self,
@@ -219,11 +183,13 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
219183
size: Size,
220184
) -> InterpResult<'tcx, &[u8]>
221185
{
222-
self.get_bytes_internal(cx, ptr, size, true, CheckInAllocMsg::MemoryAccessTest)
186+
self.get_bytes_internal(cx, ptr, size, true)
223187
}
224188

225189
/// It is the caller's responsibility to handle undefined and pointer bytes.
226190
/// However, this still checks that there are no relocations on the *edges*.
191+
///
192+
/// It is the caller's responsibility to check bounds and alignment beforehand.
227193
#[inline]
228194
pub fn get_bytes_with_undef_and_ptr(
229195
&self,
@@ -232,30 +198,28 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
232198
size: Size,
233199
) -> InterpResult<'tcx, &[u8]>
234200
{
235-
self.get_bytes_internal(cx, ptr, size, false, CheckInAllocMsg::MemoryAccessTest)
201+
self.get_bytes_internal(cx, ptr, size, false)
236202
}
237203

238204
/// Just calling this already marks everything as defined and removes relocations,
239205
/// so be sure to actually put data there!
206+
///
207+
/// It is the caller's responsibility to check bounds and alignment beforehand.
240208
pub fn get_bytes_mut(
241209
&mut self,
242210
cx: &impl HasDataLayout,
243211
ptr: Pointer<Tag>,
244212
size: Size,
245213
) -> InterpResult<'tcx, &mut [u8]>
246214
{
247-
assert_ne!(size.bytes(), 0, "0-sized accesses should never even get a `Pointer`");
248-
self.check_bounds(cx, ptr, size, CheckInAllocMsg::MemoryAccessTest)?;
215+
let range = self.check_bounds(ptr.offset, size);
249216

250217
self.mark_definedness(ptr, size, true);
251218
self.clear_relocations(cx, ptr, size)?;
252219

253220
AllocationExtra::memory_written(self, ptr, size)?;
254221

255-
assert_eq!(ptr.offset.bytes() as usize as u64, ptr.offset.bytes());
256-
assert_eq!(size.bytes() as usize as u64, size.bytes());
257-
let offset = ptr.offset.bytes() as usize;
258-
Ok(&mut self.bytes[offset..offset + size.bytes() as usize])
222+
Ok(&mut self.bytes[range])
259223
}
260224
}
261225

@@ -276,9 +240,10 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
276240
let size_with_null = Size::from_bytes((size + 1) as u64);
277241
// Go through `get_bytes` for checks and AllocationExtra hooks.
278242
// We read the null, so we include it in the request, but we want it removed
279-
// from the result!
243+
// from the result, so we do subslicing.
280244
Ok(&self.get_bytes(cx, ptr, size_with_null)?[..size])
281245
}
246+
// This includes the case where `offset` is out-of-bounds to begin with.
282247
None => err!(UnterminatedCString(ptr.erase_tag())),
283248
}
284249
}
@@ -306,7 +271,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
306271

307272
/// Writes `src` to the memory starting at `ptr.offset`.
308273
///
309-
/// Will do bounds checks on the allocation.
274+
/// It is the caller's responsibility to check bounds and alignment beforehand.
310275
pub fn write_bytes(
311276
&mut self,
312277
cx: &impl HasDataLayout,
@@ -320,6 +285,8 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
320285
}
321286

322287
/// Sets `count` bytes starting at `ptr.offset` with `val`. Basically `memset`.
288+
///
289+
/// It is the caller's responsibility to check bounds and alignment beforehand.
323290
pub fn write_repeat(
324291
&mut self,
325292
cx: &impl HasDataLayout,
@@ -342,7 +309,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
342309
/// * in oder to obtain a `Pointer` we need to check for ZSTness anyway due to integer pointers
343310
/// being valid for ZSTs
344311
///
345-
/// Note: This function does not do *any* alignment checks, you need to do these before calling
312+
/// It is the caller's responsibility to check bounds and alignment beforehand.
346313
pub fn read_scalar(
347314
&self,
348315
cx: &impl HasDataLayout,
@@ -378,7 +345,9 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
378345
Ok(ScalarMaybeUndef::Scalar(Scalar::from_uint(bits, size)))
379346
}
380347

381-
/// Note: This function does not do *any* alignment checks, you need to do these before calling
348+
/// Read a pointer-sized scalar.
349+
///
350+
/// It is the caller's responsibility to check bounds and alignment beforehand.
382351
pub fn read_ptr_sized(
383352
&self,
384353
cx: &impl HasDataLayout,
@@ -395,7 +364,7 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
395364
/// * in oder to obtain a `Pointer` we need to check for ZSTness anyway due to integer pointers
396365
/// being valid for ZSTs
397366
///
398-
/// Note: This function does not do *any* alignment checks, you need to do these before calling
367+
/// It is the caller's responsibility to check bounds and alignment beforehand.
399368
pub fn write_scalar(
400369
&mut self,
401370
cx: &impl HasDataLayout,
@@ -435,7 +404,9 @@ impl<'tcx, Tag: Copy, Extra: AllocationExtra<Tag>> Allocation<Tag, Extra> {
435404
Ok(())
436405
}
437406

438-
/// Note: This function does not do *any* alignment checks, you need to do these before calling
407+
/// Write a pointer-sized scalar.
408+
///
409+
/// It is the caller's responsibility to check bounds and alignment beforehand.
439410
pub fn write_ptr_sized(
440411
&mut self,
441412
cx: &impl HasDataLayout,

src/librustc/mir/interpret/mod.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,9 @@ pub use self::error::{
1717

1818
pub use self::value::{Scalar, ScalarMaybeUndef, RawConst, ConstValue};
1919

20-
pub use self::allocation::{
21-
InboundsCheck, Allocation, AllocationExtra,
22-
Relocations, UndefMask, CheckInAllocMsg,
23-
};
20+
pub use self::allocation::{Allocation, AllocationExtra, Relocations, UndefMask};
2421

25-
pub use self::pointer::{Pointer, PointerArithmetic};
22+
pub use self::pointer::{Pointer, PointerArithmetic, CheckInAllocMsg};
2623

2724
use std::fmt;
2825
use crate::mir;

src/librustc/mir/interpret/pointer.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,35 @@
1-
use std::fmt;
1+
use std::fmt::{self, Display};
22

33
use crate::mir;
44
use crate::ty::layout::{self, HasDataLayout, Size};
55
use rustc_macros::HashStable;
66

77
use super::{
8-
AllocId, InterpResult, CheckInAllocMsg
8+
AllocId, InterpResult,
99
};
1010

11+
/// Used by `check_in_alloc` to indicate context of check
12+
#[derive(Debug, Copy, Clone, RustcEncodable, RustcDecodable, HashStable)]
13+
pub enum CheckInAllocMsg {
14+
MemoryAccessTest,
15+
NullPointerTest,
16+
PointerArithmeticTest,
17+
InboundsTest,
18+
}
19+
20+
impl Display for CheckInAllocMsg {
21+
/// When this is printed as an error the context looks like this
22+
/// "{test name} failed: pointer must be in-bounds at offset..."
23+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24+
write!(f, "{}", match *self {
25+
CheckInAllocMsg::MemoryAccessTest => "Memory access",
26+
CheckInAllocMsg::NullPointerTest => "Null pointer test",
27+
CheckInAllocMsg::PointerArithmeticTest => "Pointer arithmetic",
28+
CheckInAllocMsg::InboundsTest => "Inbounds test",
29+
})
30+
}
31+
}
32+
1133
////////////////////////////////////////////////////////////////////////////////
1234
// Pointer arithmetic
1335
////////////////////////////////////////////////////////////////////////////////

src/librustc_mir/interpret/eval_context.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpretCx<'mir, 'tcx, M> {
442442
Ok(Some((size.align_to(align), align)))
443443
}
444444
ty::Dynamic(..) => {
445-
let vtable = metadata.expect("dyn trait fat ptr must have vtable").to_ptr()?;
445+
let vtable = metadata.expect("dyn trait fat ptr must have vtable");
446446
// the second entry in the vtable is the dynamic size of the object.
447447
Ok(Some(self.read_size_and_align_from_vtable(vtable)?))
448448
}

0 commit comments

Comments
 (0)