Skip to content

interpret: better error message for out-of-bounds pointer arithmetic and accesses #140521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 35 additions & 35 deletions compiler/rustc_const_eval/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,27 @@ const_eval_already_reported =
const_eval_assume_false =
`assume` called with `false`

const_eval_bad_pointer_op = {$operation ->
[MemoryAccess] memory access failed
[InboundsPointerArithmetic] in-bounds pointer arithmetic failed
*[Dereferenceable] pointer not dereferenceable
}
const_eval_bad_pointer_op_attempting = {const_eval_bad_pointer_op}: {$operation ->
[MemoryAccess] attempting to access {$inbounds_size ->
[1] 1 byte
*[x] {$inbounds_size} bytes
}
[InboundsPointerArithmetic] attempting to offset pointer by {$inbounds_size ->
[1] 1 byte
*[x] {$inbounds_size} bytes
}
*[Dereferenceable] pointer must {$inbounds_size ->
[0] point to some allocation
[1] be dereferenceable for 1 byte
*[x] be dereferenceable for {$inbounds_size} bytes
}
}

const_eval_bounds_check_failed =
indexing out of bounds: the len is {$len} but the index is {$index}
const_eval_call_nonzero_intrinsic =
Expand Down Expand Up @@ -39,9 +60,9 @@ const_eval_copy_nonoverlapping_overlapping =
`copy_nonoverlapping` called on overlapping ranges

const_eval_dangling_int_pointer =
{$bad_pointer_message}: {const_eval_expected_inbounds_pointer}, but got {$pointer} which is a dangling pointer (it has no provenance)
{const_eval_bad_pointer_op_attempting}, but got {$pointer} which is a dangling pointer (it has no provenance)
const_eval_dangling_null_pointer =
{$bad_pointer_message}: {const_eval_expected_inbounds_pointer}, but got a null pointer
{const_eval_bad_pointer_op_attempting}, but got null pointer

