Skip to content

Commit b1ebf00

Browse files
committed
don't UB on dangling ptr deref, instead check inbounds on projections
1 parent a483969 commit b1ebf00

File tree

102 files changed

+526
-464
lines changed

Some content is hidden

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

102 files changed

+526
-464
lines changed

compiler/rustc_const_eval/messages.ftl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ const_eval_deref_coercion_non_const =
6161
.target_note = deref defined here
6262
const_eval_deref_function_pointer =
6363
accessing {$allocation} which contains a function
64-
const_eval_deref_test = dereferencing pointer failed
6564
const_eval_deref_vtable_pointer =
6665
accessing {$allocation} which contains a vtable
6766
const_eval_different_allocations =

compiler/rustc_const_eval/src/errors.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -459,7 +459,6 @@ fn bad_pointer_message(msg: CheckInAllocMsg, handler: &Handler) -> String {
459459
use crate::fluent_generated::*;
460460

461461
let msg = match msg {
462-
CheckInAllocMsg::DerefTest => const_eval_deref_test,
463462
CheckInAllocMsg::MemoryAccessTest => const_eval_memory_access_test,
464463
CheckInAllocMsg::PointerArithmeticTest => const_eval_pointer_arithmetic_test,
465464
CheckInAllocMsg::OffsetFromTest => const_eval_offset_from_test,

compiler/rustc_const_eval/src/interpret/intern.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: CompileTimeMachine<'mir, 'tcx, const_eval::Memory
161161

162162
#[inline(always)]
163163
fn ecx(&self) -> &InterpCx<'mir, 'tcx, M> {
164-
&self.ecx
164+
self.ecx
165165
}
166166

167167
fn visit_value(&mut self, mplace: &MPlaceTy<'tcx>) -> InterpResult<'tcx> {

compiler/rustc_const_eval/src/interpret/intrinsics.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -571,16 +571,8 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
571571
pub fn ptr_offset_inbounds(
572572
&self,
573573
ptr: Pointer<Option<M::Provenance>>,
574-
pointee_ty: Ty<'tcx>,
575-
offset_count: i64,
574+
offset_bytes: i64,
576575
) -> InterpResult<'tcx, Pointer<Option<M::Provenance>>> {
577-
// We cannot overflow i64 as a type's size must be <= isize::MAX.
578-
let pointee_size = i64::try_from(self.layout_of(pointee_ty)?.size.bytes()).unwrap();
579-
// The computed offset, in bytes, must not overflow an isize.
580-
// `checked_mul` enforces a too small bound, but no actual allocation can be big enough for
581-
// the difference to be noticeable.
582-
let offset_bytes =
583-
offset_count.checked_mul(pointee_size).ok_or(err_ub!(PointerArithOverflow))?;
584576
// The offset being in bounds cannot rely on "wrapping around" the address space.
585577
// So, first rule out overflows in the pointer arithmetic.
586578
let offset_ptr = ptr.signed_offset(offset_bytes, self)?;

compiler/rustc_const_eval/src/interpret/machine.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,7 @@ pub trait Machine<'mir, 'tcx: 'mir>: Sized {
436436
place: &PlaceTy<'tcx, Self::Provenance>,
437437
) -> InterpResult<'tcx> {
438438
// Without an aliasing model, all we can do is put `Uninit` into the place.
439+
// Conveniently this also ensures that the place actually points to suitable memory.
439440
ecx.write_uninit(place)
440441
}
441442

compiler/rustc_const_eval/src/interpret/operand.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,17 @@ impl<'tcx, Prov: Provenance> ImmTy<'tcx, Prov> {
219219
/// given layout.
220220
// Not called `offset` to avoid confusion with the trait method.
221221
fn offset_(&self, offset: Size, layout: TyAndLayout<'tcx>, cx: &impl HasDataLayout) -> Self {
222+
debug_assert!(layout.is_sized(), "unsized immediates are not a thing");
223+
// `ImmTy` have already been checked to be in-bounds, so we can just check directly if this
224+
// remains in-bounds. This cannot actually be violated since projections are type-checked
225+
// and bounds-checked.
226+
assert!(
227+
offset + layout.size <= self.layout.size,
228+
"attempting to project to field at offset {} with size {} into immediate with layout {:#?}",
229+
offset.bytes(),
230+
layout.size.bytes(),
231+
self.layout,
232+
);
222233
// This makes several assumptions about what layouts we will encounter; we match what
223234
// codegen does as good as we can (see `extract_field` in `rustc_codegen_ssa/src/mir/operand.rs`).
224235
let inner_val: Immediate<_> = match (**self, self.layout.abi) {
@@ -387,7 +398,6 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for OpTy<'tcx, Prov> {
387398
match self.as_mplace_or_imm() {
388399
Left(mplace) => Ok(mplace.offset_with_meta(offset, meta, layout, ecx)?.into()),
389400
Right(imm) => {
390-
debug_assert!(layout.is_sized(), "unsized immediates are not a thing");
391401
assert_matches!(meta, MemPlaceMeta::None); // no place to store metadata here
392402
// Every part of an uninit is uninit.
393403
Ok(imm.offset_(offset, layout, ecx).into())

compiler/rustc_const_eval/src/interpret/operator.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use rustc_apfloat::{Float, FloatConvert};
22
use rustc_middle::mir;
33
use rustc_middle::mir::interpret::{InterpResult, Scalar};
4-
use rustc_middle::ty::layout::TyAndLayout;
4+
use rustc_middle::ty::layout::{LayoutOf, TyAndLayout};
55
use rustc_middle::ty::{self, FloatTy, Ty};
66
use rustc_span::symbol::sym;
77
use rustc_target::abi::Abi;
@@ -337,7 +337,15 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
337337
let offset_count = right.to_scalar().to_target_isize(self)?;
338338
let pointee_ty = left.layout.ty.builtin_deref(true).unwrap().ty;
339339

340-
let offset_ptr = self.ptr_offset_inbounds(ptr, pointee_ty, offset_count)?;
340+
// We cannot overflow i64 as a type's size must be <= isize::MAX.
341+
let pointee_size = i64::try_from(self.layout_of(pointee_ty)?.size.bytes()).unwrap();
342+
// The computed offset, in bytes, must not overflow an isize.
343+
// `checked_mul` enforces a too small bound, but no actual allocation can be big enough for
344+
// the difference to be noticeable.
345+
let offset_bytes =
346+
offset_count.checked_mul(pointee_size).ok_or(err_ub!(PointerArithOverflow))?;
347+
348+
let offset_ptr = self.ptr_offset_inbounds(ptr, offset_bytes)?;
341349
Ok((
342350
ImmTy::from_scalar(Scalar::from_maybe_pointer(offset_ptr, self), left.layout),
343351
false,

compiler/rustc_const_eval/src/interpret/place.rs

Lines changed: 22 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ use rustc_middle::ty::Ty;
1515
use rustc_target::abi::{Abi, Align, FieldIdx, HasDataLayout, Size, FIRST_VARIANT};
1616

1717
use super::{
18-
alloc_range, mir_assign_valid_types, AllocId, AllocRef, AllocRefMut, CheckInAllocMsg, ImmTy,
19-
Immediate, InterpCx, InterpResult, Machine, MemoryKind, OpTy, Operand, Pointer,
20-
PointerArithmetic, Projectable, Provenance, Readable, Scalar,
18+
alloc_range, mir_assign_valid_types, AllocId, AllocRef, AllocRefMut, ImmTy, Immediate,
19+
InterpCx, InterpResult, Machine, MemoryKind, OpTy, Operand, Pointer, PointerArithmetic,
20+
Projectable, Provenance, Readable, Scalar,
2121
};
2222

2323
#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug)]
@@ -88,17 +88,22 @@ impl<Prov: Provenance> MemPlace<Prov> {
8888

8989
#[inline]
9090
// Not called `offset_with_meta` to avoid confusion with the trait method.
91-
fn offset_with_meta_<'tcx>(
91+
fn offset_with_meta_<'mir, 'tcx, M: Machine<'mir, 'tcx, Provenance = Prov>>(
9292
self,
9393
offset: Size,
9494
meta: MemPlaceMeta<Prov>,
95-
cx: &impl HasDataLayout,
95+
ecx: &InterpCx<'mir, 'tcx, M>,
9696
) -> InterpResult<'tcx, Self> {
9797
debug_assert!(
9898
!meta.has_meta() || self.meta.has_meta(),
9999
"cannot use `offset_with_meta` to add metadata to a place"
100100
);
101-
Ok(MemPlace { ptr: self.ptr.offset(offset, cx)?, meta })
101+
if offset > ecx.data_layout().max_size_of_val() {
102+
throw_ub!(PointerArithOverflow);
103+
}
104+
let offset: i64 = offset.bytes().try_into().unwrap();
105+
let ptr = ecx.ptr_offset_inbounds(self.ptr, offset)?;
106+
Ok(MemPlace { ptr, meta })
102107
}
103108
}
104109

@@ -310,15 +315,18 @@ impl<'tcx, Prov: Provenance> Projectable<'tcx, Prov> for PlaceTy<'tcx, Prov> {
310315
Right((frame, local, old_offset)) => {
311316
debug_assert!(layout.is_sized(), "unsized locals should live in memory");
312317
assert_matches!(meta, MemPlaceMeta::None); // we couldn't store it anyway...
313-
let new_offset = ecx
314-
.data_layout()
315-
.offset(old_offset.unwrap_or(Size::ZERO).bytes(), offset.bytes())?;
318+
// `Place::Local` are always in-bounds of their surrounding local, so we can just
319+
// check directly if this remains in-bounds. This cannot actually be violated since
320+
// projections are type-checked and bounds-checked.
321+
assert!(offset + layout.size <= self.layout.size);
322+
323+
let new_offset = Size::from_bytes(
324+
ecx.data_layout()
325+
.offset(old_offset.unwrap_or(Size::ZERO).bytes(), offset.bytes())?,
326+
);
327+
316328
PlaceTy {
317-
place: Place::Local {
318-
frame,
319-
local,
320-
offset: Some(Size::from_bytes(new_offset)),
321-
},
329+
place: Place::Local { frame, local, offset: Some(new_offset) },
322330
align: self.align.restrict_for_offset(offset),
323331
layout,
324332
}
@@ -464,7 +472,6 @@ where
464472
}
465473

466474
let mplace = self.ref_to_mplace(&val)?;
467-
self.check_mplace(&mplace)?;
468475
Ok(mplace)
469476
}
470477

@@ -494,17 +501,6 @@ where
494501
self.get_ptr_alloc_mut(mplace.ptr(), size, mplace.align)
495502
}
496503

497-
/// Check if this mplace is dereferenceable and sufficiently aligned.
498-
pub fn check_mplace(&self, mplace: &MPlaceTy<'tcx, M::Provenance>) -> InterpResult<'tcx> {
499-
let (size, _align) = self
500-
.size_and_align_of_mplace(&mplace)?
501-
.unwrap_or((mplace.layout.size, mplace.layout.align.abi));
502-
// Due to packed places, only `mplace.align` matters.
503-
let align = if M::enforce_alignment(self) { mplace.align } else { Align::ONE };
504-
self.check_ptr_access_align(mplace.ptr(), size, align, CheckInAllocMsg::DerefTest)?;
505-
Ok(())
506-
}
507-
508504
/// Converts a repr(simd) place into a place where `place_index` accesses the SIMD elements.
509505
/// Also returns the number of elements.
510506
pub fn mplace_to_simd(

compiler/rustc_const_eval/src/interpret/projection.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
5858
ecx: &InterpCx<'mir, 'tcx, M>,
5959
) -> InterpResult<'tcx, Self>;
6060

61-
#[inline]
6261
fn offset<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
6362
&self,
6463
offset: Size,
@@ -69,7 +68,6 @@ pub trait Projectable<'tcx, Prov: Provenance>: Sized + std::fmt::Debug {
6968
self.offset_with_meta(offset, MemPlaceMeta::None, layout, ecx)
7069
}
7170

72-
#[inline]
7371
fn transmute<'mir, M: Machine<'mir, 'tcx, Provenance = Prov>>(
7472
&self,
7573
layout: TyAndLayout<'tcx>,

compiler/rustc_const_eval/src/interpret/terminator.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::borrow::Cow;
22

3-
use either::Either;
43
use rustc_ast::ast::InlineAsmOptions;
54
use rustc_middle::{
65
mir,
@@ -729,13 +728,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
729728
callee_ty: callee_fn_abi.ret.layout.ty
730729
});
731730
}
732-
// Ensure the return place is aligned and dereferenceable, and protect it for
733-
// in-place return value passing.
734-
if let Either::Left(mplace) = destination.as_mplace_or_local() {
735-
self.check_mplace(&mplace)?;
736-
} else {
737-
// Nothing to do for locals, they are always properly allocated and aligned.
738-
}
731+
// Protect return place for in-place return value passing.
739732
M::protect_in_place_function_argument(self, destination)?;
740733

741734
// Don't forget to mark "initially live" locals as live.

compiler/rustc_const_eval/src/interpret/validity.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValidityVisitor<'rt, 'mir, '
355355
value: &OpTy<'tcx, M::Provenance>,
356356
ptr_kind: PointerKind,
357357
) -> InterpResult<'tcx> {
358-
// Not using `deref_pointer` since we do the dereferenceable check ourselves below.
358+
// Not using `deref_pointer` since we want to use our `read_immediate` wrapper.
359359
let place = self.ecx.ref_to_mplace(&self.read_immediate(value, ptr_kind.into())?)?;
360360
// Handle wide pointers.
361361
// Check metadata early, for better diagnostics
@@ -645,7 +645,7 @@ impl<'rt, 'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> ValueVisitor<'mir, 'tcx, M>
645645

646646
#[inline(always)]
647647
fn ecx(&self) -> &InterpCx<'mir, 'tcx, M> {
648-
&self.ecx
648+
self.ecx
649649
}
650650

651651
fn read_discriminant(

compiler/rustc_middle/src/mir/interpret/error.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,6 @@ pub enum InvalidProgramInfo<'tcx> {
218218
/// Details of why a pointer had to be in-bounds.
219219
#[derive(Debug, Copy, Clone, TyEncodable, TyDecodable, HashStable)]
220220
pub enum CheckInAllocMsg {
221-
/// We are dereferencing a pointer (i.e., creating a place).
222-
DerefTest,
223221
/// We are access memory.
224222
MemoryAccessTest,
225223
/// We are doing pointer arithmetic.

src/tools/miri/src/helpers.rs

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -700,23 +700,6 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
700700

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

703-
this.check_mplace(&mplace)?;
704-
705-
Ok(mplace)
706-
}
707-
708-
/// Deref' a pointer *without* checking that the place is dereferenceable.
709-
fn deref_pointer_unchecked(
710-
&self,
711-
val: &ImmTy<'tcx, Provenance>,
712-
layout: TyAndLayout<'tcx>,
713-
) -> InterpResult<'tcx, MPlaceTy<'tcx, Provenance>> {
714-
let this = self.eval_context_ref();
715-
let mut mplace = this.ref_to_mplace(val)?;
716-
717-
mplace.layout = layout;
718-
mplace.align = layout.align.abi;
719-
720703
Ok(mplace)
721704
}
722705

src/tools/miri/src/machine.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1285,6 +1285,7 @@ impl<'mir, 'tcx> Machine<'mir, 'tcx> for MiriMachine<'mir, 'tcx> {
12851285
// We do need to write `uninit` so that even after the call ends, the former contents of
12861286
// this place cannot be observed any more. We do the write after retagging so that for
12871287
// Tree Borrows, this is considered to activate the new tag.
1288+
// Conveniently this also ensures that the place actually points to suitable memory.
12881289
ecx.write_uninit(&protected_place)?;
12891290
// Now we throw away the protected place, ensuring its tag is never used again.
12901291
Ok(())

src/tools/miri/src/shims/unix/linux/sync.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,8 @@ pub fn futex<'tcx>(
8585
return Ok(());
8686
}
8787

88-
// `read_timespec` will check the place when it is not null.
89-
let timeout = this.deref_pointer_unchecked(
90-
&this.read_immediate(&args[3])?,
88+
let timeout = this.deref_pointer_as(
89+
&args[3],
9190
this.libc_ty_layout("timespec"),
9291
)?;
9392
let timeout_time = if this.ptr_is_null(timeout.ptr())? {

src/tools/miri/tests/compiletest.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ regexes! {
181181
r"0x[0-9a-fA-F]+[0-9a-fA-F]{2,2}" => "$$HEX",
182182
// erase specific alignments
183183
"alignment [0-9]+" => "alignment ALIGN",
184+
"[0-9]+ byte alignment but found [0-9]+" => "ALIGN byte alignment but found ALIGN",
184185
// erase thread caller ids
185186
r"call [0-9]+" => "call ID",
186187
// erase platform module paths

src/tools/miri/tests/fail-dep/shims/mmap_use_after_munmap.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ LL | libc::munmap(ptr, 4096);
1313
= note: BACKTRACE:
1414
= note: inside `main` at $DIR/mmap_use_after_munmap.rs:LL:CC
1515

16-
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
16+
error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
1717
--> $DIR/mmap_use_after_munmap.rs:LL:CC
1818
|
1919
LL | let _x = *(ptr as *mut u8);
20-
| ^^^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
20+
| ^^^^^^^^^^^^^^^^^ memory access failed: ALLOC has been freed, so this pointer is dangling
2121
|
2222
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
2323
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

src/tools/miri/tests/fail/alloc/reallocate-change-alloc.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
1+
error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
22
--> $DIR/reallocate-change-alloc.rs:LL:CC
33
|
44
LL | let _z = *x;
5-
| ^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
5+
| ^^ memory access failed: ALLOC has been freed, so this pointer is dangling
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

src/tools/miri/tests/fail/concurrency/thread_local_static_dealloc.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
1+
error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
22
--> $DIR/thread_local_static_dealloc.rs:LL:CC
33
|
44
LL | let _val = *dangling_ptr.0;
5-
| ^^^^^^^^^^^^^^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
5+
| ^^^^^^^^^^^^^^^ memory access failed: ALLOC has been freed, so this pointer is dangling
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.rs

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_addr_of.stderr

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/tools/miri/tests/fail/dangling_pointers/dangling_pointer_deref.stderr

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: Undefined Behavior: dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
1+
error: Undefined Behavior: memory access failed: ALLOC has been freed, so this pointer is dangling
22
--> $DIR/dangling_pointer_deref.rs:LL:CC
33
|
44
LL | let x = unsafe { *p };
5-
| ^^ dereferencing pointer failed: ALLOC has been freed, so this pointer is dangling
5+
| ^^ memory access failed: ALLOC has been freed, so this pointer is dangling
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information

0 commit comments

Comments
 (0)