Skip to content

Commit 64ad036

Browse files
committed
Auto merge of #114333 - RalfJung:dangling-ptr-offset, r=oli-obk
Miri: fix error on dangling pointer inbounds offset We used to claim that the pointer was "dereferenced", but that is just not true. Can be reviewed commit-by-commit. The first commit is an unrelated rename that didn't seem worth splitting into its own PR. r? `@oli-obk`
2 parents 5cbfee5 + 8496292 commit 64ad036

File tree

69 files changed

+214
-207
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+214
-207
lines changed

Diff for: compiler/rustc_const_eval/messages.ftl

+1-1
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ const_eval_pointer_out_of_bounds =
282282
*[many] bytes
283283
} starting at offset {$ptr_offset} is out-of-bounds
284284
const_eval_pointer_use_after_free =
285-
pointer to {$allocation} was dereferenced after this allocation got freed
285+
{$bad_pointer_message}: {$alloc_id} has been freed, so this pointer is dangling
286286
const_eval_ptr_as_bytes_1 =
287287
this code performed an operation that depends on the underlying bytes representing a pointer
288288
const_eval_ptr_as_bytes_2 =

Diff for: compiler/rustc_const_eval/src/const_eval/machine.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,9 @@ impl<'mir, 'tcx: 'mir> CompileTimeEvalContext<'mir, 'tcx> {
214214
// &str or &&str
215215
assert!(args.len() == 1);
216216

217-
let mut msg_place = self.deref_operand(&args[0])?;
217+
let mut msg_place = self.deref_pointer(&args[0])?;
218218
while msg_place.layout.ty.is_ref() {
219-
msg_place = self.deref_operand(&msg_place)?;
219+
msg_place = self.deref_pointer(&msg_place)?;
220220
}
221221

222222
let msg = Symbol::intern(self.read_str(&msg_place)?);

Diff for: compiler/rustc_const_eval/src/const_eval/valtrees.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ pub(crate) fn const_to_valtree_inner<'tcx>(
102102
ty::FnPtr(_) | ty::RawPtr(_) => Err(ValTreeCreationError::NonSupportedType),
103103

