From bf934d5592e707ba5e7769043de3a430ed8d4923 Mon Sep 17 00:00:00 2001 From: BaaRai <130491490+BaaRai@users.noreply.github.com> Date: Sat, 10 Oct 2026 00:07:01 +0200 Subject: [PATCH] fix(hopper): probe interior navigation from multi-byte instructions (#1285) The real-Hopper navigation probe derived its interior coordinate as anchor + 1, which assumes the fixture procedure's first instruction is longer than one byte. Host toolchains without endbr64-style prologue padding (Kali gcc 16, or any build with -fcf-protection=none) compile rea_entry to start with the one-byte push rbp, so anchor + 1 is itself an instruction boundary: goto_address correctly observes that boundary and the boundary-contract run rejects a correct bridge. Advance the probe to the first instruction followed by a coordinate gap larger than one byte and derive the interior-coordinate assertions from that instruction, so the probed coordinate is never an analyzed-object boundary on either prologue encoding. --- scripts/lib/real-hopper-navigation.mjs | 27 ++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/scripts/lib/real-hopper-navigation.mjs b/scripts/lib/real-hopper-navigation.mjs index c4096d869..95353f795 100644 --- a/scripts/lib/real-hopper-navigation.mjs +++ b/scripts/lib/real-hopper-navigation.mjs @@ -91,14 +91,29 @@ async function verifyNavigation( ); assert.equal(coordinates[0], address); assert.ok(coordinates[1], "fixture omitted a second instruction"); - const interior = `0x${(BigInt(address) + 1n).toString(16)}`; - assert.equal(await call("goto_address", { address: interior }), address); - assert.equal(await call("current_address"), address); + // The host toolchain controls the prologue encoding: without endbr64-style + // padding the first instruction is one byte, so address + 1 is itself an + // instruction boundary. Probe from inside the first multi-byte instruction + // so the interior coordinate is never an analyzed-object boundary. + let anchor = 0; + while (anchor + 1 < coordinates.length) { + if (BigInt(coordinates[anchor + 1]) - BigInt(coordinates[anchor]) > 1n) + break; + anchor += 1; + } + assert.ok( + anchor + 1 < coordinates.length, + "fixture omitted a multi-byte instruction for interior navigation", + ); + const containing = coordinates[anchor]; + const interior = `0x${(BigInt(containing) + 1n).toString(16)}`; + assert.equal(await call("goto_address", { address: interior }), containing); + assert.equal(await call("current_address"), containing); assert.equal( await call("next_address", { address: interior }), - coordinates[1], + coordinates[anchor + 1], ); - assert.equal(await call("prev_address", { address: interior }), address); + assert.equal(await call("prev_address", { address: interior }), containing); for (const unmapped of ["0x0", "0xfffffffffffffffe"]) { for (const operation of ["goto_address", "next_address", "prev_address"]) await invalid( @@ -106,7 +121,7 @@ async function verifyNavigation( { address: unmapped }, /Address is outside every segment/u, ); - assert.equal(await call("current_address"), address); + assert.equal(await call("current_address"), containing); for (const operation of [ "set_address_name", "set_bookmark",