forked from rust-lang/miri
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Auto merge of #104054 - RalfJung:byte-provenance, r=oli-obk
interpret: support for per-byte provenance Also factors the provenance map into its own module. The third commit does the same for the init mask. I can move it in a separate PR if you prefer. Fixes rust-lang#2181 r? `@oli-obk`
- Loading branch information
Showing
10 changed files
with
220 additions
and
60 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 2 additions & 2 deletions
4
tests/fail/pointer_partial_overwrite.stderr → ...ovenance/pointer_partial_overwrite.stderr
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
//@error-pattern: memory is uninitialized at [0x4..0x8] | ||
//@normalize-stderr-test: "a[0-9]+" -> "ALLOC" | ||
#![feature(strict_provenance)] | ||
|
||
// Test printing allocations that contain single-byte provenance. | ||
|
||
use std::alloc::{alloc, dealloc, Layout}; | ||
use std::mem::{self, MaybeUninit}; | ||
use std::slice::from_raw_parts; | ||
|
||
fn byte_with_provenance<T>(val: u8, prov: *const T) -> MaybeUninit<u8> { | ||
let ptr = prov.with_addr(val as usize); | ||
let bytes: [MaybeUninit<u8>; mem::size_of::<*const ()>()] = unsafe { mem::transmute(ptr) }; | ||
let lsb = if cfg!(target_endian = "little") { 0 } else { bytes.len() - 1 }; | ||
bytes[lsb] | ||
} | ||
|
||
fn main() { | ||
let layout = Layout::from_size_align(16, 8).unwrap(); | ||
unsafe { | ||
let ptr = alloc(layout); | ||
let ptr_raw = ptr.cast::<MaybeUninit<u8>>(); | ||
*ptr_raw.add(0) = byte_with_provenance(0x42, &42u8); | ||
*ptr.add(1) = 0x12; | ||
*ptr.add(2) = 0x13; | ||
*ptr_raw.add(3) = byte_with_provenance(0x43, &0u8); | ||
let slice1 = from_raw_parts(ptr, 8); | ||
let slice2 = from_raw_parts(ptr.add(8), 8); | ||
drop(slice1.cmp(slice2)); | ||
dealloc(ptr, layout); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
error: Undefined Behavior: reading memory at ALLOC[0x0..0x8], but memory is uninitialized at [0x4..0x8], and this operation requires initialized memory | ||
--> RUSTLIB/core/src/slice/cmp.rs:LL:CC | ||
| | ||
LL | let mut order = unsafe { memcmp(left.as_ptr(), right.as_ptr(), len) as isize }; | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at ALLOC[0x0..0x8], but memory is uninitialized at [0x4..0x8], and this operation requires initialized memory | ||
| | ||
= 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 | ||
= note: BACKTRACE: | ||
= note: inside `<u8 as core::slice::cmp::SliceOrd>::compare` at RUSTLIB/core/src/slice/cmp.rs:LL:CC | ||
= note: inside `core::slice::cmp::<impl std::cmp::Ord for [u8]>::cmp` at RUSTLIB/core/src/slice/cmp.rs:LL:CC | ||
note: inside `main` at $DIR/uninit_buffer_with_provenance.rs:LL:CC | ||
--> $DIR/uninit_buffer_with_provenance.rs:LL:CC | ||
| | ||
LL | drop(slice1.cmp(slice2)); | ||
| ^^^^^^^^^^^^^^^^^^ | ||
|
||
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace | ||
|
||
Uninitialized memory occurred at ALLOC[0x4..0x8], in this allocation: | ||
ALLOC (Rust heap, size: 16, align: 8) { | ||
╾42[ALLOC]<TAG> (1 ptr byte)╼ 12 13 ╾43[ALLOC]<TAG> (1 ptr byte)╼ __ __ __ __ __ __ __ __ __ __ __ __ │ ━..━░░░░░░░░░░░░ | ||
} | ||
ALLOC (global (static or const), size: 1, align: 1) { | ||
2a │ * | ||
} | ||
ALLOC (global (static or const), size: 1, align: 1) { | ||
00 │ . | ||
} | ||
|
||
error: aborting due to previous error | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
#![feature(strict_provenance)] | ||
#![feature(pointer_byte_offsets)] | ||
use std::{mem, ptr}; | ||
|
||
const PTR_SIZE: usize = mem::size_of::<&i32>(); | ||
|
||
fn main() { | ||
basic(); | ||
partial_overwrite_then_restore(); | ||
bytewise_ptr_methods(); | ||
bytewise_custom_memcpy(); | ||
bytewise_custom_memcpy_chunked(); | ||
} | ||
|
||
/// Some basic smoke tests for provenance. | ||
fn basic() { | ||
let x = &42; | ||
let ptr = x as *const i32; | ||
let addr: usize = unsafe { mem::transmute(ptr) }; // an integer without provenance | ||
// But we can give provenance back via `with_addr`. | ||
let ptr_back = ptr.with_addr(addr); | ||
assert_eq!(unsafe { *ptr_back }, 42); | ||
|
||
// It is preserved by MaybeUninit. | ||
let addr_mu: mem::MaybeUninit<usize> = unsafe { mem::transmute(ptr) }; | ||
let ptr_back: *const i32 = unsafe { mem::transmute(addr_mu) }; | ||
assert_eq!(unsafe { *ptr_back }, 42); | ||
} | ||
|
||
/// Overwrite one byte of a pointer, then restore it. | ||
fn partial_overwrite_then_restore() { | ||
unsafe fn ptr_bytes<'x>(ptr: &'x mut *const i32) -> &'x mut [mem::MaybeUninit<u8>; PTR_SIZE] { | ||
mem::transmute(ptr) | ||
} | ||
|
||
// Returns a value with the same provenance as `x` but 0 for the integer value. | ||
// `x` must be initialized. | ||
unsafe fn zero_with_provenance(x: mem::MaybeUninit<u8>) -> mem::MaybeUninit<u8> { | ||
let ptr = [x; PTR_SIZE]; | ||
let ptr: *const i32 = mem::transmute(ptr); | ||
let mut ptr = ptr.with_addr(0); | ||
ptr_bytes(&mut ptr)[0] | ||
} | ||
|
||
unsafe { | ||
let ptr = &42; | ||
let mut ptr = ptr as *const i32; | ||
// Get a bytewise view of the pointer. | ||
let ptr_bytes = ptr_bytes(&mut ptr); | ||
|
||
// The highest bytes must be 0 for this to work. | ||
let hi = if cfg!(target_endian = "little") { ptr_bytes.len() - 1 } else { 0 }; | ||
assert_eq!(*ptr_bytes[hi].as_ptr().cast::<u8>(), 0); | ||
// Overwrite provenance on the last byte. | ||
ptr_bytes[hi] = mem::MaybeUninit::new(0); | ||
// Restore it from the another byte. | ||
ptr_bytes[hi] = zero_with_provenance(ptr_bytes[1]); | ||
|
||
// Now ptr should be good again. | ||
assert_eq!(*ptr, 42); | ||
} | ||
} | ||
|
||
fn bytewise_ptr_methods() { | ||
let mut ptr1 = &1; | ||
let mut ptr2 = &2; | ||
|
||
// Swap them, bytewise. | ||
unsafe { | ||
ptr::swap_nonoverlapping( | ||
&mut ptr1 as *mut _ as *mut mem::MaybeUninit<u8>, | ||
&mut ptr2 as *mut _ as *mut mem::MaybeUninit<u8>, | ||
mem::size_of::<&i32>(), | ||
); | ||
} | ||
|
||
// Make sure they still work. | ||
assert_eq!(*ptr1, 2); | ||
assert_eq!(*ptr2, 1); | ||
|
||
// TODO: also test ptr::swap, ptr::copy, ptr::copy_nonoverlapping. | ||
} | ||
|
||
fn bytewise_custom_memcpy() { | ||
unsafe fn memcpy<T>(to: *mut T, from: *const T) { | ||
let to = to.cast::<mem::MaybeUninit<u8>>(); | ||
let from = from.cast::<mem::MaybeUninit<u8>>(); | ||
for i in 0..mem::size_of::<T>() { | ||
let b = from.add(i).read(); | ||
to.add(i).write(b); | ||
} | ||
} | ||
|
||
let ptr1 = &1; | ||
let mut ptr2 = &2; | ||
|
||
// Copy, bytewise. | ||
unsafe { memcpy(&mut ptr2, &ptr1) }; | ||
|
||
// Make sure they still work. | ||
assert_eq!(*ptr1, 1); | ||
assert_eq!(*ptr2, 1); | ||
} | ||
|
||
fn bytewise_custom_memcpy_chunked() { | ||
unsafe fn memcpy<T>(to: *mut T, from: *const T) { | ||
assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0); | ||
let count = mem::size_of::<T>() / mem::size_of::<usize>(); | ||
let to = to.cast::<mem::MaybeUninit<usize>>(); | ||
let from = from.cast::<mem::MaybeUninit<usize>>(); | ||
for i in 0..count { | ||
let b = from.add(i).read(); | ||
to.add(i).write(b); | ||
} | ||
} | ||
|
||
// Prepare an array where pointers are stored at... interesting... offsets. | ||
let mut data = [0usize; 2 * PTR_SIZE]; | ||
let mut offsets = vec![]; | ||
for i in 0..mem::size_of::<usize>() { | ||
// We have 2*PTR_SIZE room for each of these pointers. | ||
let base = i * 2 * PTR_SIZE; | ||
// This one is mis-aligned by `i`. | ||
let offset = base + i; | ||
offsets.push(offset); | ||
// Store it there. | ||
unsafe { data.as_mut_ptr().byte_add(offset).cast::<&i32>().write_unaligned(&42) }; | ||
} | ||
|
||
// Now memcpy that. | ||
let mut data2 = [0usize; 2 * PTR_SIZE]; | ||
unsafe { memcpy(&mut data2, &data) }; | ||
|
||
// And check the result. | ||
for &offset in &offsets { | ||
let ptr = unsafe { data2.as_ptr().byte_add(offset).cast::<&i32>().read_unaligned() }; | ||
assert_eq!(*ptr, 42); | ||
} | ||
} |