[PR #3138] [CLOSED] fix(native): keep decoding legacy Mach-O binds past a negative skip #5635

Closed
opened 2026-10-31 01:08:08 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/3138
Author: @Dragolone
Created: 10/14/2026
Status: ❌ Closed

Base: main ← Head: fix/macho-bind-negative-skip


📝 Commits (1)

  • 54d501e fix(native): keep decoding legacy Mach-O binds past a negative skip

📊 Changes

2 files changed (+43 additions, -1 deletions)

View changed files

📝 src/native/AppleMachoFixups.test.ts (+41 -0)
📝 src/native/AppleMachoFixups.ts (+2 -1)

📄 Description

Summary

readBindStream in src/native/AppleMachoFixups.ts adds BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB's skip to the bind address without wrapping at 64 bits. Apple's linker emits a skip of 0xfffffffffffffff8 (-8), which makes the next bind land on the same slot. The reader instead moves the address past 2^64, so the next bind throws "Bind expansion leaves its file-backed segment" and the rest of that bind stream is dropped. inspect_native_dispatch_metadata (macOS provider, LC_DYLD_INFO images) then has no bind records for the slots after it, so external classes such as superclasses imported from other images are left unresolved.

No issue is linked: I searched open issues and PRs for "AppleMachoFixups", "DO_BIND_ADD_ADDR_ULEB", "bind opcode", "dyld info bind", "LC_DYLD_INFO" and "superclass" and found no report of this. No open PR touches src/native/AppleMachoFixups.ts.

Problem and expected behavior

Smallest trigger, from the bind stream of a shipping macOS app: a0 f8 ff ff ff ff ff ff ff ff 01 90 (DO_BIND_ADD_ADDR_ULEB -8, then DO_BIND). dyld advances by ULEB + pointer size in unsigned 64-bit arithmetic, so the address stays on the same slot. The ADD_ADDR_ULEB (0x80) case in this reader already wraps with & 0xffffffffffffffffn; the shared bind() step did not.

Expected: decoding continues after the negative skip, and later records in the stream are kept.

Change and scope

One line: bind() now updates the address with the same 64-bit wrap as ADD_ADDR_ULEB. This covers DO_BIND_ADD_ADDR_ULEB, DO_BIND_ADD_ADDR_IMM_SCALED and DO_BIND_ULEB_TIMES_SKIPPING_ULEB, which all advance through bind(). preflightRun is unchanged, so a repeated run that would wrap out of its segment is still refused.

Not included: a slot that this pattern binds twice is reported as "Ambiguous bind records", the result the reader already gives for any slot that two bind records target (for example via SET_SEGMENT_AND_OFFSET). Deciding which of two records wins is a separate question and is left as is.

Contribution scope: a correctness fix in an existing parser; a maintainer scope decision is not applicable.

Contract and boundary impact

  • Semantic owner and earliest changed stage: readBindStream in src/native/AppleMachoFixups.ts.
  • Contribution scope fit and linked maintainer decision: in scope; not applicable.
  • CLI and MCP/tool-catalog contract: none changed; inspect_native_dispatch_metadata resolves more binds for affected images.
  • Provider, bridge, target-format, or platform compatibility: Mach-O images with LC_DYLD_INFO/LC_DYLD_INFO_ONLY bind opcodes; chained fixups are unaffected.
  • Evidence, artifact, provenance, or reconstruction contract: none.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact: none.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: src/native/AppleMachoFixups.test.ts — "wraps a negative DO_BIND_ADD_ADDR_ULEB step and keeps decoding". It binds a slot with a -8 skip, binds it again, then binds _OBJC_CLASS_$_NSObject at the next slot. It expects no failures, the _OBJC_CLASS_$_NSObject bind at the next slot, and a plain rebase after it.
  • Base reproduction or other evidence: executed. On the base the test fails with + [ "bind bind opcodes: Bind expansion leaves its file-backed segment" ], and the next slot decodes as { kind: 'rebase', target: 0n } instead of the bind. The byte sequence a0 f8 ff ff ff ff ff ff ff ff 01 occurs 8 times in the executable of one locally installed macOS app.
  • User-visible CLI/MCP output (if applicable): not captured.
  • Remaining proof gaps: no end-to-end MCP fixture for a real LC_DYLD_INFO app was added.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npx vitest run src/native/AppleMachoFixups.test.ts on the base — 1 failed (the new test), 36 passed.

  • npx vitest run src/native/AppleMachoFixups.test.ts src/native/AppleDispatchMetadata.test.ts src/native/AppleMachoSelection.test.ts src/native/AppleSwiftVtables.test.ts with the fix — 4 files, 79 tests passed.

  • npx tsc --noEmit — 0 errors.

  • npx oxlint src/native/AppleMachoFixups.ts src/native/AppleMachoFixups.test.ts — 0 errors; the 3 size/complexity warnings on existing functions are unchanged from the base.

  • npx oxfmt --check on the changed files — clean.

  • npm run verify:module-boundaries — passed.

  • pre-push hook (deps check, typecheck, lint) — passed.

  • After rebasing onto main 583008852: npx vitest run src/native/AppleMachoFixups.test.ts src/native/AppleDispatchMetadata.test.ts src/native/AppleMachoSelection.test.ts src/native/AppleSwiftVtables.test.ts — 90 tests passed; npx tsc --noEmit — 0 errors.

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: macOS; parser-level unit coverage only.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: read-only parsing; no new inputs or side effects.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/3138 **Author:** [@Dragolone](https://github.com/Dragolone) **Created:** 10/14/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/macho-bind-negative-skip` --- ### 📝 Commits (1) - [`54d501e`](https://github.com/morluto/rea/commit/54d501edf9c2cf9e63c441c2b05c0e099053b71f) fix(native): keep decoding legacy Mach-O binds past a negative skip ### 📊 Changes **2 files changed** (+43 additions, -1 deletions) <details> <summary>View changed files</summary> 📝 `src/native/AppleMachoFixups.test.ts` (+41 -0) 📝 `src/native/AppleMachoFixups.ts` (+2 -1) </details> ### 📄 Description ## Summary `readBindStream` in `src/native/AppleMachoFixups.ts` adds `BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB`'s skip to the bind address without wrapping at 64 bits. Apple's linker emits a skip of `0xfffffffffffffff8` (-8), which makes the next bind land on the same slot. The reader instead moves the address past 2^64, so the next bind throws "Bind expansion leaves its file-backed segment" and the rest of that bind stream is dropped. `inspect_native_dispatch_metadata` (macOS provider, `LC_DYLD_INFO` images) then has no bind records for the slots after it, so external classes such as superclasses imported from other images are left unresolved. No issue is linked: I searched open issues and PRs for "AppleMachoFixups", "DO_BIND_ADD_ADDR_ULEB", "bind opcode", "dyld info bind", "LC_DYLD_INFO" and "superclass" and found no report of this. No open PR touches `src/native/AppleMachoFixups.ts`. ## Problem and expected behavior Smallest trigger, from the bind stream of a shipping macOS app: `a0 f8 ff ff ff ff ff ff ff ff 01 90` (`DO_BIND_ADD_ADDR_ULEB -8`, then `DO_BIND`). dyld advances by ULEB + pointer size in unsigned 64-bit arithmetic, so the address stays on the same slot. The `ADD_ADDR_ULEB` (`0x80`) case in this reader already wraps with `& 0xffffffffffffffffn`; the shared `bind()` step did not. Expected: decoding continues after the negative skip, and later records in the stream are kept. ## Change and scope One line: `bind()` now updates the address with the same 64-bit wrap as `ADD_ADDR_ULEB`. This covers `DO_BIND_ADD_ADDR_ULEB`, `DO_BIND_ADD_ADDR_IMM_SCALED` and `DO_BIND_ULEB_TIMES_SKIPPING_ULEB`, which all advance through `bind()`. `preflightRun` is unchanged, so a repeated run that would wrap out of its segment is still refused. Not included: a slot that this pattern binds twice is reported as "Ambiguous bind records", the result the reader already gives for any slot that two bind records target (for example via `SET_SEGMENT_AND_OFFSET`). Deciding which of two records wins is a separate question and is left as is. Contribution scope: a correctness fix in an existing parser; a maintainer scope decision is not applicable. ## Contract and boundary impact - Semantic owner and earliest changed stage: `readBindStream` in `src/native/AppleMachoFixups.ts`. - Contribution scope fit and linked maintainer decision: in scope; not applicable. - CLI and MCP/tool-catalog contract: none changed; `inspect_native_dispatch_metadata` resolves more binds for affected images. - Provider, bridge, target-format, or platform compatibility: Mach-O images with `LC_DYLD_INFO`/`LC_DYLD_INFO_ONLY` bind opcodes; chained fixups are unaffected. - Evidence, artifact, provenance, or reconstruction contract: none. - Target selection, authority, upstream behavior, and cleanup details (if applicable): not applicable. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact: none. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: `src/native/AppleMachoFixups.test.ts` — "wraps a negative DO_BIND_ADD_ADDR_ULEB step and keeps decoding". It binds a slot with a -8 skip, binds it again, then binds `_OBJC_CLASS_$_NSObject` at the next slot. It expects no failures, the `_OBJC_CLASS_$_NSObject` bind at the next slot, and a plain rebase after it. - Base reproduction or other evidence: executed. On the base the test fails with `+ [ "bind bind opcodes: Bind expansion leaves its file-backed segment" ]`, and the next slot decodes as `{ kind: 'rebase', target: 0n }` instead of the bind. The byte sequence `a0 f8 ff ff ff ff ff ff ff ff 01` occurs 8 times in the executable of one locally installed macOS app. - User-visible CLI/MCP output (if applicable): not captured. - Remaining proof gaps: no end-to-end MCP fixture for a real `LC_DYLD_INFO` app was added. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npx vitest run src/native/AppleMachoFixups.test.ts` on the base — 1 failed (the new test), 36 passed. - `npx vitest run src/native/AppleMachoFixups.test.ts src/native/AppleDispatchMetadata.test.ts src/native/AppleMachoSelection.test.ts src/native/AppleSwiftVtables.test.ts` with the fix — 4 files, 79 tests passed. - `npx tsc --noEmit` — 0 errors. - `npx oxlint src/native/AppleMachoFixups.ts src/native/AppleMachoFixups.test.ts` — 0 errors; the 3 size/complexity warnings on existing functions are unchanged from the base. - `npx oxfmt --check` on the changed files — clean. - `npm run verify:module-boundaries` — passed. - pre-push hook (deps check, typecheck, lint) — passed. - After rebasing onto main 583008852: `npx vitest run src/native/AppleMachoFixups.test.ts src/native/AppleDispatchMetadata.test.ts src/native/AppleMachoSelection.test.ts src/native/AppleSwiftVtables.test.ts` — 90 tests passed; `npx tsc --noEmit` — 0 errors. ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: macOS; parser-level unit coverage only. - Package or release metadata impact: none. - Security, privacy, process, or containment review: read-only parsing; no new inputs or side effects. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [ ] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:08:08 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#5635
No description provided.