Skip to content

Commit cde1012

Browse files
committed
de-intend some code, and extend comments
1 parent 62bb621 commit cde1012

File tree

3 files changed

+112
-111
lines changed

3 files changed

+112
-111
lines changed

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

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use nix::unistd;
77

88
use super::messages::{Confirmation, MemEvents, TraceRequest};
99
use super::parent::{ChildListener, sv_loop};
10-
use super::{FAKE_STACK_SIZE, StartFfiInfo};
10+
use super::{CALLBACK_STACK_SIZE, StartFfiInfo};
1111
use crate::alloc::isolated_alloc::IsolatedAlloc;
1212

1313
static SUPERVISOR: std::sync::Mutex<Option<Supervisor>> = std::sync::Mutex::new(None);
@@ -46,7 +46,7 @@ impl Supervisor {
4646
/// after the desired call has concluded.
4747
pub unsafe fn start_ffi(
4848
alloc: &Rc<RefCell<IsolatedAlloc>>,
49-
) -> (std::sync::MutexGuard<'static, Option<Supervisor>>, Option<*mut [u8; FAKE_STACK_SIZE]>)
49+
) -> (std::sync::MutexGuard<'static, Option<Supervisor>>, Option<*mut [u8; CALLBACK_STACK_SIZE]>)
5050
{
5151
let mut sv_guard = SUPERVISOR.lock().unwrap();
5252
// If the supervisor is not initialised for whatever reason, fast-fail.
@@ -58,10 +58,10 @@ impl Supervisor {
5858
};
5959

6060
// Get pointers to all the pages the supervisor must allow accesses in
61-
// and prepare the fake stack.
61+
// and prepare the callback stack.
6262
let page_ptrs = alloc.borrow().pages();
63-
let raw_stack_ptr: *mut [u8; FAKE_STACK_SIZE] =
64-
Box::leak(Box::new([0u8; FAKE_STACK_SIZE])).as_mut_ptr().cast();
63+
let raw_stack_ptr: *mut [u8; CALLBACK_STACK_SIZE] =
64+
Box::leak(Box::new([0u8; CALLBACK_STACK_SIZE])).as_mut_ptr().cast();
6565
let stack_ptr = raw_stack_ptr.expose_provenance();
6666
let start_info = StartFfiInfo { page_ptrs, stack_ptr };
6767

@@ -101,7 +101,7 @@ impl Supervisor {
101101
pub unsafe fn end_ffi(
102102
alloc: &Rc<RefCell<IsolatedAlloc>>,
103103
mut sv_guard: std::sync::MutexGuard<'static, Option<Supervisor>>,
104-
raw_stack_ptr: Option<*mut [u8; FAKE_STACK_SIZE]>,
104+
raw_stack_ptr: Option<*mut [u8; CALLBACK_STACK_SIZE]>,
105105
) -> Option<MemEvents> {
106106
// We can't use IPC channels here to signal that FFI mode has ended,
107107
// since they might allocate memory which could get us stuck in a SIGTRAP

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use std::ops::Range;
66

77
pub use self::child::{Supervisor, init_sv, register_retcode_sv};
88

9-
/// The size used for the array into which we can move the stack pointer.
10-
const FAKE_STACK_SIZE: usize = 1024;
9+
/// The size of the temporary stack we use for callbacks that the server executes in the client.
10+
const CALLBACK_STACK_SIZE: usize = 1024;
1111

1212
/// Information needed to begin tracing.
1313
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
@@ -16,7 +16,7 @@ struct StartFfiInfo {
1616
/// with `IsolatedAlloc::pages` and prepared with `IsolatedAlloc::prepare_ffi`.
1717
page_ptrs: Vec<usize>,
1818
/// The address of an allocation that can serve as a temporary stack.
19-
/// This should be a leaked `Box<[u8; FAKE_STACK_SIZE]>` cast to an int.
19+
/// This should be a leaked `Box<[u8; CALLBACK_STACK_SIZE]>` cast to an int.
2020
stack_ptr: usize,
2121
}
2222

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

Lines changed: 103 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use nix::sys::{ptrace, signal, wait};
55
use nix::unistd;
66

77
use super::messages::{Confirmation, MemEvents, TraceRequest};
8-
use super::{AccessEvent, FAKE_STACK_SIZE, StartFfiInfo};
8+
use super::{AccessEvent, CALLBACK_STACK_SIZE, StartFfiInfo};
99

1010
/// The flags to use when calling `waitid()`.
1111
/// Since bitwise or on the nix version of these flags is implemented as a trait,
@@ -263,7 +263,7 @@ pub fn sv_loop(
263263
ExecEvent::Start(ch_info) => {
264264
// All the pages that the child process is "allowed to" access.
265265
ch_pages = ch_info.page_ptrs;
266-
// And the fake stack it allocated for us to use later.
266+
// And the temporary callback stack it allocated for us to use later.
267267
ch_stack = Some(ch_info.stack_ptr);
268268

269269
// We received the signal and are no longer in the main listener loop,
@@ -529,112 +529,113 @@ fn handle_segfault(
529529
let addr = unsafe { siginfo.si_addr().addr() };
530530
let page_addr = addr.strict_sub(addr.strict_rem(page_size));
531531

532-
if ch_pages.iter().any(|pg| (*pg..pg.strict_add(page_size)).contains(&addr)) {
533-
// Overall structure:
534-
// - Get the address that caused the segfault
535-
// - Unprotect the memory: we force the child to execute `mempr_off`, passing
536-
// parameters via global atomic variables.
537-
// - Step 1 instruction
538-
// - Parse executed code to estimate size & type of access
539-
// - Reprotect the memory by executing `mempr_on` in the child.
540-
// - Continue
541-
542-
// Ensure the stack is properly zeroed out!
543-
for a in (ch_stack..ch_stack.strict_add(FAKE_STACK_SIZE)).step_by(ARCH_WORD_SIZE) {
544-
ptrace::write(pid, std::ptr::with_exposed_provenance_mut(a), 0).unwrap();
545-
}
546-
547-
// Guard against both architectures with upwards and downwards-growing stacks.
548-
let stack_ptr = ch_stack.strict_add(FAKE_STACK_SIZE / 2);
549-
let regs_bak = ptrace::getregs(pid).unwrap();
550-
let mut new_regs = regs_bak;
551-
let ip_prestep = regs_bak.ip();
552-
553-
// Move the instr ptr into the deprotection code.
554-
#[expect(clippy::as_conversions)]
555-
new_regs.set_ip(mempr_off as usize);
556-
// Don't mess up the stack by accident!
557-
new_regs.set_sp(stack_ptr);
558-
559-
// Modify the PAGE_ADDR global on the child process to point to the page
560-
// that we want unprotected.
561-
ptrace::write(
562-
pid,
563-
(&raw const PAGE_ADDR).cast_mut().cast(),
564-
libc::c_long::try_from(page_addr).unwrap(),
565-
)
566-
.unwrap();
567-
568-
// Check if we also own the next page, and if so unprotect it in case
569-
// the access spans the page boundary.
570-
let flag = if ch_pages.contains(&page_addr.strict_add(page_size)) { 2 } else { 1 };
571-
ptrace::write(pid, (&raw const PAGE_COUNT).cast_mut().cast(), flag).unwrap();
572-
573-
ptrace::setregs(pid, new_regs).unwrap();
574-
575-
// Our mempr_* functions end with a raise(SIGSTOP).
576-
wait_for_signal(Some(pid), signal::SIGSTOP, true)?;
577-
578-
// Step 1 instruction.
579-
ptrace::setregs(pid, regs_bak).unwrap();
580-
ptrace::step(pid, None).unwrap();
581-
// Don't use wait_for_signal here since 1 instruction doesn't give room
582-
// for any uncertainty + we don't want it `cont()`ing randomly by accident
583-
// Also, don't let it continue with unprotected memory if something errors!
584-
let _ = wait::waitid(wait::Id::Pid(pid), WAIT_FLAGS).map_err(|_| ExecEnd(None))?;
585-
586-
// Zero out again to be safe
587-
for a in (ch_stack..ch_stack.strict_add(FAKE_STACK_SIZE)).step_by(ARCH_WORD_SIZE) {
588-
ptrace::write(pid, std::ptr::with_exposed_provenance_mut(a), 0).unwrap();
589-
}
590-
591-
// Save registers and grab the bytes that were executed. This would
592-
// be really nasty if it was a jump or similar but those thankfully
593-
// won't do memory accesses and so can't trigger this!
594-
let regs_bak = ptrace::getregs(pid).unwrap();
595-
new_regs = regs_bak;
596-
let ip_poststep = regs_bak.ip();
597-
// We need to do reads/writes in word-sized chunks.
598-
let diff = (ip_poststep.strict_sub(ip_prestep)).div_ceil(ARCH_WORD_SIZE);
599-
let instr = (ip_prestep..ip_prestep.strict_add(diff)).fold(vec![], |mut ret, ip| {
600-
// This only needs to be a valid pointer in the child process, not ours.
601-
ret.append(
602-
&mut ptrace::read(pid, std::ptr::without_provenance_mut(ip))
603-
.unwrap()
604-
.to_ne_bytes()
605-
.to_vec(),
606-
);
607-
ret
608-
});
609-
610-
// Now figure out the size + type of access and log it down.
611-
// This will mark down e.g. the same area being read multiple times,
612-
// since it's more efficient to compress the accesses at the end.
613-
if capstone_disassemble(&instr, addr, cs, acc_events).is_err() {
614-
// Read goes first because we need to be pessimistic.
615-
acc_events.push(AccessEvent::Read(addr..addr.strict_add(ARCH_MAX_ACCESS_SIZE)));
616-
acc_events.push(AccessEvent::Write(addr..addr.strict_add(ARCH_MAX_ACCESS_SIZE)));
617-
}
618-
619-
// Reprotect everything and continue.
620-
#[expect(clippy::as_conversions)]
621-
new_regs.set_ip(mempr_on as usize);
622-
new_regs.set_sp(stack_ptr);
623-
ptrace::setregs(pid, new_regs).unwrap();
624-
wait_for_signal(Some(pid), signal::SIGSTOP, true)?;
625-
626-
ptrace::setregs(pid, regs_bak).unwrap();
627-
ptrace::syscall(pid, None).unwrap();
628-
Ok(())
629-
} else {
630-
// This was a real segfault, so print some debug info and quit.
532+
if !ch_pages.iter().any(|pg| (*pg..pg.strict_add(page_size)).contains(&addr)) {
533+
// This was a real segfault (not one of the Miri memory pages), so print some debug info and
534+
// quit.
631535
let regs = ptrace::getregs(pid).unwrap();
632536
eprintln!("Segfault occurred during FFI at {addr:#018x}");
633537
eprintln!("Expected access on pages: {ch_pages:#018x?}");
634538
eprintln!("Register dump: {regs:#x?}");
635539
ptrace::kill(pid).unwrap();
636-
Err(ExecEnd(None))
540+
return Err(ExecEnd(None));
637541
}
542+
543+
// Overall structure:
544+
// - Get the address that caused the segfault
545+
// - Unprotect the memory: we force the child to execute `mempr_off`, passing parameters via
546+
// global atomic variables. This is what we use the temporary callback stack for.
547+
// - Step 1 instruction
548+
// - Parse executed code to estimate size & type of access
549+
// - Reprotect the memory by executing `mempr_on` in the child.
550+
// - Continue
551+
552+
// Ensure the stack is properly zeroed out!
553+
for a in (ch_stack..ch_stack.strict_add(CALLBACK_STACK_SIZE)).step_by(ARCH_WORD_SIZE) {
554+
ptrace::write(pid, std::ptr::with_exposed_provenance_mut(a), 0).unwrap();
555+
}
556+
557+
// Guard against both architectures with upwards and downwards-growing stacks.
558+
let stack_ptr = ch_stack.strict_add(CALLBACK_STACK_SIZE / 2);
559+
let regs_bak = ptrace::getregs(pid).unwrap();
560+
let mut new_regs = regs_bak;
561+
let ip_prestep = regs_bak.ip();
562+
563+
// Move the instr ptr into the deprotection code.
564+
#[expect(clippy::as_conversions)]
565+
new_regs.set_ip(mempr_off as usize);
566+
// Don't mess up the stack by accident!
567+
new_regs.set_sp(stack_ptr);
568+
569+
// Modify the PAGE_ADDR global on the child process to point to the page
570+
// that we want unprotected.
571+
ptrace::write(
572+
pid,
573+
(&raw const PAGE_ADDR).cast_mut().cast(),
574+
libc::c_long::try_from(page_addr).unwrap(),
575+
)
576+
.unwrap();
577+
578+
// Check if we also own the next page, and if so unprotect it in case
579+
// the access spans the page boundary.
580+
let flag = if ch_pages.contains(&page_addr.strict_add(page_size)) { 2 } else { 1 };
581+
ptrace::write(pid, (&raw const PAGE_COUNT).cast_mut().cast(), flag).unwrap();
582+
583+
ptrace::setregs(pid, new_regs).unwrap();
584+
585+
// Our mempr_* functions end with a raise(SIGSTOP).
586+
wait_for_signal(Some(pid), signal::SIGSTOP, true)?;
587+
588+
// Step 1 instruction.
589+
ptrace::setregs(pid, regs_bak).unwrap();
590+
ptrace::step(pid, None).unwrap();
591+
// Don't use wait_for_signal here since 1 instruction doesn't give room
592+
// for any uncertainty + we don't want it `cont()`ing randomly by accident
593+
// Also, don't let it continue with unprotected memory if something errors!
594+
let _ = wait::waitid(wait::Id::Pid(pid), WAIT_FLAGS).map_err(|_| ExecEnd(None))?;
595+
596+
// Zero out again to be safe
597+
for a in (ch_stack..ch_stack.strict_add(CALLBACK_STACK_SIZE)).step_by(ARCH_WORD_SIZE) {
598+
ptrace::write(pid, std::ptr::with_exposed_provenance_mut(a), 0).unwrap();
599+
}
600+
601+
// Save registers and grab the bytes that were executed. This would
602+
// be really nasty if it was a jump or similar but those thankfully
603+
// won't do memory accesses and so can't trigger this!
604+
let regs_bak = ptrace::getregs(pid).unwrap();
605+
new_regs = regs_bak;
606+
let ip_poststep = regs_bak.ip();
607+
// We need to do reads/writes in word-sized chunks.
608+
let diff = (ip_poststep.strict_sub(ip_prestep)).div_ceil(ARCH_WORD_SIZE);
609+
let instr = (ip_prestep..ip_prestep.strict_add(diff)).fold(vec![], |mut ret, ip| {
610+
// This only needs to be a valid pointer in the child process, not ours.
611+
ret.append(
612+
&mut ptrace::read(pid, std::ptr::without_provenance_mut(ip))
613+
.unwrap()
614+
.to_ne_bytes()
615+
.to_vec(),
616+
);
617+
ret
618+
});
619+
620+
// Now figure out the size + type of access and log it down.
621+
// This will mark down e.g. the same area being read multiple times,
622+
// since it's more efficient to compress the accesses at the end.
623+
if capstone_disassemble(&instr, addr, cs, acc_events).is_err() {
624+
// Read goes first because we need to be pessimistic.
625+
acc_events.push(AccessEvent::Read(addr..addr.strict_add(ARCH_MAX_ACCESS_SIZE)));
626+
acc_events.push(AccessEvent::Write(addr..addr.strict_add(ARCH_MAX_ACCESS_SIZE)));
627+
}
628+
629+
// Reprotect everything and continue.
630+
#[expect(clippy::as_conversions)]
631+
new_regs.set_ip(mempr_on as usize);
632+
new_regs.set_sp(stack_ptr);
633+
ptrace::setregs(pid, new_regs).unwrap();
634+
wait_for_signal(Some(pid), signal::SIGSTOP, true)?;
635+
636+
ptrace::setregs(pid, regs_bak).unwrap();
637+
ptrace::syscall(pid, None).unwrap();
638+
Ok(())
638639
}
639640

640641
// We only get dropped into these functions via offsetting the instr pointer

0 commit comments

Comments
 (0)