Skip to content

Commit 0aec399

Browse files
authored
Merge pull request #4098 from RalfJung/native-call-warning
add warning explaining the limitations of the native code mode
2 parents 4ce3b85 + e28b1d7 commit 0aec399

File tree

5 files changed

+88
-10
lines changed

5 files changed

+88
-10
lines changed

Diff for: src/tools/miri/README.md

+13-10
Original file line numberDiff line numberDiff line change
@@ -375,16 +375,19 @@ to Miri failing to detect cases of undefined behavior in a program.
375375
* `-Zmiri-disable-weak-memory-emulation` disables the emulation of some C++11 weak
376376
memory effects.
377377
* `-Zmiri-native-lib=<path to a shared object file>` is an experimental flag for providing support
378-
for calling native functions from inside the interpreter via FFI. Functions not provided by that
379-
file are still executed via the usual Miri shims.
380-
**WARNING**: If an invalid/incorrect `.so` file is specified, this can cause Undefined Behavior in Miri itself!
381-
And of course, Miri cannot do any checks on the actions taken by the native code.
382-
Note that Miri has its own handling of file descriptors, so if you want to replace *some* functions
383-
working on file descriptors, you will have to replace *all* of them, or the two kinds of
384-
file descriptors will be mixed up.
385-
This is **work in progress**; currently, only integer arguments and return values are
386-
supported (and no, pointer/integer casts to work around this limitation will not work;
387-
they will fail horribly). It also only works on Unix hosts for now.
378+
for calling native functions from inside the interpreter via FFI. The flag is supported only on
379+
Unix systems. Functions not provided by that file are still executed via the usual Miri shims.
380+
**WARNING**: If an invalid/incorrect `.so` file is specified, this can cause Undefined Behavior in
381+
Miri itself! And of course, Miri cannot do any checks on the actions taken by the native code.
382+
Note that Miri has its own handling of file descriptors, so if you want to replace *some*
383+
functions working on file descriptors, you will have to replace *all* of them, or the two kinds of
384+
file descriptors will be mixed up. This is **work in progress**; currently, only integer and
385+
pointers arguments and return values are supported and memory allocated by the native code cannot
386+
be accessed from Rust (only the other way around). Native code must not spawn threads that keep
387+
running in the background after the call has returned to Rust and that access Rust-allocated
388+
memory. Finally, the flag is **unsound** in the sense that Miri stops tracking details such as
389+
initialization and provenance on memory shared with native code, so it is easily possible to write
390+
code that has UB which is missed by Miri.
388391
* `-Zmiri-measureme=<name>` enables `measureme` profiling for the interpreted program.
389392
This can be used to find which parts of your program are executing slowly under Miri.
390393
The profile is written out to a file inside a directory called `<name>`, and can be processed

Diff for: src/tools/miri/src/diagnostics.rs

