Skip to content

Commit

Permalink
Auto merge of #114330 - RalfJung:dagling-ptr-deref, r=oli-obk
Browse files Browse the repository at this point in the history
don't UB on dangling ptr deref, instead check inbounds on projections

This implements rust-lang/reference#1387 in Miri. See that PR for what the change is about.

Detecting dangling references in `let x = &...;` is now done by validity checking only, so some tests need to have validity checking enabled. There is no longer inherently a "nodangle" check in evaluating the expression `&*ptr` (aside from the aliasing model).

r? `@oli-obk`

Based on:
- rust-lang/reference#1387
- rust-lang/rust#115524
  • Loading branch information
bors committed Oct 16, 2023
2 parents e35c181 + dd7e34c commit c48445e
Show file tree
Hide file tree
Showing 92 changed files with 380 additions and 295 deletions.
4 changes: 2 additions & 2 deletions src/borrow_tracker/stacked_borrows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use log::trace;
use rustc_data_structures::fx::FxHashSet;
use rustc_middle::mir::{Mutability, RetagKind};
use rustc_middle::ty::{self, layout::HasParamEnv, Ty};
use rustc_target::abi::{Abi, Align, Size};
use rustc_target::abi::{Abi, Size};

use crate::borrow_tracker::{
stacked_borrows::diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder},
Expand Down Expand Up @@ -616,7 +616,7 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
) -> InterpResult<'tcx, Option<Provenance>> {
let this = self.eval_context_mut();
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
this.check_ptr_access_align(place.ptr(), size, Align::ONE, CheckInAllocMsg::InboundsTest)?;
this.check_ptr_access(place.ptr(), size, CheckInAllocMsg::InboundsTest)?;

// It is crucial that this gets called on all code paths, to ensure we track tag creation.
let log_creation = |this: &MiriInterpCx<'mir, 'tcx>,
Expand Down
5 changes: 2 additions & 3 deletions src/borrow_tracker/tree_borrows/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use log::trace;

use rustc_target::abi::{Abi, Align, Size};
use rustc_target::abi::{Abi, Size};

