Skip to content

Commit

Permalink
properly track why we checked whether a pointer is in-bounds
Browse files Browse the repository at this point in the history
also simplify the in-bounds checking in Miri's borrow trackers
  • Loading branch information
RalfJung committed Aug 1, 2023
1 parent 7d58865 commit 8496292
Show file tree
Hide file tree
Showing 44 changed files with 130 additions and 122 deletions.
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ const_eval_pointer_out_of_bounds =
*[many] bytes
} starting at offset {$ptr_offset} is out-of-bounds
const_eval_pointer_use_after_free =
pointer to {$allocation} was dereferenced after this allocation got freed
{$bad_pointer_message}: {$alloc_id} has been freed, so this pointer is dangling
const_eval_ptr_as_bytes_1 =
this code performed an operation that depends on the underlying bytes representing a pointer
const_eval_ptr_as_bytes_2 =
Expand Down
8 changes: 5 additions & 3 deletions compiler/rustc_const_eval/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
InvalidMeta(InvalidMetaKind::SliceTooBig) => const_eval_invalid_meta_slice,
InvalidMeta(InvalidMetaKind::TooBig) => const_eval_invalid_meta,
UnterminatedCString(_) => const_eval_unterminated_c_string,
PointerUseAfterFree(_) => const_eval_pointer_use_after_free,
PointerUseAfterFree(_, _) => const_eval_pointer_use_after_free,
PointerOutOfBounds { ptr_size: Size::ZERO, .. } => const_eval_zst_pointer_out_of_bounds,
PointerOutOfBounds { .. } => const_eval_pointer_out_of_bounds,
DanglingIntPointer(0, _) => const_eval_dangling_null_pointer,
Expand Down Expand Up @@ -545,8 +545,10 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => {
builder.set_arg("pointer", ptr);
}
PointerUseAfterFree(allocation) => {
builder.set_arg("allocation", allocation);
PointerUseAfterFree(alloc_id, msg) => {
builder
.set_arg("alloc_id", alloc_id)
.set_arg("bad_pointer_message", bad_pointer_message(msg, handler));
}
PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size, msg } => {
builder
Expand Down
23 changes: 15 additions & 8 deletions compiler/rustc_const_eval/src/interpret/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
kind = "static_mem"
)
}
None => err_ub!(PointerUseAfterFree(alloc_id)),
None => err_ub!(PointerUseAfterFree(alloc_id, CheckInAllocMsg::MemoryAccessTest)),
}
.into());
};
Expand Down Expand Up @@ -380,7 +380,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
M::enforce_alignment(self),
CheckInAllocMsg::MemoryAccessTest,
|alloc_id, offset, prov| {
let (size, align) = self.get_live_alloc_size_and_align(alloc_id)?;
let (size, align) = self
.get_live_alloc_size_and_align(alloc_id, CheckInAllocMsg::MemoryAccessTest)?;
Ok((size, align, (alloc_id, offset, prov)))
},
)
Expand All @@ -404,7 +405,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
CheckAlignment::Error,
msg,
|alloc_id, _, _| {
let (size, align) = self.get_live_alloc_size_and_align(alloc_id)?;
let (size, align) = self.get_live_alloc_size_and_align(alloc_id, msg)?;
Ok((size, align, ()))
},
)?;
Expand All @@ -414,7 +415,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
/// Low-level helper function to check if a ptr is in-bounds and potentially return a reference
/// to the allocation it points to. Supports both shared and mutable references, as the actual
/// checking is offloaded to a helper closure. `align` defines whether and which alignment check
/// is done. Returns `None` for size 0, and otherwise `Some` of what `alloc_size` returned.
/// is done.
///
/// If this returns `None`, the size is 0; it can however return `Some` even for size 0.
fn check_and_deref_ptr<T>(
&self,
ptr: Pointer<Option<M::Provenance>>,
Expand Down Expand Up @@ -515,7 +518,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
Some(GlobalAlloc::Function(..)) => throw_ub!(DerefFunctionPointer(id)),
Some(GlobalAlloc::VTable(..)) => throw_ub!(DerefVTablePointer(id)),
None => throw_ub!(PointerUseAfterFree(id)),
None => throw_ub!(PointerUseAfterFree(id, CheckInAllocMsg::MemoryAccessTest)),
Some(GlobalAlloc::Static(def_id)) => {
assert!(self.tcx.is_static(def_id));
assert!(!self.tcx.is_thread_local_static(def_id));
Expand Down Expand Up @@ -761,11 +764,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
}

/// Obtain the size and alignment of a live allocation.
pub fn get_live_alloc_size_and_align(&self, id: AllocId) -> InterpResult<'tcx, (Size, Align)> {
/// Obtain the size and alignment of a *live* allocation.
fn get_live_alloc_size_and_align(
&self,
id: AllocId,
msg: CheckInAllocMsg,
) -> InterpResult<'tcx, (Size, Align)> {
let (size, align, kind) = self.get_alloc_info(id);
if matches!(kind, AllocKind::Dead) {
throw_ub!(PointerUseAfterFree(id))
throw_ub!(PointerUseAfterFree(id, msg))
}
Ok((size, align))
}
Expand Down
3 changes: 1 addition & 2 deletions compiler/rustc_const_eval/src/interpret/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -224,8 +224,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {

Len(place) => {
let src = self.eval_place(place)?;
let op = self.place_to_op(&src)?;
let len = op.len(self)?;
let len = src.len(self)?;
self.write_scalar(Scalar::from_target_usize(len, self), &dest)?;
}

Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_middle/src/mir/interpret/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,8 @@ pub enum UndefinedBehaviorInfo<'a> {
InvalidMeta(InvalidMetaKind),
/// Reading a C string that does not end within its allocation.
UnterminatedCString(Pointer),
/// Dereferencing a dangling pointer after it got freed.
PointerUseAfterFree(AllocId),
/// Using a pointer after it got freed.
PointerUseAfterFree(AllocId, CheckInAllocMsg),
/// Used a pointer outside the bounds it is valid for.
/// (If `ptr_size > 0`, determines the size of the memory range that was expected to be in-bounds.)
PointerOutOfBounds {
Expand Down
16 changes: 3 additions & 13 deletions src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use rustc_middle::ty::{
layout::{HasParamEnv, LayoutOf},
Ty,
};
use rustc_target::abi::{Abi, Size};
use rustc_target::abi::{Abi, Align, Size};

use crate::borrow_tracker::{
stacked_borrows::diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder},
Expand Down Expand Up @@ -619,6 +619,8 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
retag_info: RetagInfo, // diagnostics info about this retag
) -> InterpResult<'tcx, Option<AllocId>> {
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)?;

