separate bounds-check from alignment check

This commit is contained in:
Ralf Jung
2023-09-26 21:08:21 +02:00
parent e24835c6e0
commit b131fc10ae
13 changed files with 93 additions and 140 deletions

View File

@@ -258,14 +258,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
None => self.get_alloc_raw(alloc_id)?.size(),
};
// This will also call the access hooks.
self.mem_copy(
ptr,
Align::ONE,
new_ptr.into(),
Align::ONE,
old_size.min(new_size),
/*nonoverlapping*/ true,
)?;
self.mem_copy(ptr, new_ptr.into(), old_size.min(new_size), /*nonoverlapping*/ true)?;
self.deallocate_ptr(ptr, old_size_and_align, kind)?;
Ok(new_ptr)
@@ -367,12 +360,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
&self,
ptr: Pointer<Option<M::Provenance>>,
size: Size,
align: Align,
) -> InterpResult<'tcx, Option<(AllocId, Size, M::ProvenanceExtra)>> {
self.check_and_deref_ptr(
ptr,
size,
align,
CheckInAllocMsg::MemoryAccessTest,
|alloc_id, offset, prov| {
let (size, align) = self
@@ -382,17 +373,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
)
}
/// Check if the given pointer points to live memory of given `size` and `align`.
/// Check if the given pointer points to live memory of the given `size`.
/// The caller can control the error message for the out-of-bounds case.
#[inline(always)]
pub fn check_ptr_access_align(
pub fn check_ptr_access(
&self,
ptr: Pointer<Option<M::Provenance>>,
size: Size,
align: Align,
msg: CheckInAllocMsg,
) -> InterpResult<'tcx> {
self.check_and_deref_ptr(ptr, size, align, msg, |alloc_id, _, _| {
self.check_and_deref_ptr(ptr, size, msg, |alloc_id, _, _| {
let (size, align) = self.get_live_alloc_size_and_align(alloc_id, msg)?;
Ok((size, align, ()))
})?;
@@ -408,7 +398,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
&self,
ptr: Pointer<Option<M::Provenance>>,
size: Size,
align: Align,
msg: CheckInAllocMsg,
alloc_size: impl FnOnce(
AllocId,
@@ -423,17 +412,10 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
if size.bytes() > 0 || addr == 0 {
throw_ub!(DanglingIntPointer(addr, msg));
}
// Must be aligned.
if M::enforce_alignment(self) && align.bytes() > 1 {
self.check_misalign(
Self::offset_misalignment(addr, align),
CheckAlignMsg::AccessedPtr,
)?;
}
None
}
Ok((alloc_id, offset, prov)) => {
let (alloc_size, alloc_align, ret_val) = alloc_size(alloc_id, offset, prov)?;
let (alloc_size, _alloc_align, ret_val) = alloc_size(alloc_id, offset, prov)?;
// Test bounds. This also ensures non-null.
// It is sufficient to check this for the end pointer. Also check for overflow!
if offset.checked_add(size, &self.tcx).map_or(true, |end| end > alloc_size) {
@@ -449,14 +431,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
if M::Provenance::OFFSET_IS_ADDR {
assert_ne!(ptr.addr(), Size::ZERO);
}
// Test align. Check this last; if both bounds and alignment are violated
// we want the error to be about the bounds.
if M::enforce_alignment(self) && align.bytes() > 1 {
self.check_misalign(
self.alloc_misalignment(ptr, offset, align, alloc_align),
CheckAlignMsg::AccessedPtr,
)?;
}
// We can still be zero-sized in this branch, in which case we have to
// return `None`.
@@ -465,7 +439,6 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
})
}
#[inline(always)]
pub(super) fn check_misalign(
&self,
misaligned: Option<Misalignment>,
@@ -477,53 +450,54 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
Ok(())
}
#[must_use]
fn offset_misalignment(offset: u64, align: Align) -> Option<Misalignment> {
if offset % align.bytes() == 0 {
None
} else {
// The biggest power of two through which `offset` is divisible.
let offset_pow2 = 1 << offset.trailing_zeros();
Some(Misalignment { has: Align::from_bytes(offset_pow2).unwrap(), required: align })
}
}
#[must_use]
fn alloc_misalignment(
&self,
ptr: Pointer<Option<M::Provenance>>,
offset: Size,
align: Align,
alloc_align: Align,
) -> Option<Misalignment> {
if M::use_addr_for_alignment_check(self) {
// `use_addr_for_alignment_check` can only be true if `OFFSET_IS_ADDR` is true.
Self::offset_misalignment(ptr.addr().bytes(), align)
} else {
// Check allocation alignment and offset alignment.
if alloc_align.bytes() < align.bytes() {
Some(Misalignment { has: alloc_align, required: align })
} else {
Self::offset_misalignment(offset.bytes(), align)
}
}
}
pub(super) fn is_ptr_misaligned(
&self,
ptr: Pointer<Option<M::Provenance>>,
align: Align,
) -> Option<Misalignment> {
if !M::enforce_alignment(self) {
if !M::enforce_alignment(self) || align.bytes() == 1 {
return None;
}
match self.ptr_try_get_alloc_id(ptr) {
Err(addr) => Self::offset_misalignment(addr, align),
Ok((alloc_id, offset, _prov)) => {
let (_size, alloc_align, _kind) = self.get_alloc_info(alloc_id);
self.alloc_misalignment(ptr, offset, align, alloc_align)
#[inline]
fn offset_misalignment(offset: u64, align: Align) -> Option<Misalignment> {
if offset % align.bytes() == 0 {
None
} else {
// The biggest power of two through which `offset` is divisible.
let offset_pow2 = 1 << offset.trailing_zeros();
Some(Misalignment { has: Align::from_bytes(offset_pow2).unwrap(), required: align })
}
}
match self.ptr_try_get_alloc_id(ptr) {
Err(addr) => offset_misalignment(addr, align),
Ok((alloc_id, offset, _prov)) => {
let (_size, alloc_align, _kind) = self.get_alloc_info(alloc_id);
if M::use_addr_for_alignment_check(self) {
// `use_addr_for_alignment_check` can only be true if `OFFSET_IS_ADDR` is true.
offset_misalignment(ptr.addr().bytes(), align)
} else {
// Check allocation alignment and offset alignment.
if alloc_align.bytes() < align.bytes() {
Some(Misalignment { has: alloc_align, required: align })
} else {
offset_misalignment(offset.bytes(), align)
}
}
}
}
}
/// Checks a pointer for misalignment.
///
/// The error assumes this is checking the pointer used directly for an access.
pub fn check_ptr_align(
&self,
ptr: Pointer<Option<M::Provenance>>,
align: Align,
) -> InterpResult<'tcx> {
self.check_misalign(self.is_ptr_misaligned(ptr, align), CheckAlignMsg::AccessedPtr)
}
}
@@ -629,18 +603,16 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
}
}
/// "Safe" (bounds and align-checked) allocation access.
/// Bounds-checked *but not align-checked* allocation access.
pub fn get_ptr_alloc<'a>(
&'a self,
ptr: Pointer<Option<M::Provenance>>,
size: Size,
align: Align,
) -> InterpResult<'tcx, Option<AllocRef<'a, 'tcx, M::Provenance, M::AllocExtra, M::Bytes>>>
{
let ptr_and_alloc = self.check_and_deref_ptr(
ptr,
size,
align,
CheckInAllocMsg::MemoryAccessTest,
|alloc_id, offset, prov| {
let alloc = self.get_alloc_raw(alloc_id)?;
@@ -701,15 +673,14 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
Ok((alloc, &mut self.machine))
}
/// "Safe" (bounds and align-checked) allocation access.
/// Bounds-checked *but not align-checked* allocation access.
pub fn get_ptr_alloc_mut<'a>(
&'a mut self,
ptr: Pointer<Option<M::Provenance>>,
size: Size,
align: Align,
) -> InterpResult<'tcx, Option<AllocRefMut<'a, 'tcx, M::Provenance, M::AllocExtra, M::Bytes>>>
{
let parts = self.get_ptr_access(ptr, size, align)?;
let parts = self.get_ptr_access(ptr, size)?;
if let Some((alloc_id, offset, prov)) = parts {
let tcx = *self.tcx;
// FIXME: can we somehow avoid looking up the allocation twice here?
@@ -1066,7 +1037,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
ptr: Pointer<Option<M::Provenance>>,
size: Size,
) -> InterpResult<'tcx, &[u8]> {
let Some(alloc_ref) = self.get_ptr_alloc(ptr, size, Align::ONE)? else {
let Some(alloc_ref) = self.get_ptr_alloc(ptr, size)? else {
// zero-sized access
return Ok(&[]);
};
@@ -1092,7 +1063,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
assert_eq!(lower, len, "can only write iterators with a precise length");
let size = Size::from_bytes(len);
let Some(alloc_ref) = self.get_ptr_alloc_mut(ptr, size, Align::ONE)? else {
let Some(alloc_ref) = self.get_ptr_alloc_mut(ptr, size)? else {
// zero-sized access
assert_matches!(src.next(), None, "iterator said it was empty but returned an element");
return Ok(());
@@ -1117,29 +1088,25 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
pub fn mem_copy(
&mut self,
src: Pointer<Option<M::Provenance>>,
src_align: Align,
dest: Pointer<Option<M::Provenance>>,
dest_align: Align,
size: Size,
nonoverlapping: bool,
) -> InterpResult<'tcx> {
self.mem_copy_repeatedly(src, src_align, dest, dest_align, size, 1, nonoverlapping)
self.mem_copy_repeatedly(src, dest, size, 1, nonoverlapping)
}
pub fn mem_copy_repeatedly(
&mut self,
src: Pointer<Option<M::Provenance>>,
src_align: Align,
dest: Pointer<Option<M::Provenance>>,
dest_align: Align,
size: Size,
num_copies: u64,
nonoverlapping: bool,
) -> InterpResult<'tcx> {
let tcx = self.tcx;
// We need to do our own bounds-checks.
let src_parts = self.get_ptr_access(src, size, src_align)?;
let dest_parts = self.get_ptr_access(dest, size * num_copies, dest_align)?; // `Size` multiplication
let src_parts = self.get_ptr_access(src, size)?;
let dest_parts = self.get_ptr_access(dest, size * num_copies)?; // `Size` multiplication
// FIXME: we look up both allocations twice here, once before for the `check_ptr_access`
// and once below to get the underlying `&[mut] Allocation`.