Skip to content

Commit 7e0ae3a

Browse files
authored
Merge pull request rust-lang#4551 from nia-e/fixup-jump-instr
native-lib: more resilient grabbing of instruction bytes
2 parents 1feabac + fbd8b96 commit 7e0ae3a

File tree

1 file changed

+26
-26
lines changed
  • src/tools/miri/src/shims/native_lib/trace

1 file changed

+26
-26
lines changed

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

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ const ARCH_WORD_SIZE: usize = 4;
1818
#[cfg(target_arch = "x86_64")]
1919
const 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.
2328
static 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

Comments
 (0)