use crate::borrow_tracker::{
AccessKind, GlobalState, GlobalStateInner, ProtectorKind, RetagFields,
Expand Down Expand Up @@ -206,10 +206,9 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
// Make sure the new permission makes sense as the initial permission of a fresh tag.
assert!(new_perm.initial_state.is_initial());
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
this.check_ptr_access_align(
this.check_ptr_access(
place.ptr(),
ptr_size,
Align::ONE,
CheckInAllocMsg::InboundsTest,
)?;

Expand Down
4 changes: 1 addition & 3 deletions src/concurrency/data_race.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1017,11 +1017,9 @@ trait EvalContextPrivExt<'mir, 'tcx: 'mir>: MiriInterpCxExt<'mir, 'tcx> {
// even if the type they wrap would be less aligned (e.g. AtomicU64 on 32bit must
// be 8-aligned).
let align = Align::from_bytes(place.layout.size.bytes()).unwrap();
this.check_ptr_access_align(
this.check_ptr_align(
place.ptr(),
place.layout.size,
align,
CheckInAllocMsg::MemoryAccessTest,
)?;
// Ensure the allocation is mutable. Even failing (read-only) compare_exchange need mutable
// memory on many targets (i.e., they segfault if taht memory is mapped read-only), and
Expand Down
31 changes: 6 additions & 25 deletions src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -697,27 +697,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
let this = self.eval_context_ref();
let ptr = this.read_pointer(op)?;

let mplace = MPlaceTy::from_aligned_ptr(ptr, layout);

this.check_mplace(&mplace)?;

Ok(mplace)
}

/// Deref' a pointer *without* checking that the place is dereferenceable.
fn deref_pointer_unchecked(
&self,
val: &ImmTy<'tcx, Provenance>,
layout: TyAndLayout<'tcx>,
) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
let this = self.eval_context_ref();
let mut mplace = this.ref_to_mplace(val)?;

mplace.layout = layout;
mplace.align = layout.align.abi;

Ok(mplace)
Ok(this.ptr_to_mplace(ptr, layout))
}

/// Calculates the MPlaceTy given the offset and layout of an access on an operand
Expand Down Expand Up @@ -805,7 +785,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
loop {
// FIXME: We are re-getting the allocation each time around the loop.
// Would be nice if we could somehow "extend" an existing AllocRange.
let alloc = this.get_ptr_alloc(ptr.offset(len, this)?, size1, Align::ONE)?.unwrap(); // not a ZST, so we will get a result
let alloc = this.get_ptr_alloc(ptr.offset(len, this)?, size1)?.unwrap(); // not a ZST, so we will get a result
let byte = alloc.read_integer(alloc_range(Size::ZERO, size1))?.to_u8()?;
if byte == 0 {
break;
Expand Down Expand Up @@ -845,13 +825,13 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
fn read_wide_str(&self, mut ptr: Pointer<Option<Provenance>>) -> InterpResult<'tcx, Vec<u16>> {
let this = self.eval_context_ref();
let size2 = Size::from_bytes(2);
let align2 = Align::from_bytes(2).unwrap();
this.check_ptr_align(ptr, Align::from_bytes(2).unwrap())?;

let mut wchars = Vec::new();
loop {
// FIXME: We are re-getting the allocation each time around the loop.
// Would be nice if we could somehow "extend" an existing AllocRange.
let alloc = this.get_ptr_alloc(ptr, size2, align2)?.unwrap(); // not a ZST, so we will get a result
let alloc = this.get_ptr_alloc(ptr, size2)?.unwrap(); // not a ZST, so we will get a result
let wchar = alloc.read_integer(alloc_range(Size::ZERO, size2))?.to_u16()?;
if wchar == 0 {
break;
Expand Down Expand Up @@ -887,8 +867,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// Store the UTF-16 string.
let size2 = Size::from_bytes(2);
let this = self.eval_context_mut();
this.check_ptr_align(ptr, Align::from_bytes(2).unwrap())?;
let mut alloc = this
.get_ptr_alloc_mut(ptr, size2 * string_length, Align::from_bytes(2).unwrap())?
.get_ptr_alloc_mut(ptr, size2 * string_length)?
.unwrap(); // not a ZST, so we will get a result
for (offset, wchar) in wide_str.iter().copied().chain(iter::once(0x0000)).enumerate() {
let offset = u64::try_from(offset).unwrap();
Expand Down
1 change: 1 addition & 0 deletions src/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
// We do need to write `uninit` so that even after the call ends, the former contents of
// this place cannot be observed any more. We do the write after retagging so that for
// Tree Borrows, this is considered to activate the new tag.
// Conveniently this also ensures that the place actually points to suitable memory.
ecx.write_uninit(&protected_place)?;
// Now we throw away the protected place, ensuring its tag is never used again.
Ok(())
Expand Down
4 changes: 0 additions & 4 deletions src/shims/foreign_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -807,9 +807,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {

this.mem_copy(
ptr_src,
Align::ONE,
ptr_dest,
Align::ONE,
Size::from_bytes(n),
true,
)?;
Expand All @@ -830,9 +828,7 @@ trait EvalContextExtPriv<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
let n = this.read_c_str(ptr_src)?.len().checked_add(1).unwrap();
this.mem_copy(
ptr_src,
Align::ONE,
ptr_dest,
Align::ONE,
Size::from_bytes(n),
true,
)?;
Expand Down
10 changes: 4 additions & 6 deletions src/shims/unix/fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use log::trace;

use rustc_data_structures::fx::FxHashMap;
use rustc_middle::ty::TyCtxt;
use rustc_target::abi::{Align, Size};
use rustc_target::abi::Size;

use crate::shims::os_str::bytes_to_os_str;
use crate::*;
Expand Down Expand Up @@ -756,10 +756,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
trace!("Reading from FD {}, size {}", fd, count);

// Check that the *entire* buffer is actually valid memory.
this.check_ptr_access_align(
this.check_ptr_access(
buf,
Size::from_bytes(count),
Align::ONE,
CheckInAllocMsg::MemoryAccessTest,
)?;

Expand Down Expand Up @@ -810,10 +809,9 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
// Isolation check is done via `FileDescriptor` trait.

// Check that the *entire* buffer is actually valid memory.
this.check_ptr_access_align(
this.check_ptr_access(
buf,
Size::from_bytes(count),
Align::ONE,
CheckInAllocMsg::MemoryAccessTest,
)?;

Expand Down Expand Up @@ -1370,7 +1368,7 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
("d_reclen", size.into()),
("d_type", file_type.into()),
],
&MPlaceTy::from_aligned_ptr(entry, dirent64_layout),
&this.ptr_to_mplace(entry, dirent64_layout),
)?;

let name_ptr = entry.offset(Size::from_bytes(d_name_offset), this)?;
Expand Down
7 changes: 3 additions & 4 deletions src/shims/unix/linux/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub fn futex<'tcx>(

let thread = this.get_active_thread();
// This is a vararg function so we have to bring our own type for this pointer.
let addr = MPlaceTy::from_aligned_ptr(addr, this.machine.layouts.i32);
let addr = this.ptr_to_mplace(addr, this.machine.layouts.i32);
let addr_usize = addr.ptr().addr().bytes();

let futex_private = this.eval_libc_i32("FUTEX_PRIVATE_FLAG");
Expand Down Expand Up @@ -85,9 +85,8 @@ pub fn futex<'tcx>(
return Ok(());
}

// `read_timespec` will check the place when it is not null.
let timeout = this.deref_pointer_unchecked(
&this.read_immediate(&args[3])?,
let timeout = this.deref_pointer_as(
&args[3],
this.libc_ty_layout("timespec"),
)?;
let timeout_time = if this.ptr_is_null(timeout.ptr())? {
Expand Down
4 changes: 2 additions & 2 deletions src/shims/windows/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {

let layout = this.machine.layouts.uint(size).unwrap();
let futex_val = this
.read_scalar_atomic(&MPlaceTy::from_aligned_ptr(ptr, layout), AtomicReadOrd::Relaxed)?;
let compare_val = this.read_scalar(&MPlaceTy::from_aligned_ptr(compare, layout))?;
.read_scalar_atomic(&this.ptr_to_mplace(ptr, layout), AtomicReadOrd::Relaxed)?;
let compare_val = this.read_scalar(&this.ptr_to_mplace(compare, layout))?;

if futex_val == compare_val {
// If the values are the same, we have to block.
Expand Down
3 changes: 0 additions & 3 deletions src/shims/x86/sse3.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use rustc_middle::mir;
use rustc_span::Symbol;
use rustc_target::abi::Align;
use rustc_target::spec::abi::Abi;

use super::horizontal_bin_op;
Expand Down Expand Up @@ -76,9 +75,7 @@ pub(super) trait EvalContextExt<'mir, 'tcx: 'mir>:

this.mem_copy(
src_ptr,
Align::ONE,
dest.ptr(),
Align::ONE,
dest.layout.size,
/*nonoverlapping*/ true,
)?;
Expand Down
1 change: 1 addition & 0 deletions tests/compiletest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ regexes! {
r"0x[0-9a-fA-F]+[0-9a-fA-F]{2,2}" => "$$HEX",
// erase specific alignments
"alignment [0-9]+" => "alignment ALIGN",
"[0-9]+ byte alignment but found [0-9]+" => "ALIGN byte alignment but found ALIGN",
// erase thread caller ids
r"call [0-9]+" => "call ID",
// erase platform module paths
Expand Down
4 changes: 2 additions & 2 deletions tests/fail-dep/shims/mmap_use_after_munmap.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ LL | libc::munmap(ptr, 4096);
= note: BACKTRACE:
= note: inside `main` at $DIR/mmap_use_after_munmap.rs:LL:CC

error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
--> $DIR/mmap_use_after_munmap.rs:LL:CC
|
LL | let _x = *(ptr as *mut u8);
| ^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
| ^^^^^^^^^^^^^^^^^ memory access failed: ALLOC has been freed, so this pointer is dangling
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
4 changes: 2 additions & 2 deletions tests/fail/alloc/reallocate-change-alloc.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
--> $DIR/reallocate-change-alloc.rs:LL:CC
|
LL | let _z = *x;
| ^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
| ^^ memory access failed: ALLOC has been freed, so this pointer is dangling
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
4 changes: 2 additions & 2 deletions tests/fail/concurrency/thread_local_static_dealloc.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
--> $DIR/thread_local_static_dealloc.rs:LL:CC
|
LL | let _val = *dangling_ptr.0;
| ^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
| ^^^^^^^^^^^^^^^ memory access failed: ALLOC has been freed, so this pointer is dangling
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
2 changes: 1 addition & 1 deletion tests/fail/const-ub-checks.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ error[E0080]: evaluation of constant value failed
--> $DIR/const-ub-checks.rs:LL:CC
|
LL | ptr.read();
| ^^^^^^^^^^ accessing memory with alignment ALIGN, but alignment ALIGN is required
| ^^^^^^^^^^ accessing memory based on pointer with alignment ALIGN, but alignment ALIGN is required

note: erroneous constant encountered
--> $DIR/const-ub-checks.rs:LL:CC
Expand Down
12 changes: 0 additions & 12 deletions tests/fail/dangling_pointers/dangling_pointer_addr_of.rs

This file was deleted.

26 changes: 0 additions & 26 deletions tests/fail/dangling_pointers/dangling_pointer_addr_of.stderr

This file was deleted.

4 changes: 2 additions & 2 deletions tests/fail/dangling_pointers/dangling_pointer_deref.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
--> $DIR/dangling_pointer_deref.rs:LL:CC
|
LL | let x = unsafe { *p };
| ^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
| ^^ memory access failed: ALLOC has been freed, so this pointer is dangling
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
fn main() {
let p = {
let b = Box::new(42);
&*b as *const i32
&*b as *const i32 as *const (u8, u8, u8, u8)
};
unsafe {
let _ = *p; //~ ERROR: has been freed
let _ = (*p).1; //~ ERROR: out-of-bounds pointer arithmetic
}
panic!("this should never print");
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
error: Undefined Behavior: out-of-bounds pointer arithmetic: ALLOC has been freed, so this pointer is dangling
--> $DIR/dangling_pointer_project_underscore.rs:LL:CC
|
LL | let _ = *p;
| ^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
LL | let _ = (*p).1;
| ^^^^^^ out-of-bounds pointer arithmetic: ALLOC has been freed, so this pointer is dangling
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
4 changes: 2 additions & 2 deletions tests/fail/dangling_pointers/dangling_primitive.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
--> $DIR/dangling_primitive.rs:LL:CC
|
LL | dbg!(*ptr);
| ^^^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
| ^^^^^^^^^^ memory access failed: ALLOC has been freed, so this pointer is dangling
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
4 changes: 2 additions & 2 deletions tests/fail/dangling_pointers/dangling_zst_deref.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
--> $DIR/dangling_zst_deref.rs:LL:CC
|
LL | let _x = unsafe { *p };
| ^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
| ^^ memory access failed: ALLOC has been freed, so this pointer is dangling
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
4 changes: 2 additions & 2 deletions tests/fail/dangling_pointers/deref-invalid-ptr.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: dereferencing pointer failed: 0x10[noalloc] is a dangling pointer (it has no provenance)
error: Undefined Behavior: out-of-bounds pointer use: 0x10[noalloc] is a dangling pointer (it has no provenance)
--> $DIR/deref-invalid-ptr.rs:LL:CC
|
LL | let _y = unsafe { &*x as *const u32 };
| ^^^ dereferencing pointer failed: 0x10[noalloc] is a dangling pointer (it has no provenance)
| ^^^ out-of-bounds pointer use: 0x10[noalloc] is a dangling pointer (it has no provenance)
|
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Expand Down
Loading

0 comments on commit c48445e

Please sign in to comment.