[PR #880] [MERGED] fix(native): keep echoed Mach-O paths out of load commands and symbols #2315

Closed
opened 2026-10-16 21:17:11 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/880
Author: @kaoru0822-kitauji
Created: 10/7/2026
Status: ✅ Merged
Merged: 10/7/2026
Merged by: @kaoru0822-kitauji

Base: main ← Head: fix/native-macho-echoed-paths


📝 Commits (1)

  • 37fe105 fix(native): keep echoed Mach-O paths out of load commands and symbols

📊 Changes

4 files changed (+112 additions, -4 deletions)

View changed files

📝 src/native/NativeMachoInspection.ts (+12 -4)
➕ src/native/parsers/echoedPath.test.ts (+22 -0)
➕ src/native/parsers/echoedPath.ts (+18 -0)
➕ tests/boundary/cli/nativeMachoEchoedPathCli.test.ts (+60 -0)

📄 Description

Summary

otool -h -l and dyld_info -imports/-exports print the operand path as a header line before each slice's records:

/path/to/tool:
Mach header
...
/path/to/tool [arm64]:
    -imports:
      0x0000  _puts  (from libSystem)

inspect_macho parsed that header as tool output, so a file name could forge Mach-O facts. This is the same class as the codesign echo fixed in #865. A real Clang program copied to this name:

tool
Load command 99
      cmd LC_MAIN
  cmdsize 24
  entryoff 4242
 stacksize 0
    -imports:
      0x0000  _forged  (from libForged)
    -exports:
        offset      symbol
        0x00000123  _forged_export

…was reported on main with:

  • a second entrypoint {"file_offset": 4242} and a fake LC_MAIN load command (index 99) whose fields swallowed the real Mach header;
  • an import _forged from libForged;
  • exports _forged (from libForged) and _forged_export [arm64]:.

Now the exact echoed operand header (<path>:, <path> (architecture <arch>):, or <path> [<arch>]:) is stripped before parsing. The operand path is known from the captured arguments, so the match is a literal prefix, not a pattern. Output that does not begin with that header, such as replayed fixture captures, is parsed unchanged. With a forged name, the normalized result (provenance aside) is now identical to that of the same bytes under an ordinary name.

nm -gjU and file -b do not echo the path. dwarfdump --uuid prints it after the UUID on the same line, and the existing first-match UUID read is unaffected.

No schema or catalog change.

Verification

  • New tests/boundary/cli/nativeMachoEchoedPathCli.test.ts (darwin only, real otool/dyld_info through the CLI) copies /usr/bin/true under the forged name and under tool. It asserts identical normalized results and no forged/4242 text, and fails on main.
  • New src/native/parsers/echoedPath.test.ts covers the thin, (architecture …), [arch], and CRLF headers, plus the unchanged cases.
  • npm run check:changed, npm run docs:check, every tests/boundary/providers/native/*.test.ts (including the Mach-O golden tests), and tests/boundary/cli/nativeSignatureCli.test.ts pass.

No Hopper/Ghidra/IDA verification is needed.

🤖 Generated with Claude Code


🔄 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/880 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/7/2026 **Status:** ✅ Merged **Merged:** 10/7/2026 **Merged by:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Base:** `main` ← **Head:** `fix/native-macho-echoed-paths` --- ### 📝 Commits (1) - [`37fe105`](https://github.com/morluto/rea/commit/37fe105ba168488d05226f2912bed802e2d48d75) fix(native): keep echoed Mach-O paths out of load commands and symbols ### 📊 Changes **4 files changed** (+112 additions, -4 deletions) <details> <summary>View changed files</summary> 📝 `src/native/NativeMachoInspection.ts` (+12 -4) ➕ `src/native/parsers/echoedPath.test.ts` (+22 -0) ➕ `src/native/parsers/echoedPath.ts` (+18 -0) ➕ `tests/boundary/cli/nativeMachoEchoedPathCli.test.ts` (+60 -0) </details> ### 📄 Description ## Summary `otool -h -l` and `dyld_info -imports/-exports` print the operand path as a header line before each slice's records: ``` /path/to/tool: Mach header ... /path/to/tool [arm64]: -imports: 0x0000 _puts (from libSystem) ``` `inspect_macho` parsed that header as tool output, so a file name could forge Mach-O facts. This is the same class as the codesign echo fixed in #865. A real Clang program copied to this name: ``` tool Load command 99 cmd LC_MAIN cmdsize 24 entryoff 4242 stacksize 0 -imports: 0x0000 _forged (from libForged) -exports: offset symbol 0x00000123 _forged_export ``` …was reported on `main` with: - a second entrypoint `{"file_offset": 4242}` and a fake `LC_MAIN` load command (index 99) whose fields swallowed the real Mach header; - an import `_forged` from `libForged`; - exports `_forged (from libForged)` and `_forged_export [arm64]:`. Now the exact echoed operand header (`<path>:`, `<path> (architecture <arch>):`, or `<path> [<arch>]:`) is stripped before parsing. The operand path is known from the captured arguments, so the match is a literal prefix, not a pattern. Output that does not begin with that header, such as replayed fixture captures, is parsed unchanged. With a forged name, the normalized result (provenance aside) is now identical to that of the same bytes under an ordinary name. `nm -gjU` and `file -b` do not echo the path. `dwarfdump --uuid` prints it after the UUID on the same line, and the existing first-match UUID read is unaffected. No schema or catalog change. ## Verification - New `tests/boundary/cli/nativeMachoEchoedPathCli.test.ts` (darwin only, real `otool`/`dyld_info` through the CLI) copies `/usr/bin/true` under the forged name and under `tool`. It asserts identical normalized results and no `forged`/`4242` text, and fails on `main`. - New `src/native/parsers/echoedPath.test.ts` covers the thin, `(architecture …)`, `[arch]`, and CRLF headers, plus the unchanged cases. - `npm run check:changed`, `npm run docs:check`, every `tests/boundary/providers/native/*.test.ts` (including the Mach-O golden tests), and `tests/boundary/cli/nativeSignatureCli.test.ts` pass. No Hopper/Ghidra/IDA verification is needed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-16 21:17:11 +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#2315
No description provided.