Skip to content

Commit 1feabac

Browse files
authored
Merge pull request rust-lang#4549 from RalfJung/mprotect
native-lib mode: avoid unsoundness due to mrpotect
2 parents c8d20ce + 0308a15 commit 1feabac

File tree

2 files changed

+21
-39
lines changed

2 files changed

+21
-39
lines changed

src/tools/miri/src/shims/native_lib/trace/child.rs

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,6 @@ impl Supervisor {
9090
// Unwinding might be messed up due to partly protected memory, so let's abort if something
9191
// breaks inside here.
9292
let res = std::panic::abort_unwind(|| {
93-
// SAFETY: We do not access machine memory past this point until the
94-
// supervisor is ready to allow it.
95-
// FIXME: this is sketchy, as technically the memory is still in the Rust Abstract Machine,
96-
// and the compiler would be allowed to reorder accesses below this block...
97-
unsafe {
98-
Self::protect_pages(alloc.pages(), mman::ProtFlags::PROT_NONE).unwrap();
99-
}
100-
10193
// Send over the info.
10294
// NB: if we do not wait to receive a blank confirmation response, it is
10395
// possible that the supervisor is alerted of the SIGSTOP *before* it has
@@ -110,16 +102,14 @@ impl Supervisor {
110102
// count.
111103
signal::raise(signal::SIGSTOP).unwrap();
112104

113-
let res = f();
105+
// SAFETY: We have coordinated with the supervisor to ensure that this memory will keep
106+
// working as normal, just with extra tracing. So even if the compiler moves memory
107+
// accesses down to after the `mprotect`, they won't actually segfault.
108+
unsafe {
109+
Self::protect_pages(alloc.pages(), mman::ProtFlags::PROT_NONE).unwrap();
110+
}
114111

115-
// We can't use IPC channels here to signal that FFI mode has ended,
116-
// since they might allocate memory which could get us stuck in a SIGTRAP
117-
// with no easy way out! While this could be worked around, it is much
118-
// simpler and more robust to simply use the signals which are left for
119-
// arbitrary usage. Since this will block until we are continued by the
120-
// supervisor, we can assume past this point that everything is back to
121-
// normal.
122-
signal::raise(signal::SIGUSR1).unwrap();
112+
let res = f();
123113

124114
// SAFETY: We set memory back to normal, so this is safe.
125115
unsafe {
@@ -130,6 +120,12 @@ impl Supervisor {
130120
.unwrap();
131121
}
132122

123+
// Signal the supervisor that we are done. Will block until the supervisor continues us.
124+
// This will also shut down the segfault handler, so it's important that all memory is
125+
// reset back to normal above. There must not be a window in time where accessing the
126+
// pages we protected above actually causes the program to abort.
127+
signal::raise(signal::SIGUSR1).unwrap();
128+
133129
res
134130
});
135131

src/tools/miri/src/shims/native_lib/trace/parent.rs

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,10 @@ impl Iterator for ChildListener {
132132
return Some(ExecEvent::Syscall(pid));
133133
},
134134
// Child with the given pid was stopped by the given signal.
135-
// It's somewhat dubious when this is returned instead of
136-
// WaitStatus::Stopped, but for our purposes they are the
137-
// same thing.
138-
wait::WaitStatus::PtraceEvent(pid, signal, _) =>
135+
// It's somewhat unclear when which of these two is returned;
136+
// we just treat them the same.
137+
wait::WaitStatus::Stopped(pid, signal)
138+
| wait::WaitStatus::PtraceEvent(pid, signal, _) =>
139139
if self.attached {
140140
// This is our end-of-FFI signal!
141141
if signal == signal::SIGUSR1 {
@@ -148,19 +148,6 @@ impl Iterator for ChildListener {
148148
// Just pass along the signal.
149149
ptrace::cont(pid, signal).unwrap();
150150
},
151-
// Child was stopped at the given signal. Same logic as for
152-
// WaitStatus::PtraceEvent.
153-
wait::WaitStatus::Stopped(pid, signal) =>
154-
if self.attached {
155-
if signal == signal::SIGUSR1 {
156-
self.attached = false;
157-
return Some(ExecEvent::End);
158-
} else {
159-
return Some(ExecEvent::Status(pid, signal));
160-
}
161-
} else {
162-
ptrace::cont(pid, signal).unwrap();
163-
},
164151
_ => (),
165152
},
166153
// This case should only trigger when all children died.
@@ -250,7 +237,7 @@ pub fn sv_loop(
250237
// We can't trust simply calling `Pid::this()` in the child process to give the right
251238
// PID for us, so we get it this way.
252239
curr_pid = wait_for_signal(None, signal::SIGSTOP, InitialCont::No).unwrap();
253-
240+
// Continue until next syscall.
254241
ptrace::syscall(curr_pid, None).unwrap();
255242
}
256243
// Child wants to end tracing.
@@ -289,8 +276,7 @@ pub fn sv_loop(
289276
}
290277
}
291278
},
292-
// Child entered a syscall; we wait for exits inside of this, so it
293-
// should never trigger on return from a syscall we care about.
279+
// Child entered or exited a syscall. For now we ignore this and just continue.
294280
ExecEvent::Syscall(pid) => {
295281
ptrace::syscall(pid, None).unwrap();
296282
}
@@ -344,8 +330,8 @@ fn wait_for_signal(
344330
return Err(ExecEnd(Some(code)));
345331
}
346332
wait::WaitStatus::Signaled(_, _, _) => return Err(ExecEnd(None)),
347-
wait::WaitStatus::Stopped(pid, signal) => (signal, pid),
348-
wait::WaitStatus::PtraceEvent(pid, signal, _) => (signal, pid),
333+
wait::WaitStatus::Stopped(pid, signal)
334+
| wait::WaitStatus::PtraceEvent(pid, signal, _) => (signal, pid),
349335
// This covers PtraceSyscall and variants that are impossible with
350336
// the flags set (e.g. WaitStatus::StillAlive).
351337
_ => {

0 commit comments

Comments
 (0)