1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
|
//@compile-flags: -Zmiri-permissive-provenance
use std::mem;
// This strips provenance
fn transmute_ptr_to_int<T>(x: *const T) -> usize {
unsafe { std::mem::transmute(x) }
}
fn cast() {
// Some casting-to-int with arithmetic.
let x = &42 as *const i32 as usize;
let y = x * 2;
assert_eq!(y, x + x);
let z = y as u8 as usize;
assert_eq!(z, y % 256);
}
/// Test usize->ptr cast for dangling and OOB address.
/// That is safe, and thus has to work.
fn cast_dangling() {
let b = Box::new(0);
let x = &*b as *const i32 as usize;
drop(b);
let _val = x as *const i32;
let b = Box::new(0);
let mut x = &*b as *const i32 as usize;
x += 0x100;
let _val = x as *const i32;
}
fn format() {
// Pointer string formatting! We can't check the output as it changes when libstd changes,
// but we can make sure Miri does not error.
let _ = format!("{:?}", &mut 13 as *mut _);
}
fn transmute() {
// Check that intptrcast is triggered for explicit casts and that it is consistent with
// transmuting.
let a: *const i32 = &42;
let b = transmute_ptr_to_int(a) as u8;
let c = a as u8;
assert_eq!(b, c);
}
fn ptr_bitops1() {
let bytes = [0i8, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let one = bytes.as_ptr().wrapping_offset(1);
let three = bytes.as_ptr().wrapping_offset(3);
let res = (one as usize) | (three as usize);
let _ = format!("{}", res);
}
fn ptr_bitops2() {
let val = 13usize;
let addr = &val as *const _ as usize;
let _val = addr & 13;
}
fn ptr_eq_dangling() {
let b = Box::new(0);
let x = &*b as *const i32; // soon-to-be dangling
drop(b);
let b = Box::new(0);
let y = &*b as *const i32; // different allocation
// They *could* be equal if memory is reused...
assert!(x != y || x == y);
}
fn ptr_eq_out_of_bounds() {
let b = Box::new(0);
let x = (&*b as *const i32).wrapping_sub(0x800); // out-of-bounds
let b = Box::new(0);
let y = &*b as *const i32; // different allocation
// They *could* be equal (with the right base addresses), but probably are not.
assert!(x != y);
}
fn ptr_eq_out_of_bounds_null() {
let b = Box::new(0);
let x = (&*b as *const i32).wrapping_sub(0x800); // out-of-bounds
// This *could* be NULL (with the right base address), but probably is not.
assert!(x != std::ptr::null());
}
fn ptr_eq_integer() {
let b = Box::new(0);
let x = &*b as *const i32;
// These *could* be equal (with the right base address), but probably are not.
assert!(x != 64 as *const i32);
}
fn zst_deref_of_dangling() {
let b = Box::new(0);
let addr = &*b as *const _ as usize;
drop(b);
// Now if we cast `addr` to a ptr it might pick up the dangling provenance.
// But if we only do a ZST deref there is no UB here!
let zst = addr as *const ();
let _val = unsafe { *zst };
}
fn functions() {
// Roundtrip a few functions through integers. Do this multiple times to make sure this does not
// work by chance. If we did not give unique addresses to ZST allocations -- which fn
// allocations are -- then we might be unable to cast back, or we might call the wrong function!
// Every function gets at most one address so doing a loop would not help...
fn fn0() -> i32 {
0
}
fn fn1() -> i32 {
1
}
fn fn2() -> i32 {
2
}
fn fn3() -> i32 {
3
}
fn fn4() -> i32 {
4
}
fn fn5() -> i32 {
5
}
fn fn6() -> i32 {
6
}
fn fn7() -> i32 {
7
}
let fns = [
fn0 as fn() -> i32 as *const () as usize,
fn1 as fn() -> i32 as *const () as usize,
fn2 as fn() -> i32 as *const () as usize,
fn3 as fn() -> i32 as *const () as usize,
fn4 as fn() -> i32 as *const () as usize,
fn5 as fn() -> i32 as *const () as usize,
fn6 as fn() -> i32 as *const () as usize,
fn7 as fn() -> i32 as *const () as usize,
];
for (idx, &addr) in fns.iter().enumerate() {
let fun: fn() -> i32 = unsafe { mem::transmute(addr as *const ()) };
assert_eq!(fun(), idx as i32);
}
}
/// Example that should be UB but due to wildcard pointers being too permissive
/// we don't notice.
fn should_be_ub() {
let alloc1 = 1u8;
let alloc2 = 2u8;
// Expose both allocations
let addr1: usize = &alloc1 as *const u8 as usize;
let addr2: usize = &alloc2 as *const u8 as usize;
// Cast addr1 back to a pointer. In Miri, this gives it Wildcard provenance.
let wildcard = addr1 as *const u8;
unsafe {
// Read through the wildcard
assert_eq!(*wildcard, 1);
// Offset the pointer to another allocation.
// Note that we are doing this arithmetic that does not require we stay within bounds of the allocation.
let wildcard = wildcard.wrapping_offset(addr2 as isize - addr1 as isize);
// This should report UB:
assert_eq!(*wildcard, 2);
// ... but it doesn't. A pointer's provenance specifies a single allocation that it is allowed to read from.
// And wrapping_offset only modifies the address, not the provenance.
// So which allocation is wildcard allowed to access? It cannot be both.
}
}
fn main() {
cast();
cast_dangling();
format();
transmute();
ptr_bitops1();
ptr_bitops2();
ptr_eq_dangling();
ptr_eq_out_of_bounds();
ptr_eq_out_of_bounds_null();
ptr_eq_integer();
zst_deref_of_dangling();
functions();
should_be_ub();
}
|