Skip to content

Commit 14de6ec

Browse files
committed
CTFE: throw unsupported error when partially overwriting a pointer
1 parent e66a8c2 commit 14de6ec

File tree

4 files changed

+69
-27
lines changed

4 files changed

+69
-27
lines changed

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

+43-21
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_span::DUMMY_SP;
1212
use rustc_target::abi::{Align, HasDataLayout, Size};
1313

1414
use super::{
15-
read_target_uint, write_target_uint, AllocId, InterpError, InterpResult, Pointer,
15+
read_target_uint, write_target_uint, AllocId, InterpError, InterpResult, Pointer, Provenance,
1616
ResourceExhaustionInfo, Scalar, ScalarMaybeUninit, UndefinedBehaviorInfo, UninitBytesAccess,
1717
UnsupportedOpInfo,
1818
};
@@ -53,18 +53,22 @@ pub struct Allocation<Tag = AllocId, Extra = ()> {
5353
pub enum AllocError {
5454
/// Encountered a pointer where we needed raw bytes.
5555
ReadPointerAsBytes,
56+
/// Partially overwriting a pointer.
57+
PartialPointerOverwrite(Size),
5658
/// Using uninitialized data where it is not allowed.
5759
InvalidUninitBytes(Option<UninitBytesAccess>),
5860
}
5961
pub type AllocResult<T = ()> = Result<T, AllocError>;
6062

6163
impl AllocError {
6264
pub fn to_interp_error<'tcx>(self, alloc_id: AllocId) -> InterpError<'tcx> {
65+
use AllocError::*;
6366
match self {
64-
AllocError::ReadPointerAsBytes => {
65-
InterpError::Unsupported(UnsupportedOpInfo::ReadPointerAsBytes)
66-
}
67-
AllocError::InvalidUninitBytes(info) => InterpError::UndefinedBehavior(
67+
ReadPointerAsBytes => InterpError::Unsupported(UnsupportedOpInfo::ReadPointerAsBytes),
68+
PartialPointerOverwrite(offset) => InterpError::Unsupported(
69+
UnsupportedOpInfo::PartialPointerOverwrite(Pointer::new(alloc_id, offset)),
70+
),
71+
InvalidUninitBytes(info) => InterpError::UndefinedBehavior(
6872
UndefinedBehaviorInfo::InvalidUninitBytes(info.map(|b| (alloc_id, b))),
6973
),
7074
}
@@ -218,7 +222,7 @@ impl<Tag, Extra> Allocation<Tag, Extra> {
218222
}
219223

220224
/// Byte accessors.
221-
impl<Tag: Copy, Extra> Allocation<Tag, Extra> {
225+
impl<Tag: Provenance, Extra> Allocation<Tag, Extra> {
222226
/// The last argument controls whether we error out when there are uninitialized
223227
/// or pointer bytes. You should never call this, call `get_bytes` or
224228
/// `get_bytes_with_uninit_and_ptr` instead,
@@ -275,30 +279,35 @@ impl<Tag: Copy, Extra> Allocation<Tag, Extra> {
275279
/// It is the caller's responsibility to check bounds and alignment beforehand.
276280
/// Most likely, you want to use the `PlaceTy` and `OperandTy`-based methods
277281
/// on `InterpCx` instead.
278-
pub fn get_bytes_mut(&mut self, cx: &impl HasDataLayout, range: AllocRange) -> &mut [u8] {
282+
pub fn get_bytes_mut(
283+
&mut self,
284+
cx: &impl HasDataLayout,
285+
range: AllocRange,
286+
) -> AllocResult<&mut [u8]> {
279287
self.mark_init(range, true);
280-
self.clear_relocations(cx, range);
288+
self.clear_relocations(cx, range)?;
281289

282-
&mut self.bytes[range.start.bytes_usize()..range.end().bytes_usize()]
290+
Ok(&mut self.bytes[range.start.bytes_usize()..range.end().bytes_usize()])
283291
}
284292

285293
/// A raw pointer variant of `get_bytes_mut` that avoids invalidating existing aliases into this memory.
286-
pub fn get_bytes_mut_ptr(&mut self, cx: &impl HasDataLayout, range: AllocRange) -> *mut [u8] {
294+
pub fn get_bytes_mut_ptr(
295+
&mut self,
296+
cx: &impl HasDataLayout,
297+
range: AllocRange,
298+
) -> AllocResult<*mut [u8]> {
287299
self.mark_init(range, true);
288-
// This also clears relocations that just overlap with the written range. So writing to some
289-
// byte can de-initialize its neighbors! See
290-
// <https://github.com/rust-lang/rust/issues/87184> for details.
291-
self.clear_relocations(cx, range);
300+
self.clear_relocations(cx, range)?;
292301

293302
assert!(range.end().bytes_usize() <= self.bytes.len()); // need to do our own bounds-check
294303
let begin_ptr = self.bytes.as_mut_ptr().wrapping_add(range.start.bytes_usize());
295304
let len = range.end().bytes_usize() - range.start.bytes_usize();
296-
ptr::slice_from_raw_parts_mut(begin_ptr, len)
305+
Ok(ptr::slice_from_raw_parts_mut(begin_ptr, len))
297306
}
298307
}
299308

300309
/// Reading and writing.
301-
impl<Tag: Copy, Extra> Allocation<Tag, Extra> {
310+
impl<Tag: Provenance, Extra> Allocation<Tag, Extra> {
302311
/// Validates that `ptr.offset` and `ptr.offset + size` do not point to the middle of a
303312
/// relocation. If `allow_uninit_and_ptr` is `false`, also enforces that the memory in the
304313
/// given range contains neither relocations nor uninitialized bytes.
@@ -395,7 +404,7 @@ impl<Tag: Copy, Extra> Allocation<Tag, Extra> {
395404
};
396405

397406
let endian = cx.data_layout().endian;
398-
let dst = self.get_bytes_mut(cx, range);
407+
let dst = self.get_bytes_mut(cx, range)?;
399408
write_target_uint(endian, dst, bytes).unwrap();
400409

401410
// See if we have to also write a relocation.
@@ -433,13 +442,16 @@ impl<Tag: Copy, Extra> Allocation<Tag, Extra> {
433442
/// uninitialized. This is a somewhat odd "spooky action at a distance",
434443
/// but it allows strictly more code to run than if we would just error
435444
/// immediately in that case.
436-
fn clear_relocations(&mut self, cx: &impl HasDataLayout, range: AllocRange) {
445+
fn clear_relocations(&mut self, cx: &impl HasDataLayout, range: AllocRange) -> AllocResult
446+
where
447+
Tag: Provenance,
448+
{
437449
// Find the start and end of the given range and its outermost relocations.
438450
let (first, last) = {
439451
// Find all relocations overlapping the given range.
440452
let relocations = self.get_relocations(cx, range);
441453
if relocations.is_empty() {
442-
return;
454+
return Ok(());
443455
}
444456

445457
(
@@ -450,17 +462,27 @@ impl<Tag: Copy, Extra> Allocation<Tag, Extra> {
450462
let start = range.start;
451463
let end = range.end();
452464

453-
// Mark parts of the outermost relocations as uninitialized if they partially fall outside the
454-
// given range.
465+
// We need to handle clearing the relocations from parts of a pointer. See
466+
// <https://github.com/rust-lang/rust/issues/87184> for details.
455467
if first < start {
468+
if Tag::ERR_ON_PARTIAL_PTR_OVERWRITE {
469+
return Err(AllocError::PartialPointerOverwrite(first));
470+
}
456471
self.init_mask.set_range(first, start, false);
457472
}
458473
if last > end {
474+
if Tag::ERR_ON_PARTIAL_PTR_OVERWRITE {
475+
return Err(AllocError::PartialPointerOverwrite(
476+
last - cx.data_layout().pointer_size,
477+
));
478+
}
459479
self.init_mask.set_range(end, last, false);
460480
}
461481

462482
// Forget all the relocations.
463483
self.relocations.0.remove_range(first..last);
484+
485+
Ok(())
464486
}
465487

466488
/// Errors if there are relocations overlapping with the edges of the

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

+8-2
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,9 @@ pub enum UnsupportedOpInfo {
404404
Unsupported(String),
405405
/// Encountered a pointer where we needed raw bytes.
406406
ReadPointerAsBytes,
407+
/// Overwriting parts ofa pointer; the resulting state cannot be represented in our
408+
/// `Allocation` data structure.
409+
PartialPointerOverwrite(Pointer<AllocId>),
407410
//
408411
// The variants below are only reachable from CTFE/const prop, miri will never emit them.
409412
//
@@ -418,9 +421,12 @@ impl fmt::Display for UnsupportedOpInfo {
418421
use UnsupportedOpInfo::*;
419422
match self {
420423
Unsupported(ref msg) => write!(f, "{}", msg),
421-
ReadExternStatic(did) => write!(f, "cannot read from extern static ({:?})", did),
422-
ReadPointerAsBytes => write!(f, "unable to turn pointer into raw bytes",),
424+
ReadPointerAsBytes => write!(f, "unable to turn pointer into raw bytes"),
425+
PartialPointerOverwrite(ptr) => {
426+
write!(f, "unable to overwrite parts of a pointer in memory at {:?}", ptr)
427+
}
423428
ThreadLocalStatic(did) => write!(f, "cannot access thread local static ({:?})", did),
429+
ReadExternStatic(did) => write!(f, "cannot read from extern static ({:?})", did),
424430
}
425431
}
426432
}

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

+7
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ pub trait Provenance: Copy + fmt::Debug {
108108
/// If `false`, ptr-to-int casts are not supported. The offset *must* be relative in that case.
109109
const OFFSET_IS_ADDR: bool;
110110

111+
/// We also use this trait to control whether to abort execution when a pointer is being partially overwritten
112+
/// (this avoids a separate trait in `allocation.rs` just for this purpose).
113+
const ERR_ON_PARTIAL_PTR_OVERWRITE: bool;
114+
111115
/// Determines how a pointer should be printed.
112116
fn fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result
113117
where
@@ -123,6 +127,9 @@ impl Provenance for AllocId {
123127
// so ptr-to-int casts are not possible (since we do not know the global physical offset).
124128
const OFFSET_IS_ADDR: bool = false;
125129

130+
// For now, do not allow this, so that we keep our options open.
131+
const ERR_ON_PARTIAL_PTR_OVERWRITE: bool = true;
132+
126133
fn fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127134
// Forward `alternate` flag to `alloc_id` printing.
128135
if f.alternate() {

compiler/rustc_mir/src/interpret/memory.rs

+11-4
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ impl<'a, 'mir, 'tcx, M: Machine<'mir, 'tcx>> std::fmt::Debug for DumpAllocs<'a,
907907
}
908908

909909
/// Reading and writing.
910-
impl<'tcx, 'a, Tag: Copy, Extra> AllocRefMut<'a, 'tcx, Tag, Extra> {
910+
impl<'tcx, 'a, Tag: Provenance, Extra> AllocRefMut<'a, 'tcx, Tag, Extra> {
911911
pub fn write_scalar(
912912
&mut self,
913913
range: AllocRange,
@@ -928,7 +928,7 @@ impl<'tcx, 'a, Tag: Copy, Extra> AllocRefMut<'a, 'tcx, Tag, Extra> {
928928
}
929929
}
930930

931-
impl<'tcx, 'a, Tag: Copy, Extra> AllocRef<'a, 'tcx, Tag, Extra> {
931+
impl<'tcx, 'a, Tag: Provenance, Extra> AllocRef<'a, 'tcx, Tag, Extra> {
932932
pub fn read_scalar(&self, range: AllocRange) -> InterpResult<'tcx, ScalarMaybeUninit<Tag>> {
933933
Ok(self
934934
.alloc
@@ -998,7 +998,11 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
998998

999999
// Side-step AllocRef and directly access the underlying bytes more efficiently.
10001000
// (We are staying inside the bounds here so all is good.)
1001-
let bytes = alloc_ref.alloc.get_bytes_mut(&alloc_ref.tcx, alloc_ref.range);
1001+
let alloc_id = alloc_ref.alloc_id;
1002+
let bytes = alloc_ref
1003+
.alloc
1004+
.get_bytes_mut(&alloc_ref.tcx, alloc_ref.range)
1005+
.map_err(move |e| e.to_interp_error(alloc_id))?;
10021006
// `zip` would stop when the first iterator ends; we want to definitely
10031007
// cover all of `bytes`.
10041008
for dest in bytes {
@@ -1072,7 +1076,10 @@ impl<'mir, 'tcx, M: Machine<'mir, 'tcx>> Memory<'mir, 'tcx, M> {
10721076
let (dest_alloc, extra) = self.get_raw_mut(dest_alloc_id)?;
10731077
let dest_range = alloc_range(dest_offset, size * num_copies);
10741078
M::memory_written(extra, &mut dest_alloc.extra, dest.provenance, dest_range)?;
1075-
let dest_bytes = dest_alloc.get_bytes_mut_ptr(&tcx, dest_range).as_mut_ptr();
1079+
let dest_bytes = dest_alloc
1080+
.get_bytes_mut_ptr(&tcx, dest_range)
1081+
.map_err(|e| e.to_interp_error(dest_alloc_id))?
1082+
.as_mut_ptr();
10761083

10771084
if compressed.no_bytes_init() {
10781085
// Fast path: If all bytes are `uninit` then there is nothing to copy. The target range

0 commit comments

Comments
 (0)