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.

0 commit comments

Comments
 (0)