[GH-ISSUE #2139] [Bug] IDA provider analyzes the x86_64 slice of a universal Mach-O while reporting arm64 #700

Closed
opened 2026-10-15 18:27:31 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @zecaro on GitHub (Oct 12, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/2139

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Native analysis or decompilation

Observed behavior

On an arm64 macOS host with the IDA provider (REA_ANALYSIS_PROVIDER=ida, idalib-mcp upstream, headless), opening /bin/cat (universal: x86_64, arm64e, arm64e.x1) reports architecture: "arm64" in open_binary and in every Evidence subject and analysis profile, but the analysed code is x86_64.

Evidence from one session:

  • procedure_pseudo_code on start returns x86_64 SysV code: push rbp, mov rbp, rsp, push r15, sub rsp, 18h, mov r14d, edi, and locals at [rbp-40h].
  • The same result carries "architecture": "arm64" in subject and in analysis_profile.
  • A direct call to the configured upstream (idalib-mcp --stdio) on my own copy of the same binary returns the same x86_64 disassembly, so the upstream chooses the x86_64 slice.

list_architectures (native-macos provider) correctly lists all three slices, which makes the mismatch visible in the same session.

Expected behavior

The architecture recorded in Evidence must describe the slice the provider actually analyses. Either thin the adopted FAT slice before the provider opens it, or refuse a FAT whose selected slice the provider will not analyse.

Root cause

Read from src at HEAD:

  • src/application/BinaryTargetResolver.ts:70 selects a host-compatible slice for the reported architecture: const hostArchitecture = options.hostArchitecture ?? process.arch.
  • The resolved path stays the whole FAT (src/application/BinaryTargetResolver.ts:196) and is never replaced by a thinned slice.
  • src/ida/IdaWorkspace.ts:52 copies that file into the private workspace unchanged and verifies the whole-file digest.
  • The upstream then selects its own slice (x86_64 for /bin/cat), which can differ from the reported one.

Environment

  • REA 6.4.0 (published), IDA provider via REA_IDA_MCP_CONFIG.
  • macOS 27.0.1 on arm64.
  • Upstream ida-pro-mcp 1.0.0 through idalib-mcp --stdio --max-workers 1.
  • #656 reports the same class for the Ghidra provider.
  • #1502 selects the FAT slice for the admitted CPU type.
Originally created by @zecaro on GitHub (Oct 12, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/2139 ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Native analysis or decompilation ### Observed behavior On an arm64 macOS host with the IDA provider (`REA_ANALYSIS_PROVIDER=ida`, `idalib-mcp` upstream, headless), opening `/bin/cat` (universal: `x86_64`, `arm64e`, `arm64e.x1`) reports `architecture: "arm64"` in `open_binary` and in every Evidence subject and analysis profile, but the analysed code is x86_64. Evidence from one session: - `procedure_pseudo_code` on `start` returns x86_64 SysV code: `push rbp`, `mov rbp, rsp`, `push r15`, `sub rsp, 18h`, `mov r14d, edi`, and locals at `[rbp-40h]`. - The same result carries `"architecture": "arm64"` in `subject` and in `analysis_profile`. - A direct call to the configured upstream (`idalib-mcp --stdio`) on my own copy of the same binary returns the same x86_64 disassembly, so the upstream chooses the x86_64 slice. `list_architectures` (native-macos provider) correctly lists all three slices, which makes the mismatch visible in the same session. ### Expected behavior The architecture recorded in Evidence must describe the slice the provider actually analyses. Either thin the adopted FAT slice before the provider opens it, or refuse a FAT whose selected slice the provider will not analyse. ### Root cause Read from `src` at HEAD: - `src/application/BinaryTargetResolver.ts:70` selects a host-compatible slice for the reported architecture: `const hostArchitecture = options.hostArchitecture ?? process.arch`. - The resolved `path` stays the whole FAT (`src/application/BinaryTargetResolver.ts:196`) and is never replaced by a thinned slice. - `src/ida/IdaWorkspace.ts:52` copies that file into the private workspace unchanged and verifies the whole-file digest. - The upstream then selects its own slice (x86_64 for `/bin/cat`), which can differ from the reported one. ### Environment - REA 6.4.0 (published), IDA provider via `REA_IDA_MCP_CONFIG`. - macOS 27.0.1 on arm64. - Upstream `ida-pro-mcp` 1.0.0 through `idalib-mcp --stdio --max-workers 1`. ### Related - #656 reports the same class for the Ghidra provider. - #1502 selects the FAT slice for the admitted CPU type.
bsakaguchi 2026-10-15 18:27:31 +00:00
Author
Owner

@akushonkamen commented on GitHub (Oct 12, 2026):

Confirming the root cause at current main: parseFatMachO (src/domain/binaryTarget.ts:136-146) resolves the host-compatible preferred slice and even computes its byte range (:124), but the resolved target keeps the whole FAT path with no slice offset/size exported; BinaryTargetResolver.ts:62 only uses process.arch for the reported architecture; and IdaWorkspace.copyInput (src/ida/IdaWorkspace.ts:56-59) copies the entire FAT into the private workspace, so upstream idalib picks its own slice (x86_64 for /bin/cat) while Evidence reports arm64.

I'm preparing a fix: export the selected slice's byte range from the FAT parse and have the IDA provider's copyInput thin the input to exactly that slice (digest over thinned bytes), preserving the container-identity semantics introduced by b38429cb6. Validation will use controlled universal Mach-O fixtures (lipo -create) with unit tests — no live IDA in the loop (no license available here), which I'll disclose in the PR.

Note: this work is assisted by an AI coding agent (account akushonkamen).

<!-- gh-comment-id:6124328966 --> @akushonkamen commented on GitHub (Oct 12, 2026): Confirming the root cause at current main: `parseFatMachO` (`src/domain/binaryTarget.ts:136-146`) resolves the host-compatible preferred slice and even computes its byte range (`:124`), but the resolved target keeps the whole FAT path with no slice offset/size exported; `BinaryTargetResolver.ts:62` only uses `process.arch` for the *reported* architecture; and `IdaWorkspace.copyInput` (`src/ida/IdaWorkspace.ts:56-59`) copies the entire FAT into the private workspace, so upstream idalib picks its own slice (x86_64 for /bin/cat) while Evidence reports arm64. I'm preparing a fix: export the selected slice's byte range from the FAT parse and have the IDA provider's copyInput thin the input to exactly that slice (digest over thinned bytes), preserving the container-identity semantics introduced by b38429cb6. Validation will use controlled universal Mach-O fixtures (`lipo -create`) with unit tests — no live IDA in the loop (no license available here), which I'll disclose in the PR. Note: this work is assisted by an AI coding agent (account akushonkamen).
Author
Owner

@akushonkamen commented on GitHub (Oct 12, 2026):

Fix up: #2201 thins the selected FAT slice into the IDA provider's private input — copyInput now writes exactly the bytes of the slice whose architecture Evidence reports (universalSlice { offset, size } exported from parseFatMachO), the thinned product anchors the post-session identity check, and container identity (mach-o-universal, whole-file sha256, snapshot cache key) is unchanged. Validated with controlled lipo -create universal fixtures plus the targeted suites listed in the PR; no live IDA in the loop (no license here), as flagged in my earlier note. One disclosure: snapshots keyed by the whole-FAT digest made before this fix can theoretically still replay against the whole container.

<!-- gh-comment-id:6125298170 --> @akushonkamen commented on GitHub (Oct 12, 2026): Fix up: #2201 thins the selected FAT slice into the IDA provider's private input — `copyInput` now writes exactly the bytes of the slice whose architecture Evidence reports (`universalSlice { offset, size }` exported from `parseFatMachO`), the thinned product anchors the post-session identity check, and container identity (mach-o-universal, whole-file sha256, snapshot cache key) is unchanged. Validated with controlled `lipo -create` universal fixtures plus the targeted suites listed in the PR; no live IDA in the loop (no license here), as flagged in my earlier note. One disclosure: snapshots keyed by the whole-FAT digest made before this fix can theoretically still replay against the whole container.
Author
Owner

@nikolay-dementiev commented on GitHub (Oct 12, 2026):

Verified the fix from #2201 on real IDA Professional 9.1 (headless idalib, ida-pro-mcp c133c38) with main at 11fe5a13f. rea function <copy of /usr/bin/caffeinate> start --json (universal x86_64 + arm64e) now decompiles and disassembles arm64e code at 0x100000740, which matches subject.architecture: "arm64". Before the fix, at c22111eca, the same command returned x86_64 code at 0x1000006c0.

<!-- gh-comment-id:6129562328 --> @nikolay-dementiev commented on GitHub (Oct 12, 2026): Verified the fix from #2201 on real IDA Professional 9.1 (headless idalib, ida-pro-mcp c133c38) with `main` at 11fe5a13f. `rea function <copy of /usr/bin/caffeinate> start --json` (universal `x86_64` + `arm64e`) now decompiles and disassembles arm64e code at `0x100000740`, which matches `subject.architecture: "arm64"`. Before the fix, at c22111eca, the same command returned x86_64 code at `0x1000006c0`.
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#700
No description provided.