Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit b7b2179

Browse files
authoredMar 6, 2025
Rollup merge of #137802 - RalfJung:miri-native-call-exposed, r=oli-obk
miri native-call support: all previously exposed provenance is accessible to the callee When Miri invokes a native C function, the memory C can access needs to be "prepared": to avoid false positives, we need to consider all that memory initialized, and we need to consider it to have arbitrary provenance. So far we did this for all pointers passed to C, but not for pointers that were exposed already before the native call. This PR adjusts the logic so that we now "prepare" all memory that has ever been exposed. This fixes cases such as: - cast a pointer to integer, send that integer to C, and access the memory there (`test_pass_ptr_as_int`) - send a pointer to some memory to C, which stores it somewhere; then in Rust store another pointer in that memory, and access that via C (`test_pass_ptr_via_previously_shared_mem`) r? `````@oli-obk`````
2 parents 59cd967 + 88f988c commit b7b2179

File tree

8 files changed

+125
-64
lines changed

8 files changed

+125
-64
lines changed
 

‎compiler/rustc_const_eval/src/interpret/memory.rs

+5-10
Original file line numberDiff line numberDiff line change
@@ -955,18 +955,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
955955

956956
/// Handle the effect an FFI call might have on the state of allocations.
957957
/// This overapproximates the modifications which external code might make to memory:
958-
/// We set all reachable allocations as initialized, mark all provenances as exposed
958+
/// We set all reachable allocations as initialized, mark all reachable provenances as exposed
959959
/// and overwrite them with `Provenance::WILDCARD`.
960-
pub fn prepare_for_native_call(
961-
&mut self,
962-
id: AllocId,
963-
initial_prov: M::Provenance,
964-
) -> InterpResult<'tcx> {
965-
// Expose provenance of the root allocation.
966-
M::expose_provenance(self, initial_prov)?;
967-
960+
///
961+
/// The allocations in `ids` are assumed to be already exposed.
962+
pub fn prepare_for_native_call(&mut self, ids: Vec<AllocId>) -> InterpResult<'tcx> {
968963
let mut done = FxHashSet::default();
969-
let mut todo = vec![id];
964+
let mut todo = ids;
970965
while let Some(id) = todo.pop() {
971966
if !done.insert(id) {
972967
// We already saw this allocation before, don't process it again.

‎src/tools/miri/src/alloc_addresses/mod.rs

+24-1
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,19 @@ trait EvalContextExtPriv<'tcx>: crate::MiriInterpCxExt<'tcx> {
285285

286286
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
287287
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
288-
fn expose_ptr(&self, alloc_id: AllocId, tag: BorTag) -> InterpResult<'tcx> {
288+
fn expose_provenance(&self, provenance: Provenance) -> InterpResult<'tcx> {
289289
let this = self.eval_context_ref();
290290
let mut global_state = this.machine.alloc_addresses.borrow_mut();
291+
292+
let (alloc_id, tag) = match provenance {
293+
Provenance::Concrete { alloc_id, tag } => (alloc_id, tag),
294+
Provenance::Wildcard => {
295+
// No need to do anything for wildcard pointers as
296+
// their provenances have already been previously exposed.
297+
return interp_ok(());
298+
}
299+
};
300+
291301
// In strict mode, we don't need this, so we can save some cycles by not tracking it.
292302
if global_state.provenance_mode == ProvenanceMode::Strict {
293303
return interp_ok(());
@@ -422,6 +432,19 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
422432
let rel_offset = this.truncate_to_target_usize(addr.bytes().wrapping_sub(base_addr));
423433
Some((alloc_id, Size::from_bytes(rel_offset)))
424434
}
435+
436+
/// Prepare all exposed memory for a native call.
437+
/// This overapproximates the modifications which external code might make to memory:
438+
/// We set all reachable allocations as initialized, mark all reachable provenances as exposed
439+
/// and overwrite them with `Provenance::WILDCARD`.
440+
fn prepare_exposed_for_native_call(&mut self) -> InterpResult<'tcx> {
441+
let this = self.eval_context_mut();
442+
// We need to make a deep copy of this list, but it's fine; it also serves as scratch space
443+
// for the search within `prepare_for_native_call`.
444+
let exposed: Vec<AllocId> =
445+
this.machine.alloc_addresses.get_mut().exposed.iter().copied().collect();
446+
this.prepare_for_native_call(exposed)
447+
}
425448
}
426449

427450
impl<'tcx> MiriMachine<'tcx> {

‎src/tools/miri/src/machine.rs

+2-8
Original file line numberDiff line numberDiff line change
@@ -1291,18 +1291,12 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> {
12911291
/// Called on `ptr as usize` casts.
12921292
/// (Actually computing the resulting `usize` doesn't need machine help,
12931293
/// that's just `Scalar::try_to_int`.)
1294+
#[inline(always)]
12941295
fn expose_provenance(
12951296
ecx: &InterpCx<'tcx, Self>,
12961297
provenance: Self::Provenance,
12971298
) -> InterpResult<'tcx> {
1298-
match provenance {
1299-
Provenance::Concrete { alloc_id, tag } => ecx.expose_ptr(alloc_id, tag),
1300-
Provenance::Wildcard => {
1301-
// No need to do anything for wildcard pointers as
1302-
// their provenances have already been previously exposed.
1303-
interp_ok(())
1304-
}
1305-
}
1299+
ecx.expose_provenance(provenance)
13061300
}
13071301

13081302
/// Convert a pointer with provenance into an allocation-offset pair and extra provenance info.

‎src/tools/miri/src/shims/native_lib.rs

+6-10
Original file line numberDiff line numberDiff line change
@@ -160,16 +160,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
160160
}
161161
let imm = this.read_immediate(arg)?;
162162
libffi_args.push(imm_to_carg(&imm, this)?);
163-
// If we are passing a pointer, prepare the memory it points to.
163+
// If we are passing a pointer, expose its provenance. Below, all exposed memory
164+
// (previously exposed and new exposed) will then be properly prepared.
164165
if matches!(arg.layout.ty.kind(), ty::RawPtr(..)) {
165166
let ptr = imm.to_scalar().to_pointer(this)?;
166167
let Some(prov) = ptr.provenance else {
167-
// Pointer without provenance may not access any memory.
168-
continue;
169-
};
170-
// We use `get_alloc_id` for its best-effort behaviour with Wildcard provenance.
171-
let Some(alloc_id) = prov.get_alloc_id() else {
172-
// Wildcard pointer, whatever it points to must be already exposed.
168+
// Pointer without provenance may not access any memory anyway, skip.
173169
continue;
174170
};
175171
// The first time this happens, print a warning.
@@ -178,12 +174,12 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
178174
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem);
179175
}
180176

