Skip to content

Commit 95894cb

Browse files
authored
Rollup merge of rust-lang#63075 - RalfJung:deref-checks, r=oli-obk
Miri: Check that a ptr is aligned and inbounds already when evaluating `*` This syncs Miri with what the Nomicon and the Reference say, and resolves rust-lang/miri#447. Also this would not have worked without rust-lang#62982 due to new cycles. ;) r? @oli-obk
2 parents be7f5f5 + 647c0e0 commit 95894cb

File tree

7 files changed

+66
-25
lines changed

7 files changed

+66
-25
lines changed

src/librustc/mir/interpret/pointer.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,11 @@ impl<'tcx, Tag> Pointer<Tag> {
189189
Pointer { alloc_id: self.alloc_id, offset: self.offset, tag: () }
190190
}
191191

192+
/// Test if the pointer is "inbounds" of an allocation of the given size.
193+
/// A pointer is "inbounds" even if its offset is equal to the size; this is
194+
/// a "one-past-the-end" pointer.
192195
#[inline(always)]
193-
pub fn check_in_alloc(
196+
pub fn check_inbounds_alloc(
194197
self,
195198
allocation_size: Size,
196199
msg: CheckInAllocMsg,

src/librustc_mir/interpret/memory.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
368368
// It is sufficient to check this for the end pointer. The addition
369369
// checks for overflow.
370370
let end_ptr = ptr.offset(size, self)?;
371-
end_ptr.check_in_alloc(allocation_size, CheckInAllocMsg::MemoryAccessTest)?;
371+
end_ptr.check_inbounds_alloc(allocation_size, CheckInAllocMsg::MemoryAccessTest)?;
372372
// Test align. Check this last; if both bounds and alignment are violated
373373
// we want the error to be about the bounds.
374374
if let Some(align) = align {
@@ -400,7 +400,7 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
400400
) -> bool {
401401
let (size, _align) = self.get_size_and_align(ptr.alloc_id, AllocCheck::MaybeDead)
402402
.expect("alloc info with MaybeDead cannot fail");
403-
ptr.check_in_alloc(size, CheckInAllocMsg::NullPointerTest).is_err()
403+
ptr.check_inbounds_alloc(size, CheckInAllocMsg::NullPointerTest).is_err()
404404
}
405405
}
406406

src/librustc_mir/interpret/operand.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,9 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
246246
return Ok(None);
247247
}
248248