+26
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ pub enum NonHaltingDiagnostic {
126126
Int2Ptr {
127127
details: bool,
128128
},
129+
NativeCallSharedMem,
129130
WeakMemoryOutdatedLoad {
130131
ptr: Pointer,
131132
},
@@ -602,6 +603,8 @@ impl<'tcx> MiriMachine<'tcx> {
602603
RejectedIsolatedOp(_) =>
603604
("operation rejected by isolation".to_string(), DiagLevel::Warning),
604605
Int2Ptr { .. } => ("integer-to-pointer cast".to_string(), DiagLevel::Warning),
606+
NativeCallSharedMem =>
607+
("sharing memory with a native function".to_string(), DiagLevel::Warning),
605608
ExternTypeReborrow =>
606609
("reborrow of reference to `extern type`".to_string(), DiagLevel::Warning),
607610
CreatedPointerTag(..)
@@ -637,6 +640,7 @@ impl<'tcx> MiriMachine<'tcx> {
637640
ProgressReport { .. } =>
638641
format!("progress report: current operation being executed is here"),
639642
Int2Ptr { .. } => format!("integer-to-pointer cast"),
643+
NativeCallSharedMem => format!("sharing memory with a native function called via FFI"),
640644
WeakMemoryOutdatedLoad { ptr } =>
641645
format!("weak memory emulation: outdated value returned from load at {ptr}"),
642646
ExternTypeReborrow =>
@@ -679,7 +683,29 @@ impl<'tcx> MiriMachine<'tcx> {
679683
}
680684
v
681685
}
686+
NativeCallSharedMem => {
687+
vec![
688+
note!(
689+
"when memory is shared with a native function call, Miri stops tracking initialization and provenance for that memory"
690+
),
691+
note!(
692+
"in particular, Miri assumes that the native call initializes all memory it has access to"
693+
),
694+
note!(
695+
"Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory"
696+
),
697+
note!(
698+
"what this means is that Miri will easily miss Undefined Behavior related to incorrect usage of this shared memory, so you should not take a clean Miri run as a signal that your FFI code is UB-free"
699+
),
700+
]
701+
}
682702
ExternTypeReborrow => {
703+
assert!(self.borrow_tracker.as_ref().is_some_and(|b| {
704+
matches!(
705+
b.borrow().borrow_tracker_method(),
706+
BorrowTrackerMethod::StackedBorrows
707+
)
708+
}));
683709
vec![
684710
note!(
685711
"`extern type` are not compatible with the Stacked Borrows aliasing model implemented by Miri; Miri may miss bugs in this code"

Diff for: src/tools/miri/src/shims/native_lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! Implements calling functions from a native library.
2+
use std::cell::RefCell;
23
use std::ops::Deref;
34

45
use libffi::high::call as ffi;
@@ -172,6 +173,18 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
172173
// Wildcard pointer, whatever it points to must be already exposed.
173174
continue;
174175
};
176+
// The first time this happens at a particular location, print a warning.
177+
thread_local! {
178+
static HAVE_WARNED: RefCell<bool> = const { RefCell::new(false) };
179+
}
180+
HAVE_WARNED.with_borrow_mut(|have_warned| {
181+
if !*have_warned {
182+
// Newly inserted, so first time we see this span.
183+
this.emit_diagnostic(NonHaltingDiagnostic::NativeCallSharedMem);
184+
*have_warned = true;
185+
}
186+
});
187+
175188
this.prepare_for_native_call(alloc_id, prov)?;
176189
}
177190
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
warning: sharing memory with a native function
2+
--> tests/native-lib/pass/ptr_read_access.rs:LL:CC
3+
|
4+
LL | unsafe { print_pointer(&x) };
5+
| ^^^^^^^^^^^^^^^^^ sharing memory with a native function called via FFI
6+
|
7+
= help: when memory is shared with a native function call, Miri stops tracking initialization and provenance for that memory
8+
= help: in particular, Miri assumes that the native call initializes all memory it has access to
9+
= help: Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory
10+
= help: what this means is that Miri will easily miss Undefined Behavior related to incorrect usage of this shared memory, so you should not take a clean Miri run as a signal that your FFI code is UB-free
11+
= note: BACKTRACE:
12+
= note: inside `test_access_pointer` at tests/native-lib/pass/ptr_read_access.rs:LL:CC
13+
note: inside `main`
14+
--> tests/native-lib/pass/ptr_read_access.rs:LL:CC
15+
|
16+
LL | test_access_pointer();
17+
| ^^^^^^^^^^^^^^^^^^^^^
18+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
warning: sharing memory with a native function
2+
--> tests/native-lib/pass/ptr_write_access.rs:LL:CC
3+
|
4+
LL | unsafe { increment_int(&mut x) };
5+
| ^^^^^^^^^^^^^^^^^^^^^ sharing memory with a native function called via FFI
6+
|
7+
= help: when memory is shared with a native function call, Miri stops tracking initialization and provenance for that memory
8+
= help: in particular, Miri assumes that the native call initializes all memory it has access to
9+
= help: Miri also assumes that any part of this memory may be a pointer that is permitted to point to arbitrary exposed memory
10+
= help: what this means is that Miri will easily miss Undefined Behavior related to incorrect usage of this shared memory, so you should not take a clean Miri run as a signal that your FFI code is UB-free
11+
= note: BACKTRACE:
12+
= note: inside `test_increment_int` at tests/native-lib/pass/ptr_write_access.rs:LL:CC
13+
note: inside `main`
14+
--> tests/native-lib/pass/ptr_write_access.rs:LL:CC
15+
|
16+
LL | test_increment_int();
17+
| ^^^^^^^^^^^^^^^^^^^^
18+

0 commit comments

Comments
 (0)