104104
ty::Ref(_, _, _) => {
105-
let Ok(derefd_place)= ecx.deref_operand(place) else {
105+
let Ok(derefd_place)= ecx.deref_pointer(place) else {
106106
return Err(ValTreeCreationError::Other);
107107
};
108108
debug!(?derefd_place);

Diff for: compiler/rustc_const_eval/src/errors.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
492492
InvalidMeta(InvalidMetaKind::SliceTooBig) => const_eval_invalid_meta_slice,
493493
InvalidMeta(InvalidMetaKind::TooBig) => const_eval_invalid_meta,
494494
UnterminatedCString(_) => const_eval_unterminated_c_string,
495-
PointerUseAfterFree(_) => const_eval_pointer_use_after_free,
495+
PointerUseAfterFree(_, _) => const_eval_pointer_use_after_free,
496496
PointerOutOfBounds { ptr_size: Size::ZERO, .. } => const_eval_zst_pointer_out_of_bounds,
497497
PointerOutOfBounds { .. } => const_eval_pointer_out_of_bounds,
498498
DanglingIntPointer(0, _) => const_eval_dangling_null_pointer,
@@ -545,8 +545,10 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
545545
UnterminatedCString(ptr) | InvalidFunctionPointer(ptr) | InvalidVTablePointer(ptr) => {
546546
builder.set_arg("pointer", ptr);
547547
}
548-
PointerUseAfterFree(allocation) => {
549-
builder.set_arg("allocation", allocation);
548+
PointerUseAfterFree(alloc_id, msg) => {
549+
builder
550+
.set_arg("alloc_id", alloc_id)
551+
.set_arg("bad_pointer_message", bad_pointer_message(msg, handler));
550552
}
551553
PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, ptr_size, msg } => {
552554
builder

Diff for: compiler/rustc_const_eval/src/interpret/intrinsics.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
144144
}
145145

146146
sym::min_align_of_val | sym::size_of_val => {
147-
// Avoid `deref_operand` -- this is not a deref, the ptr does not have to be
147+
// Avoid `deref_pointer` -- this is not a deref, the ptr does not have to be
148148
// dereferenceable!
149149
let place = self.ref_to_mplace(&self.read_immediate(&args[0])?)?;
150150
let (size, align) = self
@@ -225,7 +225,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
225225
self.write_scalar(val, dest)?;
226226
}
227227
sym::discriminant_value => {
228-
let place = self.deref_operand(&args[0])?;
228+
let place = self.deref_pointer(&args[0])?;
229229
let variant = self.read_discriminant(&place)?;
230230
let discr = self.discriminant_for_variant(place.layout, variant)?;
231231
self.write_scalar(discr, dest)?;

Diff for: compiler/rustc_const_eval/src/interpret/memory.rs

+15-8
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
317317
kind = "static_mem"
318318
)
319319
}
320-
None => err_ub!(PointerUseAfterFree(alloc_id)),
320+
None => err_ub!(PointerUseAfterFree(alloc_id, CheckInAllocMsg::MemoryAccessTest)),
321321
}
322322
.into());
323323
};
@@ -380,7 +380,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
380380
M::enforce_alignment(self),
381381
CheckInAllocMsg::MemoryAccessTest,
382382
|alloc_id, offset, prov| {
383-
let (size, align) = self.get_live_alloc_size_and_align(alloc_id)?;
383+
let (size, align) = self
384+
.get_live_alloc_size_and_align(alloc_id, CheckInAllocMsg::MemoryAccessTest)?;
384385
Ok((size, align, (alloc_id, offset, prov)))
385386
},
386387
)
@@ -404,7 +405,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
404405
CheckAlignment::Error,
405406
msg,
406407
|alloc_id, _, _| {
407-
let (size, align) = self.get_live_alloc_size_and_align(alloc_id)?;
408+
let (size, align) = self.get_live_alloc_size_and_align(alloc_id, msg)?;
408409
Ok((size, align, ()))
409410
},
410411
)?;
@@ -414,7 +415,9 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
414415
/// Low-level helper function to check if a ptr is in-bounds and potentially return a reference
415416
/// to the allocation it points to. Supports both shared and mutable references, as the actual
416417
/// checking is offloaded to a helper closure. `align` defines whether and which alignment check
417-
/// is done. Returns `None` for size 0, and otherwise `Some` of what `alloc_size` returned.
418+
/// is done.
419+
///
420+
/// If this returns `None`, the size is 0; it can however return `Some` even for size 0.
418421
fn check_and_deref_ptr<T>(
419422
&self,
420423
ptr: Pointer<Option<M::Provenance>>,
@@ -515,7 +518,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
515518
}
516519
Some(GlobalAlloc::Function(..)) => throw_ub!(DerefFunctionPointer(id)),
517520
Some(GlobalAlloc::VTable(..)) => throw_ub!(DerefVTablePointer(id)),
518-
None => throw_ub!(PointerUseAfterFree(id)),
521+
None => throw_ub!(PointerUseAfterFree(id, CheckInAllocMsg::MemoryAccessTest)),
519522
Some(GlobalAlloc::Static(def_id)) => {
520523
assert!(self.tcx.is_static(def_id));
521524
assert!(!self.tcx.is_thread_local_static(def_id));
@@ -761,11 +764,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
761764
}
762765
}
763766

764-
/// Obtain the size and alignment of a live allocation.
765-
pub fn get_live_alloc_size_and_align(&self, id: AllocId) -> InterpResult<'tcx, (Size, Align)> {
767+
/// Obtain the size and alignment of a *live* allocation.
768+
fn get_live_alloc_size_and_align(
769+
&self,
770+
id: AllocId,
771+
msg: CheckInAllocMsg,
772+
) -> InterpResult<'tcx, (Size, Align)> {
766773
let (size, align, kind) = self.get_alloc_info(id);
767774
if matches!(kind, AllocKind::Dead) {
768-
throw_ub!(PointerUseAfterFree(id))
775+
throw_ub!(PointerUseAfterFree(id, msg))
769776
}
770777
Ok((size, align))
771778
}

Diff for: compiler/rustc_const_eval/src/interpret/place.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ where
419419
///
420420
/// Only call this if you are sure the place is "valid" (aligned and inbounds), or do not
421421
/// want to ever use the place for memory access!
422-
/// Generally prefer `deref_operand`.
422+
/// Generally prefer `deref_pointer`.
423423
pub fn ref_to_mplace(
424424
&self,
425425
val: &ImmTy<'tcx, M::Provenance>,
@@ -439,8 +439,9 @@ where
439439
}
440440

