@@ -5,7 +5,7 @@ use nix::sys::{ptrace, signal, wait};
5
5
use nix:: unistd;
6
6
7
7
use super :: messages:: { Confirmation , MemEvents , TraceRequest } ;
8
- use super :: { AccessEvent , FAKE_STACK_SIZE , StartFfiInfo } ;
8
+ use super :: { AccessEvent , CALLBACK_STACK_SIZE , StartFfiInfo } ;
9
9
10
10
/// The flags to use when calling `waitid()`.
11
11
/// Since bitwise or on the nix version of these flags is implemented as a trait,
@@ -263,7 +263,7 @@ pub fn sv_loop(
263
263
ExecEvent :: Start ( ch_info) => {
264
264
// All the pages that the child process is "allowed to" access.
265
265
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.
267
267
ch_stack = Some ( ch_info. stack_ptr ) ;
268
268
269
269
// We received the signal and are no longer in the main listener loop,
@@ -529,112 +529,113 @@ fn handle_segfault(
529
529
let addr = unsafe { siginfo. si_addr ( ) . addr ( ) } ;
530
530
let page_addr = addr. strict_sub ( addr. strict_rem ( page_size) ) ;
531
531
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.
631
535
let regs = ptrace:: getregs ( pid) . unwrap ( ) ;
632
536
eprintln ! ( "Segfault occurred during FFI at {addr:#018x}" ) ;
633
537
eprintln ! ( "Expected access on pages: {ch_pages:#018x?}" ) ;
634
538
eprintln ! ( "Register dump: {regs:#x?}" ) ;
635
539
ptrace:: kill ( pid) . unwrap ( ) ;
636
- Err ( ExecEnd ( None ) )
540
+ return Err ( ExecEnd ( None ) ) ;
637
541
}
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 ( ( ) )
638
639
}
639
640
640
641
// We only get dropped into these functions via offsetting the instr pointer
0 commit comments