Skip to content

Commit 2bc07c6

Browse files
committed
generic-sandbox: Fix invalid jump detection on macOS / Rosetta 2
On Rosetta 2, indirect jump to a non-canonical address doesn't trigger a page fault (SIGBUS) right away. Instead, it jumps to the address, and then triggers a page fault (SIGBUS) when trying to fetch the instruction. This means that previous program counter is now lost, and now we have no way of detecting if the guest program triggered an invalid jump or not. We fix this program by updating the vmctx before making the jump and in the signal handler we check if the current program counter is non-canonical. Signed-off-by: Aman <[email protected]>
1 parent e0fbf1b commit 2bc07c6

File tree

2 files changed

+31
-0
lines changed

2 files changed

+31
-0
lines changed

crates/polkavm/src/compiler/amd64.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,22 @@ where
920920
asm.assert_reserved_exactly_as_needed();
921921
}
922922
SandboxKind::Generic => {
923+
// On Rosetta 2, indirect jump to a non-canonical address doesn't trigger a SIGBUS right away.
924+
// Instead, it jumps to the address, and then triggers a SIGBUS when trying to fetch the instruction.
925+
// This means that previous program counter is now lost. There are other ways we can design it (by adding a
926+
// dedicated jump table subroutine) but that would be less efficient.
927+
// Therefore, let's store the executing address to the program counter before jumping.
928+
#[cfg(target_os = "macos")]
929+
{
930+
let label_start = self.asm.create_label();
931+
self.asm.push(lea_rip_label(TMP_REG, label_start));
932+
self.push(store(
933+
RegSize::R64,
934+
Self::vmctx_field(S::offset_table().next_native_program_counter),
935+
TMP_REG,
936+
));
937+
}
938+
923939
// TODO: This also could be more efficient.
924940
self.push(lea_rip_label(TMP_REG, self.jump_table_label));
925941
self.push(push(conv_reg(base)));

crates/polkavm/src/sandbox/generic.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,21 @@ unsafe extern "C" fn signal_handler(signal: c_int, info: &sys::siginfo_t, contex
415415

416416
let rip = fetch_reg!(rip);
417417
let vmctx = &mut *vmctx;
418+
419+
// On Rosetta 2, the JMP emulation logic doesn't work same as on x64.
420+
// Instead of triggering a GPF immdiately, it jumps to that address and then trigger a PF.
421+
// Therefore the original program counter is lost.
422+
// We fix this problem by storing the program counter in vmctx before jumping.
423+
// See jump_indirect_impl for more details.
424+
#[cfg(target_os = "macos")]
425+
{
426+
let is_invalid_rip = (rip >> 48) != 0;
427+
if is_invalid_rip {
428+
log::trace!("Jump table invalid address hit, returning to host");
429+
trigger_exit(vmctx, ExitReason::Signal);
430+
}
431+
}
432+
418433
if vmctx.program_range.contains(&rip) {
419434
use polkavm_common::regmap::NativeReg;
420435
for reg in polkavm_common::program::Reg::ALL {

0 commit comments

Comments
 (0)