181-
this.prepare_for_native_call(alloc_id, prov)?;
177+
this.expose_provenance(prov)?;
182178
}
183179
}
184180

185-
// FIXME: In the future, we should also call `prepare_for_native_call` on all previously
186-
// exposed allocations, since C may access any of them.
181+
// Prepare all exposed memory.
182+
this.prepare_exposed_for_native_call()?;
187183

188184
// Convert them to `libffi::high::Arg` type.
189185
let libffi_args = libffi_args

‎src/tools/miri/tests/native-lib/pass/ptr_write_access.rs

+36-4
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#![feature(box_as_ptr)]
77

88
use std::mem::MaybeUninit;
9-
use std::ptr::null;
9+
use std::ptr;
1010

1111
fn main() {
1212
test_increment_int();
@@ -20,6 +20,8 @@ fn main() {
2020
test_pass_dangling();
2121
test_swap_ptr_triple_dangling();
2222
test_return_ptr();
23+
test_pass_ptr_as_int();
24+
test_pass_ptr_via_previously_shared_mem();
2325
}
2426

2527
/// Test function that modifies an int.
@@ -112,7 +114,7 @@ fn test_swap_ptr() {
112114
}
113115

114116
let x = 61;
115-
let (mut ptr0, mut ptr1) = (&raw const x, null());
117+
let (mut ptr0, mut ptr1) = (&raw const x, ptr::null());
116118

117119
unsafe { swap_ptr(&mut ptr0, &mut ptr1) };
118120
assert_eq!(unsafe { *ptr1 }, x);
@@ -131,7 +133,7 @@ fn test_swap_ptr_tuple() {
131133
}
132134

133135
let x = 71;
134-
let mut tuple = Tuple { ptr0: &raw const x, ptr1: null() };
136+
let mut tuple = Tuple { ptr0: &raw const x, ptr1: ptr::null() };
135137

136138
unsafe { swap_ptr_tuple(&mut tuple) }
137139
assert_eq!(unsafe { *tuple.ptr1 }, x);
@@ -148,7 +150,7 @@ fn test_overwrite_dangling() {
148150
drop(b);
149151

150152
unsafe { overwrite_ptr(&mut ptr) };
151-
assert_eq!(ptr, null());
153+
assert_eq!(ptr, ptr::null());
152154
}
153155

154156
/// Test function that passes a dangling pointer.
@@ -200,3 +202,33 @@ fn test_return_ptr() {
200202
let ptr = unsafe { return_ptr(ptr) };
201203
assert_eq!(unsafe { *ptr }, x);
202204
}
205+
206+
/// Test casting a pointer to an integer and passing that to C.
207+
fn test_pass_ptr_as_int() {
208+
extern "C" {
209+
fn pass_ptr_as_int(ptr: usize, set_to_val: i32);
210+
}
211+
212+
let mut m: MaybeUninit<i32> = MaybeUninit::uninit();
213+
unsafe { pass_ptr_as_int(m.as_mut_ptr() as usize, 42) };
214+
assert_eq!(unsafe { m.assume_init() }, 42);
215+
}
216+
217+
fn test_pass_ptr_via_previously_shared_mem() {
218+
extern "C" {
219+
fn set_shared_mem(ptr: *mut *mut i32);
220+
fn init_ptr_stored_in_shared_mem(val: i32);
221+
}
222+
223+
let mut m: *mut i32 = ptr::null_mut();
224+
let ptr_to_m = &raw mut m;
225+
unsafe { set_shared_mem(&raw mut m) };
226+
227+
let mut m2: MaybeUninit<i32> = MaybeUninit::uninit();
228+
// Store a pointer to m2 somewhere that C code can access it.
229+
unsafe { ptr_to_m.write(m2.as_mut_ptr()) };
230+
// Have C code write there.
231+
unsafe { init_ptr_stored_in_shared_mem(42) };
232+
// Ensure this memory is now considered initialized.
233+
assert_eq!(unsafe { m2.assume_init() }, 42);
234+
}
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,34 @@
11
#include <stdio.h>
2+
#include <stdint.h>
23

34
// See comments in build_native_lib()
45
#define EXPORT __attribute__((visibility("default")))
56

67
/* Test: test_access_pointer */
78

8-
EXPORT void print_pointer(const int *ptr) {
9+
EXPORT void print_pointer(const int32_t *ptr) {
910
printf("printing pointer dereference from C: %d\n", *ptr);
1011
}
1112

1213
/* Test: test_access_simple */
1314

1415
typedef struct Simple {
15-
int field;
16+
int32_t field;
1617
} Simple;
1718

18-
EXPORT int access_simple(const Simple *s_ptr) {
19+
EXPORT int32_t access_simple(const Simple *s_ptr) {
1920
return s_ptr->field;
2021
}
2122

2223
/* Test: test_access_nested */
2324

2425
typedef struct Nested {
25-
int value;
26+
int32_t value;
2627
struct Nested *next;
2728
} Nested;
2829

2930
// Returns the innermost/last value of a Nested pointer chain.
30-
EXPORT int access_nested(const Nested *n_ptr) {
31+
EXPORT int32_t access_nested(const Nested *n_ptr) {
3132
// Edge case: `n_ptr == NULL` (i.e. first Nested is None).
3233
if (!n_ptr) { return 0; }
3334

@@ -41,10 +42,10 @@ EXPORT int access_nested(const Nested *n_ptr) {
4142
/* Test: test_access_static */
4243

4344
typedef struct Static {
44-
int value;
45+
int32_t value;
4546
struct Static *recurse;
4647
} Static;
4748

48-
EXPORT int access_static(const Static *s_ptr) {
49+
EXPORT int32_t access_static(const Static *s_ptr) {
4950
return s_ptr->recurse->recurse->value;
5051
}
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
#include <stddef.h>
2+
#include <stdint.h>
23

34
// See comments in build_native_lib()
45
#define EXPORT __attribute__((visibility("default")))
56

67
/* Test: test_increment_int */
78

8-
EXPORT void increment_int(int *ptr) {
9+
EXPORT void increment_int(int32_t *ptr) {
910
*ptr += 1;
1011
}
1112

1213
/* Test: test_init_int */
1314

14-
EXPORT void init_int(int *ptr, int val) {
15+
EXPORT void init_int(int32_t *ptr, int32_t val) {
1516
*ptr = val;
1617
}
1718

1819
/* Test: test_init_array */
1920

20-
EXPORT void init_array(int *array, size_t len, int val) {
21+
EXPORT void init_array(int32_t *array, size_t len, int32_t val) {
2122
for (size_t i = 0; i < len; i++) {
2223
array[i] = val;
2324
}
@@ -26,65 +27,83 @@ EXPORT void init_array(int *array, size_t len, int val) {
2627
/* Test: test_init_static_inner */
2728

2829
typedef struct SyncPtr {
29-
int *ptr;
30+
int32_t *ptr;
3031
} SyncPtr;
3132

32-
EXPORT void init_static_inner(const SyncPtr *s_ptr, int val) {
33+
EXPORT void init_static_inner(const SyncPtr *s_ptr, int32_t val) {
3334
*(s_ptr->ptr) = val;
3435
}
3536

3637
/* Tests: test_exposed, test_pass_dangling */
3738

38-
EXPORT void ignore_ptr(__attribute__((unused)) const int *ptr) {
39+
EXPORT void ignore_ptr(__attribute__((unused)) const int32_t *ptr) {
3940
return;
4041
}
4142

4243
/* Test: test_expose_int */
43-
EXPORT void expose_int(const int *int_ptr, const int **pptr) {
44+
EXPORT void expose_int(const int32_t *int_ptr, const int32_t **pptr) {
4445
*pptr = int_ptr;
4546
}
4647

4748
/* Test: test_swap_ptr */
4849

49-
EXPORT void swap_ptr(const int **pptr0, const int **pptr1) {
50-
const int *tmp = *pptr0;
50+
EXPORT void swap_ptr(const int32_t **pptr0, const int32_t **pptr1) {
51+
const int32_t *tmp = *pptr0;
5152
*pptr0 = *pptr1;
5253
*pptr1 = tmp;
5354
}
5455

5556
/* Test: test_swap_ptr_tuple */
5657

5758
typedef struct Tuple {
58-
int *ptr0;
59-
int *ptr1;
59+
int32_t *ptr0;
60+
int32_t *ptr1;
6061
} Tuple;
6162

6263
EXPORT void swap_ptr_tuple(Tuple *t_ptr) {
63-
int *tmp = t_ptr->ptr0;
64+
int32_t *tmp = t_ptr->ptr0;
6465
t_ptr->ptr0 = t_ptr->ptr1;
6566
t_ptr->ptr1 = tmp;
6667
}
6768

6869
/* Test: test_overwrite_dangling */
6970

70-
EXPORT void overwrite_ptr(const int **pptr) {
71+
EXPORT void overwrite_ptr(const int32_t **pptr) {
7172
*pptr = NULL;
7273
}
7374

7475
/* Test: test_swap_ptr_triple_dangling */
7576

7677
typedef struct Triple {
77-
int *ptr0;
78-
int *ptr1;
79-
int *ptr2;
78+
int32_t *ptr0;
79+
int32_t *ptr1;
80+
int32_t *ptr2;
8081
} Triple;
8182

8283
EXPORT void swap_ptr_triple_dangling(Triple *t_ptr) {
83-
int *tmp = t_ptr->ptr0;
84+
int32_t *tmp = t_ptr->ptr0;
8485
t_ptr->ptr0 = t_ptr->ptr2;
8586
t_ptr->ptr2 = tmp;
8687
}
8788

88-
EXPORT const int *return_ptr(const int *ptr) {
89+
EXPORT const int32_t *return_ptr(const int32_t *ptr) {
8990
return ptr;
9091
}
92+
93+
/* Test: test_pass_ptr_as_int */
94+
95+
EXPORT void pass_ptr_as_int(uintptr_t ptr, int32_t set_to_val) {
96+
*(int32_t*)ptr = set_to_val;
97+
}
98+
99+
/* Test: test_pass_ptr_via_previously_shared_mem */
100+
101+
int32_t** shared_place;
102+
103+
EXPORT void set_shared_mem(int32_t** ptr) {
104+
shared_place = ptr;
105+
}
106+
107+
EXPORT void init_ptr_stored_in_shared_mem(int32_t val) {
108+
**shared_place = val;
109+
}
There was a problem loading the remainder of the diff.

0 commit comments

Comments
 (0)
Failed to load comments.