Rollup merge of #128277 - RalfJung:offset_from_wildcard, r=oli-obk

miri: fix offset_from behavior on wildcard pointers

offset_from wouldn't behave correctly when the "end" pointer was a wildcard pointer (result of an int2ptr cast) just at the end of the allocation. Fix that by expressing the "same allocation" check in terms of two `check_ptr_access_signed` instead of something specific to offset_from, which is both more canonical and works better with wildcard pointers.

The second commit just improves diagnostics: I wanted the "pointer is dangling (has no provenance)" message to say how many bytes of memory it expected to see (since if it were 0 bytes, this would actually be legal, so it's good to tell the user that it's not 0 bytes). And then I was annoying that the error looks so different for when you deref a dangling pointer vs an out-of-bounds pointer so I made them more similar.

Fixes https://github.com/rust-lang/miri/issues/3767
This commit is contained in:
Matthias Krüger
2024-07-29 11:42:34 +02:00
committed by GitHub
80 changed files with 301 additions and 239 deletions

View File

@@ -411,6 +411,25 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
Ok(())
}
/// Check whether the given pointer points to live memory for a signed amount of bytes.
/// A negative amounts means that the given range of memory to the left of the pointer
/// needs to be dereferenceable.
pub fn check_ptr_access_signed(
&self,
ptr: Pointer<Option<M::Provenance>>,
size: i64,
msg: CheckInAllocMsg,
) -> InterpResult<'tcx> {
if let Ok(size) = u64::try_from(size) {
self.check_ptr_access(ptr, Size::from_bytes(size), msg)
} else {
// Compute the pointer at the beginning of the range, and do the standard
// dereferenceability check from there.
let begin_ptr = ptr.wrapping_signed_offset(size, self);
self.check_ptr_access(begin_ptr, Size::from_bytes(size.unsigned_abs()), msg)
}
}
/// Low-level helper function to check if a ptr is in-bounds and potentially return a reference
/// to the allocation it points to. Supports both shared and mutable references, as the actual
/// checking is offloaded to a helper closure.
@@ -437,7 +456,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
Ok(match self.ptr_try_get_alloc_id(ptr) {
Err(addr) => {
// We couldn't get a proper allocation.
throw_ub!(DanglingIntPointer(addr, msg));
throw_ub!(DanglingIntPointer { addr, inbounds_size: size, msg });
}
Ok((alloc_id, offset, prov)) => {
let (alloc_size, _alloc_align, ret_val) = alloc_size(alloc_id, offset, prov)?;
@@ -448,7 +467,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
alloc_id,
alloc_size,
ptr_offset: self.target_usize_to_isize(offset.bytes()),
ptr_size: size,
inbounds_size: size,
msg,
})
}
@@ -1421,7 +1440,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
ptr: Pointer<Option<M::Provenance>>,
) -> InterpResult<'tcx, (AllocId, Size, M::ProvenanceExtra)> {
self.ptr_try_get_alloc_id(ptr).map_err(|offset| {
err_ub!(DanglingIntPointer(offset, CheckInAllocMsg::InboundsTest)).into()
err_ub!(DanglingIntPointer {
addr: offset,
// We don't know the actually required size.
inbounds_size: Size::ZERO,
msg: CheckInAllocMsg::InboundsTest
})
.into()
})
}
}