From 576b58cefea9ed6abd8f467565ab114ec248e259 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 14 Oct 2026 13:58:14 +0800 Subject: [PATCH] fix(native): wrap bind address advances to 64 bits Apply dyld modulo-64 address arithmetic at the shared bind advance, retaining subsequent bindings after a negative ULEB skip. Validation: all 37 Mach-O fixup tests pass, including continuation and false-failure controls. Based on #3138. Co-authored-by: Dragolone <810170966qq@gmail.com> --- src/native/AppleMachoFixups.test.ts | 41 +++++++++++++++++++++++++++++ src/native/AppleMachoFixups.ts | 4 ++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/native/AppleMachoFixups.test.ts b/src/native/AppleMachoFixups.test.ts index bf4ae348f2..318fdbf7c1 100644 --- a/src/native/AppleMachoFixups.test.ts +++ b/src/native/AppleMachoFixups.test.ts @@ -907,6 +907,47 @@ describe("legacy pointer fixup streams", () => { target: 0x99n, }); }); + + it("wraps a negative DO_BIND_ADD_ADDR_ULEB step and keeps decoding", () => { + // The unsigned 64-bit encoding of -8 revisits the current pointer slot. + const back = [0xf8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01]; + const opcodes = Buffer.from([ + 0x11, // SET_DYLIB_ORDINAL_IMM 1 + 0x40, + ...Buffer.from("__ZdlPv\0"), + 0x71, + 0x08, // SET_SEGMENT_AND_OFFSET segment 1 + 8 + 0xa0, + ...back, // DO_BIND_ADD_ADDR_ULEB -8 + 0x90, // DO_BIND the same slot again + 0x12, // SET_DYLIB_ORDINAL_IMM 2 + 0x40, + ...Buffer.from("_OBJC_CLASS_$_NSObject\0"), + 0x90, // DO_BIND the next slot + 0x00, + ]); + const fixups = parsePointerFixups( + opcodes, + { + chained: null, + binds: [{ offset: 0, size: opcodes.length, stream: "bind" }], + rebases: [], + dylibs: ["/usr/lib/libc++.1.dylib", "/usr/lib/libobjc.A.dylib"], + }, + SEGMENTS, + BASE, + ); + expect(fixups.failures).toEqual([]); + expect(fixups.decode(BASE + 0x4010n, 0n)).toMatchObject({ + kind: "bind", + symbol: "_OBJC_CLASS_$_NSObject", + library: "/usr/lib/libobjc.A.dylib", + }); + expect(fixups.decode(BASE + 0x4018n, 0x99n)).toEqual({ + kind: "rebase", + target: 0x99n, + }); + }); }); describe("Objective-C property attributes", () => { diff --git a/src/native/AppleMachoFixups.ts b/src/native/AppleMachoFixups.ts index 09080ec1d6..bafb676f87 100644 --- a/src/native/AppleMachoFixups.ts +++ b/src/native/AppleMachoFixups.ts @@ -652,7 +652,9 @@ const readBindStream = ( } else { binds.push({ start: state.address, count, stride, binding }); } - state.address += count * stride; + // The dyld bind stream advances addresses modulo 64 bits; ld uses -8 to + // bind the current pointer slot again with DO_BIND_ADD_ADDR_ULEB. + state.address = (state.address + count * stride) & 0xffffffffffffffffn; }; while (cursor.at < end) { const byte = bytes[cursor.at++] ?? 0;