@@ -18,6 +18,11 @@ const ARCH_WORD_SIZE: usize = 4;
1818#[ cfg( target_arch = "x86_64" ) ]
1919const ARCH_WORD_SIZE : usize = 8 ;
2020
21+ // x86 max instruction length is 15 bytes:
22+ // https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
23+ // See vol. 3B section 24.25.
24+ const ARCH_MAX_INSTR_SIZE : usize = 15 ;
25+
2126/// The address of the page set to be edited, initialised to a sentinel null
2227/// pointer.
2328static PAGE_ADDR : AtomicPtr < u8 > = AtomicPtr :: new ( std:: ptr:: null_mut ( ) ) ;
@@ -472,7 +477,27 @@ fn handle_segfault(
472477 let stack_ptr = ch_stack. strict_add ( CALLBACK_STACK_SIZE / 2 ) ;
473478 let regs_bak = ptrace:: getregs ( pid) . unwrap ( ) ;
474479 let mut new_regs = regs_bak;
475- let ip_prestep = regs_bak. ip ( ) ;
480+
481+ // Read at least one instruction from the ip. It's possible that the instruction
482+ // that triggered the segfault was short and at the end of the mapped text area,
483+ // so some of these reads may fail; in that case, just write empty bytes. If all
484+ // reads failed, the disassembler will report an error.
485+ let instr = ( 0 ..( ARCH_MAX_INSTR_SIZE . div_ceil ( ARCH_WORD_SIZE ) ) )
486+ . flat_map ( |ofs| {
487+ // This reads one word of memory; we divided by `ARCH_WORD_SIZE` above to compensate for that.
488+ ptrace:: read (
489+ pid,
490+ std:: ptr:: without_provenance_mut (
491+ regs_bak. ip ( ) . strict_add ( ARCH_WORD_SIZE . strict_mul ( ofs) ) ,
492+ ) ,
493+ )
494+ . unwrap_or_default ( )
495+ . to_ne_bytes ( )
496+ } )
497+ . collect :: < Vec < _ > > ( ) ;
498+
499+ // Now figure out the size + type of access and log it down.
500+ capstone_disassemble ( & instr, addr, cs, acc_events) . expect ( "Failed to disassemble instruction" ) ;
476501
477502 // Move the instr ptr into the deprotection code.
478503 #[ expect( clippy:: as_conversions) ]
@@ -512,33 +537,8 @@ fn handle_segfault(
512537 ptrace:: write ( pid, std:: ptr:: with_exposed_provenance_mut ( a) , 0 ) . unwrap ( ) ;
513538 }
514539
515- // Save registers and grab the bytes that were executed. This would
516- // be really nasty if it was a jump or similar but those thankfully
517- // won't do memory accesses and so can't trigger this!
518540 let regs_bak = ptrace:: getregs ( pid) . unwrap ( ) ;
519541 new_regs = regs_bak;
520- let ip_poststep = regs_bak. ip ( ) ;
521-
522- // Ensure that we've actually gone forwards.
523- assert ! ( ip_poststep > ip_prestep) ;
524- // But not by too much. 64 bytes should be "big enough" on ~any architecture.
525- assert ! ( ip_prestep. strict_add( 64 ) > ip_poststep) ;
526-
527- // We need to do reads/writes in word-sized chunks.
528- let diff = ( ip_poststep. strict_sub ( ip_prestep) ) . div_ceil ( ARCH_WORD_SIZE ) ;
529- let instr = ( ip_prestep..ip_prestep. strict_add ( diff) ) . fold ( vec ! [ ] , |mut ret, ip| {
530- // This only needs to be a valid pointer in the child process, not ours.
531- ret. append (
532- & mut ptrace:: read ( pid, std:: ptr:: without_provenance_mut ( ip) )
533- . unwrap ( )
534- . to_ne_bytes ( )
535- . to_vec ( ) ,
536- ) ;
537- ret
538- } ) ;
539-
540- // Now figure out the size + type of access and log it down.
541- capstone_disassemble ( & instr, addr, cs, acc_events) . expect ( "Failed to disassemble instruction" ) ;
542542
543543 // Reprotect everything and continue.
544544 #[ expect( clippy:: as_conversions) ]
0 commit comments