@@ -18,6 +18,11 @@ const ARCH_WORD_SIZE: usize = 4;
18
18
#[ cfg( target_arch = "x86_64" ) ]
19
19
const ARCH_WORD_SIZE : usize = 8 ;
20
20
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
+
21
26
/// The address of the page set to be edited, initialised to a sentinel null
22
27
/// pointer.
23
28
static PAGE_ADDR : AtomicPtr < u8 > = AtomicPtr :: new ( std:: ptr:: null_mut ( ) ) ;
@@ -472,7 +477,27 @@ fn handle_segfault(
472
477
let stack_ptr = ch_stack. strict_add ( CALLBACK_STACK_SIZE / 2 ) ;
473
478
let regs_bak = ptrace:: getregs ( pid) . unwrap ( ) ;
474
479
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" ) ;
476
501
477
502
// Move the instr ptr into the deprotection code.
478
503
#[ expect( clippy:: as_conversions) ]
@@ -512,33 +537,8 @@ fn handle_segfault(
512
537
ptrace:: write ( pid, std:: ptr:: with_exposed_provenance_mut ( a) , 0 ) . unwrap ( ) ;
513
538
}
514
539
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!
518
540
let regs_bak = ptrace:: getregs ( pid) . unwrap ( ) ;
519
541
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" ) ;
542
542
543
543
// Reprotect everything and continue.
544
544
#[ expect( clippy:: as_conversions) ]
0 commit comments