Skip to content

Commit eff09e7

Browse files
committed
Auto merge of rust-lang#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/miri#2181 r? `@oli-obk`
2 parents eb47a0f + be59349 commit eff09e7

10 files changed

+220
-60
lines changed

src/diagnostics.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -229,13 +229,13 @@ pub fn report_error<'tcx, 'mir>(
229229
Unsupported(
230230
UnsupportedOpInfo::ThreadLocalStatic(_) |
231231
UnsupportedOpInfo::ReadExternStatic(_) |
232-
UnsupportedOpInfo::PartialPointerOverwrite(_) | // we make memory uninit instead
232+
UnsupportedOpInfo::PartialPointerOverwrite(_) |
233+
UnsupportedOpInfo::PartialPointerCopy(_) |
233234
UnsupportedOpInfo::ReadPointerAsBytes
234235
) =>
235236
panic!("Error should never be raised by Miri: {kind:?}", kind = e.kind()),
236237
Unsupported(
237-
UnsupportedOpInfo::Unsupported(_) |
238-
UnsupportedOpInfo::PartialPointerCopy(_)
238+
UnsupportedOpInfo::Unsupported(_)
239239
) =>
240240
vec![(None, format!("this is likely not a bug in the program; it indicates that the program performed an operation that the interpreter does not support"))],
241241
UndefinedBehavior(UndefinedBehaviorInfo::AlignmentCheckFailed { .. })

src/machine.rs

+9-14
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ impl fmt::Display for MiriMemoryKind {
133133
}
134134

135135
/// Pointer provenance.
136-
#[derive(Debug, Clone, Copy)]
136+
#[derive(Clone, Copy)]
137137
pub enum Provenance {
138138
Concrete {
139139
alloc_id: AllocId,
@@ -176,18 +176,9 @@ static_assert_size!(Pointer<Provenance>, 24);
176176
#[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))]
177177
static_assert_size!(Scalar<Provenance>, 32);
178178

179-
impl interpret::Provenance for Provenance {
180-
/// We use absolute addresses in the `offset` of a `Pointer<Provenance>`.
181-
const OFFSET_IS_ADDR: bool = true;
182-
183-
/// We cannot err on partial overwrites, it happens too often in practice (due to unions).
184-
const ERR_ON_PARTIAL_PTR_OVERWRITE: bool = false;
185-
186-
fn fmt(ptr: &Pointer<Self>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187-
let (prov, addr) = ptr.into_parts(); // address is absolute
188-
write!(f, "{:#x}", addr.bytes())?;
189-
190-
match prov {
179+
impl fmt::Debug for Provenance {
180+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
181+
match self {
191182
Provenance::Concrete { alloc_id, sb } => {
192183
// Forward `alternate` flag to `alloc_id` printing.
193184
if f.alternate() {
@@ -202,9 +193,13 @@ impl interpret::Provenance for Provenance {
202193
write!(f, "[wildcard]")?;
203194
}
204195
}
205-
206196
Ok(())
207197
}
198+
}
199+
200+
impl interpret::Provenance for Provenance {
201+
/// We use absolute addresses in the `offset` of a `Pointer<Provenance>`.
202+
const OFFSET_IS_ADDR: bool = true;
208203

209204
fn get_alloc_id(self) -> Option<AllocId> {
210205
match self {

src/tag_gc.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ impl VisitTags for Operand<Provenance> {
127127

128128
impl VisitTags for Allocation<Provenance, AllocExtra> {
129129
fn visit_tags(&self, visit: &mut dyn FnMut(SbTag)) {
130-
for (_size, prov) in self.provenance().iter() {
130+
for prov in self.provenance().provenances() {
131131
prov.visit_tags(visit);
132132
}
133133

tests/fail/copy_half_a_pointer.rs

-21
This file was deleted.

tests/fail/copy_half_a_pointer.stderr

-14
This file was deleted.

tests/fail/pointer_partial_overwrite.rs renamed to tests/fail/provenance/pointer_partial_overwrite.rs

+2-5
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,13 @@
22
//@compile-flags: -Zmiri-disable-alignment-check -Zmiri-disable-stacked-borrows -Zmiri-disable-validation
33

44
// Test what happens when we overwrite parts of a pointer.
5-
// Also see <https://github.com/rust-lang/miri/issues/2181>.
65

76
fn main() {
87
let mut p = &42;
98
unsafe {
109
let ptr: *mut _ = &mut p;
11-
*(ptr as *mut u8) = 123; // if we ever support 8 bit pointers, this is gonna cause
12-
// "attempted to interpret some raw bytes as a pointer address" instead of
13-
// "attempted to read undefined bytes"
10+
*(ptr as *mut u8) = 123; // this removes provenance from one of the bytes, meaning the entire ptr is considered to have no provenance.
1411
}
15-
let x = *p; //~ ERROR: this operation requires initialized memory
12+
let x = *p; //~ ERROR: no provenance
1613
panic!("this should never print: {}", x);
1714
}

tests/fail/pointer_partial_overwrite.stderr renamed to tests/fail/provenance/pointer_partial_overwrite.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory
1+
error: Undefined Behavior: dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance)
22
--> $DIR/pointer_partial_overwrite.rs:LL:CC
33
|
44
LL | let x = *p;
5-
| ^^ using uninitialized data, but this operation requires initialized memory
5+
| ^^ dereferencing pointer failed: $HEX[noalloc] is a dangling pointer (it has no provenance)
66
|
77
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
88
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//@error-pattern: memory is uninitialized at [0x4..0x8]
2+
//@normalize-stderr-test: "a[0-9]+" -> "ALLOC"
3+
#![feature(strict_provenance)]
4+
5+
// Test printing allocations that contain single-byte provenance.
6+
7+
use std::alloc::{alloc, dealloc, Layout};
8+
use std::mem::{self, MaybeUninit};
9+
use std::slice::from_raw_parts;
10+
11+
fn byte_with_provenance<T>(val: u8, prov: *const T) -> MaybeUninit<u8> {
12+
let ptr = prov.with_addr(val as usize);
13+
let bytes: [MaybeUninit<u8>; mem::size_of::<*const ()>()] = unsafe { mem::transmute(ptr) };
14+
let lsb = if cfg!(target_endian = "little") { 0 } else { bytes.len() - 1 };
15+
bytes[lsb]
16+
}
17+
18+
fn main() {
19+
let layout = Layout::from_size_align(16, 8).unwrap();
20+
unsafe {
21+
let ptr = alloc(layout);
22+
let ptr_raw = ptr.cast::<MaybeUninit<u8>>();
23+
*ptr_raw.add(0) = byte_with_provenance(0x42, &42u8);
24+
*ptr.add(1) = 0x12;
25+
*ptr.add(2) = 0x13;
26+
*ptr_raw.add(3) = byte_with_provenance(0x43, &0u8);
27+
let slice1 = from_raw_parts(ptr, 8);
28+
let slice2 = from_raw_parts(ptr.add(8), 8);
29+
drop(slice1.cmp(slice2));
30+
dealloc(ptr, layout);
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
error: Undefined Behavior: reading memory at ALLOC[0x0..0x8], but memory is uninitialized at [0x4..0x8], and this operation requires initialized memory
2+
--> RUSTLIB/core/src/slice/cmp.rs:LL:CC
3+
|
4+
LL | let mut order = unsafe { memcmp(left.as_ptr(), right.as_ptr(), len) as isize };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ reading memory at ALLOC[0x0..0x8], but memory is uninitialized at [0x4..0x8], and this operation requires initialized memory
6+
|
7+
= help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
8+
= help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
9+
= note: BACKTRACE:
10+
= note: inside `<u8 as core::slice::cmp::SliceOrd>::compare` at RUSTLIB/core/src/slice/cmp.rs:LL:CC
11+
= note: inside `core::slice::cmp::<impl std::cmp::Ord for [u8]>::cmp` at RUSTLIB/core/src/slice/cmp.rs:LL:CC
12+
note: inside `main` at $DIR/uninit_buffer_with_provenance.rs:LL:CC
13+
--> $DIR/uninit_buffer_with_provenance.rs:LL:CC
14+
|
15+
LL | drop(slice1.cmp(slice2));
16+
| ^^^^^^^^^^^^^^^^^^
17+
18+
note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
19+
20+
Uninitialized memory occurred at ALLOC[0x4..0x8], in this allocation:
21+
ALLOC (Rust heap, size: 16, align: 8) {
22+
╾42[ALLOC]<TAG> (1 ptr byte)╼ 12 13 ╾43[ALLOC]<TAG> (1 ptr byte)╼ __ __ __ __ __ __ __ __ __ __ __ __ │ ━..━░░░░░░░░░░░░
23+
}
24+
ALLOC (global (static or const), size: 1, align: 1) {
25+
2a │ *
26+
}
27+
ALLOC (global (static or const), size: 1, align: 1) {
28+
00 │ .
29+
}
30+
31+
error: aborting due to previous error
32+

tests/pass/provenance.rs

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#![feature(strict_provenance)]
2+
#![feature(pointer_byte_offsets)]
3+
use std::{mem, ptr};
4+
5+
const PTR_SIZE: usize = mem::size_of::<&i32>();
6+
7+
fn main() {
8+
basic();
9+
partial_overwrite_then_restore();
10+
bytewise_ptr_methods();
11+
bytewise_custom_memcpy();
12+
bytewise_custom_memcpy_chunked();
13+
}
14+
15+
/// Some basic smoke tests for provenance.
16+
fn basic() {
17+
let x = &42;
18+
let ptr = x as *const i32;
19+
let addr: usize = unsafe { mem::transmute(ptr) }; // an integer without provenance
20+
// But we can give provenance back via `with_addr`.
21+
let ptr_back = ptr.with_addr(addr);
22+
assert_eq!(unsafe { *ptr_back }, 42);
23+
24+
// It is preserved by MaybeUninit.
25+
let addr_mu: mem::MaybeUninit<usize> = unsafe { mem::transmute(ptr) };
26+
let ptr_back: *const i32 = unsafe { mem::transmute(addr_mu) };
27+
assert_eq!(unsafe { *ptr_back }, 42);
28+
}
29+
30+
/// Overwrite one byte of a pointer, then restore it.
31+
fn partial_overwrite_then_restore() {
32+
unsafe fn ptr_bytes<'x>(ptr: &'x mut *const i32) -> &'x mut [mem::MaybeUninit<u8>; PTR_SIZE] {
33+
mem::transmute(ptr)
34+
}
35+
36+
// Returns a value with the same provenance as `x` but 0 for the integer value.
37+
// `x` must be initialized.
38+
unsafe fn zero_with_provenance(x: mem::MaybeUninit<u8>) -> mem::MaybeUninit<u8> {
39+
let ptr = [x; PTR_SIZE];
40+
let ptr: *const i32 = mem::transmute(ptr);
41+
let mut ptr = ptr.with_addr(0);
42+
ptr_bytes(&mut ptr)[0]
43+
}
44+
45+
unsafe {
46+
let ptr = &42;
47+
let mut ptr = ptr as *const i32;
48+
// Get a bytewise view of the pointer.
49+
let ptr_bytes = ptr_bytes(&mut ptr);
50+
51+
// The highest bytes must be 0 for this to work.
52+
let hi = if cfg!(target_endian = "little") { ptr_bytes.len() - 1 } else { 0 };
53+
assert_eq!(*ptr_bytes[hi].as_ptr().cast::<u8>(), 0);
54+
// Overwrite provenance on the last byte.
55+
ptr_bytes[hi] = mem::MaybeUninit::new(0);
56+
// Restore it from the another byte.
57+
ptr_bytes[hi] = zero_with_provenance(ptr_bytes[1]);
58+
59+
// Now ptr should be good again.
60+
assert_eq!(*ptr, 42);
61+
}
62+
}
63+
64+
fn bytewise_ptr_methods() {
65+
let mut ptr1 = &1;
66+
let mut ptr2 = &2;
67+
68+
// Swap them, bytewise.
69+
unsafe {
70+
ptr::swap_nonoverlapping(
71+
&mut ptr1 as *mut _ as *mut mem::MaybeUninit<u8>,
72+
&mut ptr2 as *mut _ as *mut mem::MaybeUninit<u8>,
73+
mem::size_of::<&i32>(),
74+
);
75+
}
76+
77+
// Make sure they still work.
78+
assert_eq!(*ptr1, 2);
79+
assert_eq!(*ptr2, 1);
80+
81+
// TODO: also test ptr::swap, ptr::copy, ptr::copy_nonoverlapping.
82+
}
83+
84+
fn bytewise_custom_memcpy() {
85+
unsafe fn memcpy<T>(to: *mut T, from: *const T) {
86+
let to = to.cast::<mem::MaybeUninit<u8>>();
87+
let from = from.cast::<mem::MaybeUninit<u8>>();
88+
for i in 0..mem::size_of::<T>() {
89+
let b = from.add(i).read();
90+
to.add(i).write(b);
91+
}
92+
}
93+
94+
let ptr1 = &1;
95+
let mut ptr2 = &2;
96+
97+
// Copy, bytewise.
98+
unsafe { memcpy(&mut ptr2, &ptr1) };
99+
100+
// Make sure they still work.
101+
assert_eq!(*ptr1, 1);
102+
assert_eq!(*ptr2, 1);
103+
}
104+
105+
fn bytewise_custom_memcpy_chunked() {
106+
unsafe fn memcpy<T>(to: *mut T, from: *const T) {
107+
assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0);
108+
let count = mem::size_of::<T>() / mem::size_of::<usize>();
109+
let to = to.cast::<mem::MaybeUninit<usize>>();
110+
let from = from.cast::<mem::MaybeUninit<usize>>();
111+
for i in 0..count {
112+
let b = from.add(i).read();
113+
to.add(i).write(b);
114+
}
115+
}
116+
117+
// Prepare an array where pointers are stored at... interesting... offsets.
118+
let mut data = [0usize; 2 * PTR_SIZE];
119+
let mut offsets = vec![];
120+
for i in 0..mem::size_of::<usize>() {
121+
// We have 2*PTR_SIZE room for each of these pointers.
122+
let base = i * 2 * PTR_SIZE;
123+
// This one is mis-aligned by `i`.
124+
let offset = base + i;
125+
offsets.push(offset);
126+
// Store it there.
127+
unsafe { data.as_mut_ptr().byte_add(offset).cast::<&i32>().write_unaligned(&42) };
128+
}
129+
130+
// Now memcpy that.
131+
let mut data2 = [0usize; 2 * PTR_SIZE];
132+
unsafe { memcpy(&mut data2, &data) };
133+
134+
// And check the result.
135+
for &offset in &offsets {
136+
let ptr = unsafe { data2.as_ptr().byte_add(offset).cast::<&i32>().read_unaligned() };
137+
assert_eq!(*ptr, 42);
138+
}
139+
}

0 commit comments

Comments
 (0)