249-
let ptr = match self.check_mplace_access(mplace, None)? {
249+
let ptr = match self.check_mplace_access(mplace, None)
250+
.expect("places should be checked on creation")
251+
{
250252
Some(ptr) => ptr,
251253
None => return Ok(Some(ImmTy { // zero-sized type
252254
imm: Scalar::zst().into(),

src/librustc_mir/interpret/place.rs

+30-4
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,10 @@ where
277277
{
278278
/// Take a value, which represents a (thin or fat) reference, and make it a place.
279279
/// Alignment is just based on the type. This is the inverse of `MemPlace::to_ref()`.
280+
///
281+
/// Only call this if you are sure the place is "valid" (aligned and inbounds), or do not
282+
/// want to ever use the place for memory access!
283+
/// Generally prefer `deref_operand`.
280284
pub fn ref_to_mplace(
281285
&self,
282286
val: ImmTy<'tcx, M::PointerTag>,
@@ -304,7 +308,8 @@ where
304308
) -> InterpResult<'tcx, MPlaceTy<'tcx, M::PointerTag>> {
305309
let val = self.read_immediate(src)?;
306310
trace!("deref to {} on {:?}", val.layout.ty, *val);
307-
self.ref_to_mplace(val)
311+
let place = self.ref_to_mplace(val)?;
312+
self.mplace_access_checked(place)
308313
}
309314

310315
/// Check if the given place is good for memory access with the given
@@ -327,6 +332,23 @@ where
327332
self.memory.check_ptr_access(place.ptr, size, place.align)
328333
}
329334

335+
/// Return the "access-checked" version of this `MPlace`, where for non-ZST
336+
/// this is definitely a `Pointer`.
337+
pub fn mplace_access_checked(
338+
&self,
339+
mut place: MPlaceTy<'tcx, M::PointerTag>,
340+
) -> InterpResult<'tcx, MPlaceTy<'tcx, M::PointerTag>> {
341+
let (size, align) = self.size_and_align_of_mplace(place)?
342+
.unwrap_or((place.layout.size, place.layout.align.abi));
343+
assert!(place.mplace.align <= align, "dynamic alignment less strict than static one?");
344+
place.mplace.align = align; // maximally strict checking
345+
// When dereferencing a pointer, it must be non-NULL, aligned, and live.
346+
if let Some(ptr) = self.check_mplace_access(place, Some(size))? {
347+
place.mplace.ptr = ptr.into();
348+
}
349+
Ok(place)
350+
}
351+
330352
/// Force `place.ptr` to a `Pointer`.
331353
/// Can be helpful to avoid lots of `force_ptr` calls later, if this place is used a lot.
332354
pub fn force_mplace_ptr(
@@ -750,7 +772,9 @@ where
750772
// to handle padding properly, which is only correct if we never look at this data with the
751773
// wrong type.
752774

753-
let ptr = match self.check_mplace_access(dest, None)? {
775+
let ptr = match self.check_mplace_access(dest, None)
776+
.expect("places should be checked on creation")
777+
{
754778
Some(ptr) => ptr,
755779
None => return Ok(()), // zero-sized access
756780
};
@@ -853,8 +877,10 @@ where
853877
});
854878
assert_eq!(src.meta, dest.meta, "Can only copy between equally-sized instances");
855879

856-
let src = self.check_mplace_access(src, Some(size))?;
857-
let dest = self.check_mplace_access(dest, Some(size))?;
880+
let src = self.check_mplace_access(src, Some(size))
881+
.expect("places should be checked on creation");
882+
let dest = self.check_mplace_access(dest, Some(size))
883+
.expect("places should be checked on creation");
858884
let (src_ptr, dest_ptr) = match (src, dest) {
859885
(Some(src_ptr), Some(dest_ptr)) => (src_ptr, dest_ptr),
860886
(None, None) => return Ok(()), // zero-sized copy

src/librustc_mir/interpret/step.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -240,8 +240,12 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
240240

241241
Ref(_, _, ref place) => {
242242
let src = self.eval_place(place)?;
243-
let val = self.force_allocation(src)?;
244-
self.write_immediate(val.to_ref(), dest)?;
243+
let place = self.force_allocation(src)?;
244+
if place.layout.size.bytes() > 0 {
245+
// definitely not a ZST
246+
assert!(place.ptr.is_ptr(), "non-ZST places should be normalized to `Pointer`");
247+
}
248+
self.write_immediate(place.to_ref(), dest)?;
245249
}
246250

247251
NullaryOp(mir::NullOp::Box, _) => {

src/test/ui/consts/const-eval/ub-nonnull.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ const NON_NULL_PTR: NonNull<u8> = unsafe { mem::transmute(&1) };
1111
const NULL_PTR: NonNull<u8> = unsafe { mem::transmute(0usize) };
1212
//~^ ERROR it is undefined behavior to use this value
1313

14+
#[deny(const_err)] // this triggers a `const_err` so validation does not even happen
1415
const OUT_OF_BOUNDS_PTR: NonNull<u8> = { unsafe {
15-
//~^ ERROR it is undefined behavior to use this value
16-
let ptr: &(u8, u8, u8) = mem::transmute(&0u8); // &0 gets promoted so it does not dangle
17-
let out_of_bounds_ptr = &ptr.2; // use address-of-field for pointer arithmetic
16+
let ptr: &[u8; 256] = mem::transmute(&0u8); // &0 gets promoted so it does not dangle
17+
// Use address-of-element for pointer arithmetic. This could wrap around to NULL!
18+
let out_of_bounds_ptr = &ptr[255]; //~ ERROR any use of this value will cause an error
1819
mem::transmute(out_of_bounds_ptr)
1920
} };
2021

src/test/ui/consts/const-eval/ub-nonnull.stderr

+17-12
Original file line numberDiff line numberDiff line change
@@ -6,53 +6,58 @@ LL | const NULL_PTR: NonNull<u8> = unsafe { mem::transmute(0usize) };
66
|
77
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
88

9-
error[E0080]: it is undefined behavior to use this value
10-
--> $DIR/ub-nonnull.rs:14:1
9+
error: any use of this value will cause an error
10+
--> $DIR/ub-nonnull.rs:18:29
1111
|
1212
LL | / const OUT_OF_BOUNDS_PTR: NonNull<u8> = { unsafe {
13-
LL | |
14-
LL | | let ptr: &(u8, u8, u8) = mem::transmute(&0u8); // &0 gets promoted so it does not dangle
15-
LL | | let out_of_bounds_ptr = &ptr.2; // use address-of-field for pointer arithmetic
13+
LL | | let ptr: &[u8; 256] = mem::transmute(&0u8); // &0 gets promoted so it does not dangle
14+
LL | | // Use address-of-element for pointer arithmetic. This could wrap around to NULL!
15+
LL | | let out_of_bounds_ptr = &ptr[255];
16+
| | ^^^^^^^^^ Memory access failed: pointer must be in-bounds at offset 256, but is outside bounds of allocation 6 which has size 1
1617
LL | | mem::transmute(out_of_bounds_ptr)
1718
LL | | } };
18-
| |____^ type validation failed: encountered a potentially NULL pointer, but expected something that cannot possibly fail to be greater or equal to 1
19+
| |____-
1920
|
20-
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
21+
note: lint level defined here
22+
--> $DIR/ub-nonnull.rs:14:8
23+
|
24+
LL | #[deny(const_err)] // this triggers a `const_err` so validation does not even happen
25+
| ^^^^^^^^^
2126

2227
error[E0080]: it is undefined behavior to use this value
23-
--> $DIR/ub-nonnull.rs:21:1
28+
--> $DIR/ub-nonnull.rs:22:1
2429
|
2530
LL | const NULL_U8: NonZeroU8 = unsafe { mem::transmute(0u8) };
2631
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0, but expected something greater or equal to 1
2732
|
2833
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
2934

3035
error[E0080]: it is undefined behavior to use this value
31-
--> $DIR/ub-nonnull.rs:23:1
36+
--> $DIR/ub-nonnull.rs:24:1
3237
|
3338
LL | const NULL_USIZE: NonZeroUsize = unsafe { mem::transmute(0usize) };
3439
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 0, but expected something greater or equal to 1
3540
|
3641
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
3742

3843
error[E0080]: it is undefined behavior to use this value
39-
--> $DIR/ub-nonnull.rs:30:1
44+
--> $DIR/ub-nonnull.rs:31:1
4045
|
4146
LL | const UNINIT: NonZeroU8 = unsafe { Transmute { uninit: () }.out };
4247
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered uninitialized bytes, but expected something greater or equal to 1
4348
|
4449
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
4550

4651
error[E0080]: it is undefined behavior to use this value
47-
--> $DIR/ub-nonnull.rs:38:1
52+
--> $DIR/ub-nonnull.rs:39:1
4853
|
4954
LL | const BAD_RANGE1: RestrictedRange1 = unsafe { RestrictedRange1(42) };
5055
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 42, but expected something in the range 10..=30
5156
|
5257
= note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
5358

5459
error[E0080]: it is undefined behavior to use this value
55-
--> $DIR/ub-nonnull.rs:44:1
60+
--> $DIR/ub-nonnull.rs:45:1
5661
|
5762
LL | const BAD_RANGE2: RestrictedRange2 = unsafe { RestrictedRange2(20) };
5863
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered 20, but expected something less or equal to 10, or greater or equal to 30

0 commit comments

Comments
 (0)