441441
/// Take an operand, representing a pointer, and dereference it to a place.
442+
/// Corresponds to the `*` operator in Rust.
442443
#[instrument(skip(self), level = "debug")]
443-
pub fn deref_operand(
444+
pub fn deref_pointer(
444445
&self,
445446
src: &impl Readable<'tcx, M::Provenance>,
446447
) -> InterpResult<'tcx, MPlaceTy<'tcx, M::Provenance>> {

Diff for: compiler/rustc_const_eval/src/interpret/projection.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ where
290290
OpaqueCast(ty) => base.transmute(self.layout_of(ty)?, self)?,
291291
Field(field, _) => self.project_field(base, field.index())?,
292292
Downcast(_, variant) => self.project_downcast(base, variant)?,
293-
Deref => self.deref_operand(&base.to_op(self)?)?.into(),
293+
Deref => self.deref_pointer(&base.to_op(self)?)?.into(),
294294
Index(local) => {
295295
let layout = self.layout_of(self.tcx.types.usize)?;
296296
let n = self.local_to_op(self.frame(), local, Some(layout))?;

Diff for: compiler/rustc_const_eval/src/interpret/step.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
224224

225225
Len(place) => {
226226
let src = self.eval_place(place)?;
227-
let op = self.place_to_op(&src)?;
228-
let len = op.len(self)?;
227+
let len = src.len(self)?;
229228
self.write_scalar(Scalar::from_target_usize(len, self), &dest)?;
230229
}
231230

Diff for: compiler/rustc_const_eval/src/interpret/terminator.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -661,7 +661,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
661661
let receiver_place = loop {
662662
match receiver.layout.ty.kind() {
663663
ty::Ref(..) | ty::RawPtr(..) => {
664-
// We do *not* use `deref_operand` here: we don't want to conceptually
664+
// We do *not* use `deref_pointer` here: we don't want to conceptually
665665
// create a place that must be dereferenceable, since the receiver might
666666
// be a raw pointer and (for `*const dyn Trait`) we don't need to
667667
// actually access memory to resolve this method.

Diff for: compiler/rustc_const_eval/src/interpret/validity.rs

+1-3
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
345345
value: &OpTy<'tcx, M::Provenance>,
346346
ptr_kind: PointerKind,
347347
) -> InterpResult<'tcx> {
348+
// Not using `deref_pointer` since we do the dereferenceable check ourselves below.
348349
let place = self.ecx.ref_to_mplace(&self.read_immediate(value, ptr_kind.into())?)?;
349350
// Handle wide pointers.
350351
// Check metadata early, for better diagnostics
@@ -515,9 +516,6 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
515516
Ok(true)
516517
}
517518
ty::RawPtr(..) => {
518-
// We are conservative with uninit for integers, but try to
519-
// actually enforce the strict rules for raw pointers (mostly because
520-
// that lets us re-use `ref_to_mplace`).
521519
let place =
522520
self.ecx.ref_to_mplace(&self.read_immediate(value, ExpectedKind::RawPtr)?)?;
523521
if place.layout.is_unsized() {

Diff for: compiler/rustc_middle/src/mir/interpret/error.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,8 @@ pub enum UndefinedBehaviorInfo<'a> {
282282
InvalidMeta(InvalidMetaKind),
283283
/// Reading a C string that does not end within its allocation.
284284
UnterminatedCString(Pointer),
285-
/// Dereferencing a dangling pointer after it got freed.
286-
PointerUseAfterFree(AllocId),
285+
/// Using a pointer after it got freed.
286+
PointerUseAfterFree(AllocId, CheckInAllocMsg),
287287
/// Used a pointer outside the bounds it is valid for.
288288
/// (If `ptr_size > 0`, determines the size of the memory range that was expected to be in-bounds.)
289289
PointerOutOfBounds {

Diff for: src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs

+3-13
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use rustc_middle::ty::{
1818
layout::{HasParamEnv, LayoutOf},
1919
Ty,
2020
};
21-
use rustc_target::abi::{Abi, Size};
21+
use rustc_target::abi::{Abi, Align, Size};
2222

2323
use crate::borrow_tracker::{
2424
stacked_borrows::diagnostics::{AllocHistory, DiagnosticCx, DiagnosticCxBuilder},
@@ -619,6 +619,8 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
619619
retag_info: RetagInfo, // diagnostics info about this retag
620620
) -> InterpResult<'tcx, Option<AllocId>> {
621621
let this = self.eval_context_mut();
622+
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
623+
this.check_ptr_access_align(place.ptr, size, Align::ONE, CheckInAllocMsg::InboundsTest)?;
622624

623625
// It is crucial that this gets called on all code paths, to ensure we track tag creation.
624626
let log_creation = |this: &MiriInterpCx<'mir, 'tcx>,
@@ -707,18 +709,6 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
707709
let (alloc_id, base_offset, orig_tag) = this.ptr_get_alloc_id(place.ptr)?;
708710
log_creation(this, Some((alloc_id, base_offset, orig_tag)))?;
709711

710-
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
711-
let (alloc_size, _) = this.get_live_alloc_size_and_align(alloc_id)?;
712-
if base_offset + size > alloc_size {
713-
throw_ub!(PointerOutOfBounds {
714-
alloc_id,
715-
alloc_size,
716-
ptr_offset: this.target_usize_to_isize(base_offset.bytes()),
717-
ptr_size: size,
718-
msg: CheckInAllocMsg::InboundsTest
719-
});
720-
}
721-
722712
trace!(
723713
"reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
724714
new_tag,

Diff for: src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs

+23-39
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use log::trace;
22

3-
use rustc_target::abi::{Abi, Size};
3+
use rustc_target::abi::{Abi, Align, Size};
44

55
use crate::borrow_tracker::{AccessKind, GlobalStateInner, ProtectorKind, RetagFields};
66
use rustc_middle::{
@@ -182,6 +182,8 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
182182
new_tag: BorTag,
183183
) -> InterpResult<'tcx, Option<(AllocId, BorTag)>> {
184184
let this = self.eval_context_mut();
185+
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
186+
this.check_ptr_access_align(place.ptr, ptr_size, Align::ONE, CheckInAllocMsg::InboundsTest)?;
185187

186188
// It is crucial that this gets called on all code paths, to ensure we track tag creation.
187189
let log_creation = |this: &MiriInterpCx<'mir, 'tcx>,
@@ -202,51 +204,33 @@ trait EvalContextPrivExt<'mir: 'ecx, 'tcx: 'mir, 'ecx>: crate::MiriInterpCxExt<'
202204
};
203205

204206
trace!("Reborrow of size {:?}", ptr_size);
205-
let (alloc_id, base_offset, parent_prov) = if ptr_size > Size::ZERO {
206-
this.ptr_get_alloc_id(place.ptr)?
207-
} else {
208-
match this.ptr_try_get_alloc_id(place.ptr) {
209-
Ok(data) => data,
210-
Err(_) => {
211-
// This pointer doesn't come with an AllocId, so there's no
212-
// memory to do retagging in.
213-
trace!(
214-
"reborrow of size 0: reference {:?} derived from {:?} (pointee {})",
215-
new_tag,
216-
place.ptr,
217-
place.layout.ty,
218-
);
219-
log_creation(this, None)?;
220-
return Ok(None);
221-
}
207+
let (alloc_id, base_offset, parent_prov) = match this.ptr_try_get_alloc_id(place.ptr) {
208+
Ok(data) => {
209+
// Unlike SB, we *do* a proper retag for size 0 if can identify the allocation.
210+
// After all, the pointer may be lazily initialized outside this initial range.
211+
data
212+
},
213+
Err(_) => {
214+
assert_eq!(ptr_size, Size::ZERO); // we did the deref check above, size has to be 0 here
215+
// This pointer doesn't come with an AllocId, so there's no
216+
// memory to do retagging in.
217+
trace!(
218+
"reborrow of size 0: reference {:?} derived from {:?} (pointee {})",
219+
new_tag,
220+
place.ptr,
221+
place.layout.ty,
222+
);
223+
log_creation(this, None)?;
224+
return Ok(None);
222225
}
223226
};
227+
log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
228+
224229
let orig_tag = match parent_prov {
225230
ProvenanceExtra::Wildcard => return Ok(None), // TODO: handle wildcard pointers
226231
ProvenanceExtra::Concrete(tag) => tag,
227232
};
228233

229-
// Protection against trying to get a reference to a vtable:
230-
// vtables do not have an alloc_extra so the call to
231-
// `get_alloc_extra` that follows fails.
232-
let (alloc_size, _align, alloc_kind) = this.get_alloc_info(alloc_id);
233-
if ptr_size == Size::ZERO && !matches!(alloc_kind, AllocKind::LiveData) {
234-
return Ok(Some((alloc_id, orig_tag)));
235-
}
236-
237-
log_creation(this, Some((alloc_id, base_offset, parent_prov)))?;
238-
239-
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
240-
if base_offset + ptr_size > alloc_size {
241-
throw_ub!(PointerOutOfBounds {
242-
alloc_id,
243-
alloc_size,
244-
ptr_offset: this.target_usize_to_isize(base_offset.bytes()),
245-
ptr_size,
246-
msg: CheckInAllocMsg::InboundsTest
247-
});
248-
}
249-
250234
trace!(
251235
"reborrow: reference {:?} derived from {:?} (pointee {}): {:?}, size {}",
252236
new_tag,

Diff for: src/tools/miri/src/concurrency/sync.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ pub(super) trait EvalContextExtPriv<'mir, 'tcx: 'mir>:
206206
) -> InterpResult<'tcx, Option<Id>> {
207207
let this = self.eval_context_mut();
208208
let value_place =
209-
this.deref_operand_and_offset(lock_op, offset, lock_layout, this.machine.layouts.u32)?;
209+
this.deref_pointer_and_offset(lock_op, offset, lock_layout, this.machine.layouts.u32)?;
210210

211211
// Since we are lazy, this update has to be atomic.
212212
let (old, success) = this

0 commit comments

Comments
 (0)