@@ -5,7 +5,7 @@ use nix::sys::{ptrace, signal, wait};
55use nix:: unistd;
66
77use 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