const_eval_dangling_ptr_in_final = encountered dangling pointer in final value of {const_eval_intern_kind}
const_eval_dead_local =
Expand Down Expand Up @@ -77,21 +98,6 @@ const_eval_error = {$error_kind ->
const_eval_exact_div_has_remainder =
exact_div: {$a} cannot be divided by {$b} without remainder

const_eval_expected_inbounds_pointer =
expected a pointer to {$inbounds_size_abs ->
[0] some allocation
*[x] {$inbounds_size_is_neg ->
[false] {$inbounds_size_abs ->
[1] 1 byte of memory
*[x] {$inbounds_size_abs} bytes of memory
}
*[true] the end of {$inbounds_size_abs ->
[1] 1 byte of memory
*[x] {$inbounds_size_abs} bytes of memory
}
}
}

const_eval_extern_static =
cannot access extern static `{$did}`
const_eval_extern_type_field = `extern type` field does not have a known offset
Expand All @@ -111,7 +117,6 @@ const_eval_frame_note_inner = inside {$where_ ->

const_eval_frame_note_last = the failure occurred here

const_eval_in_bounds_test = out-of-bounds pointer use
const_eval_incompatible_calling_conventions =
calling a function with calling convention {$callee_conv} using calling convention {$caller_conv}

Expand Down Expand Up @@ -206,7 +211,6 @@ const_eval_long_running =

const_eval_max_num_nodes_in_const = maximum number of nodes exceeded in constant {$global_const_id}

const_eval_memory_access_test = memory access failed
const_eval_memory_exhausted =
tried to allocate more memory than available to compiler

Expand Down Expand Up @@ -287,8 +291,6 @@ const_eval_offset_from_out_of_bounds =
`{$name}` called on two different pointers where the memory range between them is not in-bounds of an allocation
const_eval_offset_from_overflow =
`{$name}` called when first pointer is too far ahead of second
const_eval_offset_from_test =
out-of-bounds `offset_from` origin
const_eval_offset_from_underflow =
`{$name}` called when first pointer is too far before second
const_eval_offset_from_unsigned_overflow =
Expand All @@ -312,27 +314,25 @@ const_eval_partial_pointer_overwrite =
unable to overwrite parts of a pointer in memory at {$ptr}
const_eval_pointer_arithmetic_overflow =
overflowing pointer arithmetic: the total offset in bytes does not fit in an `isize`
const_eval_pointer_arithmetic_test = out-of-bounds pointer arithmetic

const_eval_pointer_out_of_bounds =
{$bad_pointer_message}: {const_eval_expected_inbounds_pointer}, but got {$pointer} {$ptr_offset_is_neg ->
[true] which points to before the beginning of the allocation
*[false] {$inbounds_size_is_neg ->
[true] {$ptr_offset_abs ->
[0] which is at the beginning of the allocation
*[other] which does not have enough space to the beginning of the allocation
}
*[false] {$alloc_size_minus_ptr_offset ->
[0] which is at or beyond the end of the allocation of size {$alloc_size ->
{const_eval_bad_pointer_op_attempting}, but got {$pointer} which {$inbounds_size_is_neg ->
[false] {$alloc_size_minus_ptr_offset ->
[0] is at or beyond the end of the allocation of size {$alloc_size ->
[1] 1 byte
*[x] {$alloc_size} bytes
}
[1] which is only 1 byte from the end of the allocation
*[x] which is only {$alloc_size_minus_ptr_offset} bytes from the end of the allocation
[1] is only 1 byte from the end of the allocation
*[x] is only {$alloc_size_minus_ptr_offset} bytes from the end of the allocation
}
*[true] {$ptr_offset_abs ->
[0] is at the beginning of the allocation
*[other] is only {$ptr_offset_abs} bytes from the beginning of the allocation
}
}
}

const_eval_pointer_use_after_free =
{$bad_pointer_message}: {$alloc_id} has been freed, so this pointer is dangling
{const_eval_bad_pointer_op}: {$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
33 changes: 10 additions & 23 deletions compiler/rustc_const_eval/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,14 @@ use either::Either;
use rustc_abi::WrappingRange;
use rustc_errors::codes::*;
use rustc_errors::{
Diag, DiagArgValue, DiagCtxtHandle, DiagMessage, Diagnostic, EmissionGuarantee, Level,
MultiSpan, Subdiagnostic,
Diag, DiagArgValue, DiagMessage, Diagnostic, EmissionGuarantee, Level, MultiSpan, Subdiagnostic,
};
use rustc_hir::ConstContext;
use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
use rustc_middle::mir::interpret::{
CheckInAllocMsg, CtfeProvenance, ExpectedKind, InterpErrorKind, InvalidMetaKind,
InvalidProgramInfo, Misalignment, Pointer, PointerKind, ResourceExhaustionInfo,
UndefinedBehaviorInfo, UnsupportedOpInfo, ValidationErrorInfo,
CtfeProvenance, ExpectedKind, InterpErrorKind, InvalidMetaKind, InvalidProgramInfo,
Misalignment, Pointer, PointerKind, ResourceExhaustionInfo, UndefinedBehaviorInfo,
UnsupportedOpInfo, ValidationErrorInfo,
};
use rustc_middle::ty::{self, Mutability, Ty};
use rustc_span::{Span, Symbol};
Expand Down Expand Up @@ -498,19 +497,6 @@ pub trait ReportErrorExt {
}
}

fn bad_pointer_message(msg: CheckInAllocMsg, dcx: DiagCtxtHandle<'_>) -> String {
use crate::fluent_generated::*;

let msg = match msg {
CheckInAllocMsg::MemoryAccessTest => const_eval_memory_access_test,
CheckInAllocMsg::PointerArithmeticTest => const_eval_pointer_arithmetic_test,
CheckInAllocMsg::OffsetFromTest => const_eval_offset_from_test,
CheckInAllocMsg::InboundsTest => const_eval_in_bounds_test,
};

dcx.eagerly_translate_to_string(msg, [].into_iter())
}

impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
fn diagnostic_message(&self) -> DiagMessage {
use UndefinedBehaviorInfo::*;
Expand Down Expand Up @@ -564,7 +550,6 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {

fn add_args<G: EmissionGuarantee>(self, diag: &mut Diag<'_, G>) {
use UndefinedBehaviorInfo::*;
let dcx = diag.dcx;
match self {
Ub(_) => {}
Custom(custom) => {
Expand Down Expand Up @@ -612,12 +597,10 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
diag.arg("vtable_dyn_type", vtable_dyn_type.to_string());
}
PointerUseAfterFree(alloc_id, msg) => {
diag.arg("alloc_id", alloc_id)
.arg("bad_pointer_message", bad_pointer_message(msg, dcx));
diag.arg("alloc_id", alloc_id).arg("operation", format!("{:?}", msg));
}
PointerOutOfBounds { alloc_id, alloc_size, ptr_offset, inbounds_size, msg } => {
diag.arg("alloc_size", alloc_size.bytes());
diag.arg("bad_pointer_message", bad_pointer_message(msg, dcx));
diag.arg("pointer", {
let mut out = format!("{:?}", alloc_id);
if ptr_offset > 0 {
Expand All @@ -627,14 +610,17 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
}
out
});
diag.arg("inbounds_size", inbounds_size);
diag.arg("inbounds_size_is_neg", inbounds_size < 0);
diag.arg("inbounds_size_abs", inbounds_size.unsigned_abs());
diag.arg("ptr_offset", ptr_offset);
diag.arg("ptr_offset_is_neg", ptr_offset < 0);
diag.arg("ptr_offset_abs", ptr_offset.unsigned_abs());
diag.arg(
"alloc_size_minus_ptr_offset",
alloc_size.bytes().saturating_sub(ptr_offset as u64),
);
diag.arg("operation", format!("{:?}", msg));
}
DanglingIntPointer { addr, inbounds_size, msg } => {
if addr != 0 {
Expand All @@ -644,9 +630,10 @@ impl<'a> ReportErrorExt for UndefinedBehaviorInfo<'a> {
);
}

diag.arg("inbounds_size", inbounds_size);
diag.arg("inbounds_size_is_neg", inbounds_size < 0);
diag.arg("inbounds_size_abs", inbounds_size.unsigned_abs());
diag.arg("bad_pointer_message", bad_pointer_message(msg, dcx));
diag.arg("operation", format!("{:?}", msg));
}
AlignmentCheckFailed(Misalignment { required, has }, msg) => {
diag.arg("required", required.bytes());
Expand Down
10 changes: 7 additions & 3 deletions compiler/rustc_const_eval/src/interpret/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {

// Check that the memory between them is dereferenceable at all, starting from the
// origin pointer: `dist` is `a - b`, so it is based on `b`.
self.check_ptr_access_signed(b, dist, CheckInAllocMsg::OffsetFromTest)
self.check_ptr_access_signed(b, dist, CheckInAllocMsg::Dereferenceable)
.map_err_kind(|_| {
// This could mean they point to different allocations, or they point to the same allocation
// but not the entire range between the pointers is in-bounds.
Expand All @@ -373,7 +373,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
self.check_ptr_access_signed(
a,
dist.checked_neg().unwrap(), // i64::MIN is impossible as no allocation can be that large
CheckInAllocMsg::OffsetFromTest,
CheckInAllocMsg::Dereferenceable,
)
.map_err_kind(|_| {
// Make the error more specific.
Expand Down Expand Up @@ -652,7 +652,11 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
offset_bytes: i64,
) -> InterpResult<'tcx, Pointer<Option<M::Provenance>>> {
// The offset must be in bounds starting from `ptr`.
self.check_ptr_access_signed(ptr, offset_bytes, CheckInAllocMsg::PointerArithmeticTest)?;
self.check_ptr_access_signed(
ptr,
offset_bytes,
CheckInAllocMsg::InboundsPointerArithmetic,
)?;
// This also implies that there is no overflow, so we are done.
interp_ok(ptr.wrapping_signed_offset(offset_bytes, self))
}
Expand Down
16 changes: 8 additions & 8 deletions compiler/rustc_const_eval/src/interpret/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
kind = "static_mem"
)
}
None => err_ub!(PointerUseAfterFree(alloc_id, CheckInAllocMsg::MemoryAccessTest)),
None => err_ub!(PointerUseAfterFree(alloc_id, CheckInAllocMsg::MemoryAccess)),
})
.into();
};
Expand Down Expand Up @@ -414,10 +414,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
self,
ptr,
size,
CheckInAllocMsg::MemoryAccessTest,
CheckInAllocMsg::MemoryAccess,
|this, alloc_id, offset, prov| {
let (size, align) = this
.get_live_alloc_size_and_align(alloc_id, CheckInAllocMsg::MemoryAccessTest)?;
let (size, align) =
this.get_live_alloc_size_and_align(alloc_id, CheckInAllocMsg::MemoryAccess)?;
interp_ok((size, align, (alloc_id, offset, prov)))
},
)
Expand Down Expand Up @@ -613,7 +613,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
}
Some(GlobalAlloc::Function { .. }) => throw_ub!(DerefFunctionPointer(id)),
Some(GlobalAlloc::VTable(..)) => throw_ub!(DerefVTablePointer(id)),
None => throw_ub!(PointerUseAfterFree(id, CheckInAllocMsg::MemoryAccessTest)),
None => throw_ub!(PointerUseAfterFree(id, CheckInAllocMsg::MemoryAccess)),
Some(GlobalAlloc::Static(def_id)) => {
assert!(self.tcx.is_static(def_id));
// Thread-local statics do not have a constant address. They *must* be accessed via
Expand Down Expand Up @@ -707,7 +707,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
self,
ptr,
size_i64,
CheckInAllocMsg::MemoryAccessTest,
CheckInAllocMsg::MemoryAccess,
|this, alloc_id, offset, prov| {
let alloc = this.get_alloc_raw(alloc_id)?;
interp_ok((alloc.size(), alloc.align, (alloc_id, offset, prov, alloc)))
Expand Down Expand Up @@ -809,7 +809,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
self,
ptr,
size_i64,
CheckInAllocMsg::MemoryAccessTest,
CheckInAllocMsg::MemoryAccess,
|this, alloc_id, offset, prov| {
let (alloc, machine) = this.get_alloc_raw_mut(alloc_id)?;
interp_ok((alloc.size(), alloc.align, (alloc_id, offset, prov, alloc, machine)))
Expand Down Expand Up @@ -1615,7 +1615,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
err_ub!(DanglingIntPointer {
addr: offset,
inbounds_size: size,
msg: CheckInAllocMsg::InboundsTest
msg: CheckInAllocMsg::Dereferenceable
})
})
.into()
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/validity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
self.ecx.check_ptr_access(
place.ptr(),
size,
CheckInAllocMsg::InboundsTest, // will anyway be replaced by validity message
CheckInAllocMsg::Dereferenceable, // will anyway be replaced by validity message
),
self.path,
Ub(DanglingIntPointer { addr: 0, .. }) => NullPtr { ptr_kind },
Expand Down
8 changes: 3 additions & 5 deletions compiler/rustc_middle/src/mir/interpret/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -221,13 +221,11 @@ pub enum InvalidProgramInfo<'tcx> {
#[derive(Debug, Copy, Clone)]
pub enum CheckInAllocMsg {
/// We are access memory.
MemoryAccessTest,
MemoryAccess,
/// We are doing pointer arithmetic.
PointerArithmeticTest,
/// We are doing pointer offset_from.
OffsetFromTest,
InboundsPointerArithmetic,
/// None of the above -- generic/unspecific inbounds test.
InboundsTest,
Dereferenceable,
}

/// Details of which pointer is not aligned.
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/src/borrow_tracker/stacked_borrows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,7 @@ trait EvalContextPrivExt<'tcx, 'ecx>: crate::MiriInterpCxExt<'tcx> {
) -> InterpResult<'tcx, Option<Provenance>> {
let this = self.eval_context_mut();
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
this.check_ptr_access(place.ptr(), size, CheckInAllocMsg::InboundsTest)?;
this.check_ptr_access(place.ptr(), size, CheckInAllocMsg::Dereferenceable)?;

// It is crucial that this gets called on all code paths, to ensure we track tag creation.
let log_creation = |this: &MiriInterpCx<'tcx>,
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/src/borrow_tracker/tree_borrows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ trait EvalContextPrivExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Make sure the new permission makes sense as the initial permission of a fresh tag.
assert!(new_perm.initial_state.is_initial());
// Ensure we bail out if the pointer goes out-of-bounds (see miri#1050).
this.check_ptr_access(place.ptr(), ptr_size, CheckInAllocMsg::InboundsTest)?;
this.check_ptr_access(place.ptr(), ptr_size, CheckInAllocMsg::Dereferenceable)?;

// It is crucial that this gets called on all code paths, to ensure we track tag creation.
let log_creation = |this: &MiriInterpCx<'tcx>,
Expand Down
2 changes: 1 addition & 1 deletion src/tools/miri/src/shims/unix/android/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub fn prctl<'tcx>(
ecx.check_ptr_access(
name.to_pointer(ecx)?,
Size::from_bytes(TASK_COMM_LEN),
CheckInAllocMsg::MemoryAccessTest,
CheckInAllocMsg::MemoryAccess,
)?;
let res = ecx.pthread_getname_np(thread, name, len, /* truncate*/ false)?;
assert_eq!(res, ThreadNameResult::Ok);
Expand Down
4 changes: 2 additions & 2 deletions src/tools/miri/src/shims/unix/fd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
trace!("Reading from FD {}, size {}", fd_num, count);

// Check that the *entire* buffer is actually valid memory.
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;

// We cap the number of read bytes to the largest value that we are able to fit in both the
// host's and target's `isize`. This saves us from having to handle overflows later.
Expand Down Expand Up @@ -292,7 +292,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
// Isolation check is done via `FileDescription` trait.

// Check that the *entire* buffer is actually valid memory.
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccessTest)?;
this.check_ptr_access(buf, Size::from_bytes(count), CheckInAllocMsg::MemoryAccess)?;

// We cap the number of written bytes to the largest value that we are able to fit in both the
// host's and target's `isize`. This saves us from having to handle overflows later.
Expand Down
4 changes: 2 additions & 2 deletions src/tools/miri/tests/fail-dep/libc/affinity.stderr
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
error: Undefined Behavior: memory access failed: expected a pointer to 129 bytes of memory, but got ALLOC which is only 128 bytes from the end of the allocation
error: Undefined Behavior: memory access failed: attempting to access 129 bytes, but got ALLOC which is only 128 bytes from the end of the allocation
--> tests/fail-dep/libc/affinity.rs:LL:CC
|
LL | let err = unsafe { sched_setaffinity(PID, size_of::<cpu_set_t>() + 1, &cpuset) };
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: expected a pointer to 129 bytes of memory, but got ALLOC which is only 128 bytes from the end of the allocation
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ memory access failed: attempting to access 129 bytes, but got ALLOC which is only 128 bytes from the end of the allocation
|
= 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
Loading
Loading