// 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 Expand Up @@ -707,18 +709,6 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr)?;
log_creation(this, Some((alloc_id, base_offset, orig_tag)))?;

// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
let (alloc_size, _) = this.get_live_alloc_size_and_align(alloc_id)?;
if base_offset + size > alloc_size {
throw_ub!(PointerOutOfBounds {
alloc_id,
alloc_size,
ptr_offset: this.target_usize_to_isize(base_offset.bytes()),
ptr_size: size,
msg: CheckInAllocMsg::InboundsTest
});
}

trace!(
"reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
new_tag,
Expand Down
62 changes: 23 additions & 39 deletions src/tools/miri/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, Size};
use rustc_target::abi::{Abi, Align, Size};

use crate::borrow_tracker::{AccessKind, GlobalStateInner, ProtectorKind, RetagFields};
use rustc_middle::{
Expand Down Expand Up @@ -182,6 +182,8 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
new_tag: BorTag,
) -> InterpResult<'tcx, Option<(AllocId, BorTag)>> {
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, ptr_size, Align::ONE, 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 All @@ -202,51 +204,33 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
};

trace!("Reborrow of size {:?}", ptr_size);
let (alloc_id, base_offset, parent_prov) = if ptr_size > Size::ZERO {
this.ptr_get_alloc_id(place.ptr)?
} else {
match this.ptr_try_get_alloc_id(place.ptr) {
Ok(data) => data,
Err(_) => {
// This pointer doesn't come with an AllocId, so there's no
// memory to do retagging in.
trace!(
"reborrow of size 0: reference {:?} derived from {:?} (pointee {})",
new_tag,
place.ptr,
place.layout.ty,
);
log_creation(this, None)?;
return Ok(None);
}
let (alloc_id, base_offset, parent_prov) = match this.ptr_try_get_alloc_id(place.ptr) {
Ok(data) => {
// Unlike SB, we *do* a proper retag for size 0 if can identify the allocation.
// After all, the pointer may be lazily initialized outside this initial range.
data
},
Err(_) => {
assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
// This pointer doesn't come with an AllocId, so there's no
// memory to do retagging in.
trace!(
"reborrow of size 0: reference {:?} derived from {:?} (pointee {})",
new_tag,
place.ptr,
place.layout.ty,
);
log_creation(this, None)?;
return Ok(None);
}
};
log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;

let orig_tag = match parent_prov {
ProvenanceExtra::Wildcard => return Ok(None), // TODO: handle wildcard pointers
ProvenanceExtra::Concrete(tag) => tag,
};

// Protection against trying to get a reference to a vtable:
// vtables do not have an alloc_extra so the call to
// `get_alloc_extra` that follows fails.
let (alloc_size, _align, alloc_kind) = this.get_alloc_info(alloc_id);
if ptr_size == Size::ZERO && !matches!(alloc_kind, AllocKind::LiveData) {
return Ok(Some((alloc_id, orig_tag)));
}

log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;

// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
if base_offset + ptr_size > alloc_size {
throw_ub!(PointerOutOfBounds {
alloc_id,
alloc_size,
ptr_offset: this.target_usize_to_isize(base_offset.bytes()),
ptr_size,
msg: CheckInAllocMsg::InboundsTest
});
}

trace!(
"reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
new_tag,
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/tests/fail/alloc/deallocate-twice.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::alloc::{alloc, dealloc, Layout};

//@error-in-other-file: dereferenced after this allocation got freed
//@error-in-other-file: has been freed

fn main() {
unsafe {
Expand Down
4 changes: 2 additions & 2 deletions src/tools/miri/tests/fail/alloc/deallocate-twice.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
--> RUSTLIB/alloc/src/alloc.rs:LL:CC
|
LL | unsafe { __rust_dealloc(ptr, layout.size(), layout.align()) }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 src/tools/miri/tests/fail/alloc/reallocate-change-alloc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ fn main() {
unsafe {
let x = alloc(Layout::from_size_align_unchecked(1, 1));
let _y = realloc(x, Layout::from_size_align_unchecked(1, 1), 1);
let _z = *x; //~ ERROR: dereferenced after this allocation got freed
let _z = *x; //~ ERROR: has been freed
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
--> $DIR/reallocate-change-alloc.rs:LL:CC
|
LL | let _z = *x;
| ^^ pointer to ALLOC was dereferenced after this allocation got freed
| ^^ dereferencing pointer 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 src/tools/miri/tests/fail/alloc/reallocate-dangling.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::alloc::{alloc, dealloc, realloc, Layout};

//@error-in-other-file: dereferenced after this allocation got freed
//@error-in-other-file: has been freed

fn main() {
unsafe {
Expand Down
4 changes: 2 additions & 2 deletions src/tools/miri/tests/fail/alloc/reallocate-dangling.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
--> RUSTLIB/alloc/src/alloc.rs:LL:CC
|
LL | unsafe { __rust_realloc(ptr, layout.size(), layout.align(), new_size) }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 @@ -11,6 +11,6 @@ unsafe impl Send for SendRaw {}
fn main() {
unsafe {
let dangling_ptr = std::thread::spawn(|| SendRaw(&TLS as *const u8)).join().unwrap();
let _val = *dangling_ptr.0; //~ ERROR: dereferenced after this allocation got freed
let _val = *dangling_ptr.0; //~ ERROR: has been freed
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
--> $DIR/thread_local_static_dealloc.rs:LL:CC
|
LL | let _val = *dangling_ptr.0;
| ^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
| ^^^^^^^^^^^^^^^ dereferencing pointer 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 @@ -7,6 +7,6 @@ fn main() {
let b = Box::new(42);
&*b as *const i32
};
let x = unsafe { ptr::addr_of!(*p) }; //~ ERROR: dereferenced after this allocation got freed
let x = unsafe { ptr::addr_of!(*p) }; //~ ERROR: has been freed
panic!("this should never print: {:?}", x);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
--> $DIR/dangling_pointer_addr_of.rs:LL:CC
|
LL | let x = unsafe { ptr::addr_of!(*p) };
| ^^^^^^^^^^^^^^^^^ pointer to ALLOC was dereferenced after this allocation got freed
| ^^^^^^^^^^^^^^^^^ dereferencing pointer 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 @@ -6,6 +6,6 @@ fn main() {
let b = Box::new(42);
&*b as *const i32
};
let x = unsafe { *p }; //~ ERROR: dereferenced after this allocation got freed
let x = unsafe { *p }; //~ ERROR: has been freed
panic!("this should never print: {}", x);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: pointer to ALLOC was dereferenced after this allocation got freed
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
--> $DIR/dangling_pointer_deref.rs:LL:CC
|
LL | let x = unsafe { *p };
| ^^ pointer to ALLOC was dereferenced after this allocation got freed
| ^^ dereferencing pointer 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
@@ -0,0 +1,11 @@
// Make sure we find these even with many checks disabled.
//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation

fn main() {
let p = {
let b = Box::new(42);
&*b as *const i32
};
let x = unsafe { p.offset(42) }; //~ ERROR: /out-of-bounds pointer arithmetic: .* has been freed/
panic!("this should never print: {:?}", x);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error: Undefined Behavior: out-of-bounds pointer arithmetic: ALLOC has been freed, so this pointer is dangling
--> $DIR/dangling_pointer_offset.rs:LL:CC
|
LL | let x = unsafe { p.offset(42) };
| ^^^^^^^^^^^^ 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
= note: BACKTRACE:
= note: inside `main` at $DIR/dangling_pointer_offset.rs:LL:CC

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to previous error

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ fn main() {
&*b as *const i32
};
unsafe {
let _ = *p; //~ ERROR: dereferenced after this allocation got freed
let _ = *p; //~ ERROR: has been freed
}
panic!("this should never print");
}
Loading

0 comments on commit 8496292

Please sign in to comment.