[GH-ISSUE #3390] [Bug] Mach-O export inspection merges names ending in carriage return #1257

Closed
opened 2026-10-16 21:03:26 +00:00 by bsakaguchi · 1 comment
Owner

Originally created by @coygeek on GitHub (Oct 14, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/3390

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

inspect-macho collapses the distinct exports "trailing" and "trailing\r" into one result named "trailing". Both symbols exist in an unstripped executable and have different addresses. Native tools preserve both names, but REA loses one identity during successful inspection.

The CLI exits with status 0. The native export table reports the ordinary name at offset 0x330 and the carriage-return name at offset 0x328 in the tested build. REA returns only "trailing", with address "0x100000330", alongside the unrelated header and main exports. It returns no entry for "trailing\r". Its generic incomplete-export limitations mention stripped binaries, but this input is unstripped and both names are present in the observed native output.

Reproduction

  1. Create a clean checkout and build the runtime at the tested revision.
git clone https://github.com/morluto/rea.git rea
cd rea
git checkout --detach 8dde6370d0809ad04cda3342d4e6f290f48aa063
npm ci
npm run build:cached
  1. From the repository root, create and compile the following input. The C string escape \r gives the first assembler label a literal trailing carriage-return byte.
rea_root="$PWD"
probe_root="$(mktemp -d -t rea-macho-exports)"
cd "$probe_root"
mkdir home tmp
cat > symbols.c <<'C'
int first(void) __asm__("trailing\r");
int first(void) { return 42; }
int second(void) __asm__("trailing");
int second(void) { return 7; }
int main(void) { return first() + second(); }
C
xcrun clang symbols.c -o symbols
  1. Inspect the exact native name bytes and export addresses, then inspect the same executable with REA.
xcrun nm -gjU --no-dyldinfo symbols | \
  python3 -c 'import sys; print(repr(sys.stdin.buffer.read()))'
xcrun dyld_info -exports symbols
env HOME="$PWD/home" TMPDIR="$PWD/tmp" \
  node "$rea_root/scripts/rea.mjs" inspect-macho "$PWD/symbols" --json
  1. Observe that native output preserves both names and separate addresses, while the successful REA result contains only "trailing". The source-built executable is not run. The byte representation printed by the nm command distinguishes the name payload from its LF record terminator.

Expected behavior

The export inventory should retain both exact names as distinct entries. The carriage return is part of an observed Mach-O symbol name, so it should not become a record separator or cause the symbol to merge with the ordinary "trailing" export. The different native-tool addresses must remain associated with their respective names.

AGENTS.md:19,29 requires preservation of identity and source values across boundaries. src/contracts/native/nativeToolContracts.ts:95-96 advertises export inspection. tests/boundary/providers/native/nativeInventory.golden.test.ts:480-498 also pins preservation of exact nm-established export names containing display-like suffixes.

Impact and scope

  • Impact level: P2.
  • Assessment status: supported.
  • Impact basis: A successful inspection silently omits a distinct observed export for a valid symbol-name input class. This loses symbol identity and its address from the returned inventory without changing the original executable.
  • Workaround status: available.
  • Workaround basis: Raw nm bytes retain both exact names, as demonstrated by the reproduction's byte representation. Direct dyld_info output retains the separate export addresses. These native outputs recover the facts but do not repair REA's normalized inventory.

REA version

6.4.0 (main at 8dde6370d0)

Runtime and platform

Node v26.10.0, macOS 27.0.1 arm64, Apple clang 21.0.0. The observed reproduction used the existing built runtime without source changes.

Target and artifact context

A source-built, unstripped Mach-O executable containing the distinct export names "trailing" and "trailing\r" at different addresses. Inspection is static, and the executable is not run.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Raw nm output from the executed public CLI reproduction:

b'__mh_execute_header\n_main\ntrailing\ntrailing\r\n'

The raw distinct names, different export addresses, and successful response missing one symbol were observed with a source-built executable. Evidence provenance is observed, local verification reproduced the failure, and the reproduction is complete. Native offsets were 0x330 for "trailing" and 0x328 for "trailing\r"; REA returned only "trailing" at "0x100000330".

Typed outcome or recovery expected

Inspect the same executable and require distinct "trailing" and "trailing\r" entries, with each address matching its native export-table entry. Keep the plain-name control working. A result that merely renames the carriage-return export or returns two copies of the ordinary name does not restore the identity contract.

Analysis or suspected cause

Source-derived reasoning: src/native/NativeMachoInspection.ts:342-347 treats a literal CR followed by the tool's LF as CRLF framing. src/native/parsers/dyldInfo.ts:19-20 also splits and trims these names. src/native/NativeMachoInspection.ts:365-373 deduplicates the resulting collided identities. These source locations explain how the name byte can disappear before deduplication; restoration has not been established by a patch.

Additional context

  • Related #760 concerns line breaks in filenames corrupting thin architecture metadata, whereas this report concerns a carriage-return byte in an export name.
  • Related #765 concerns whitespace in CFBundleExecutable names during plist decoding and target resolution, whereas this inspection succeeds and loses an export identity.
  • Related #2473 concerns excessive inline import/export output, whereas this report concerns two exact export names collapsing into one.
Originally created by @coygeek on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3390 ### 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 `inspect-macho` collapses the distinct exports `"trailing"` and `"trailing\r"` into one result named `"trailing"`. Both symbols exist in an unstripped executable and have different addresses. Native tools preserve both names, but REA loses one identity during successful inspection. The CLI exits with status 0. The native export table reports the ordinary name at offset `0x330` and the carriage-return name at offset `0x328` in the tested build. REA returns only `"trailing"`, with address `"0x100000330"`, alongside the unrelated header and main exports. It returns no entry for `"trailing\r"`. Its generic incomplete-export limitations mention stripped binaries, but this input is unstripped and both names are present in the observed native output. ### Reproduction 1. Create a clean checkout and build the runtime at the tested revision. ```sh git clone https://github.com/morluto/rea.git rea cd rea git checkout --detach 8dde6370d0809ad04cda3342d4e6f290f48aa063 npm ci npm run build:cached ``` 2. From the repository root, create and compile the following input. The C string escape `\r` gives the first assembler label a literal trailing carriage-return byte. ```sh rea_root="$PWD" probe_root="$(mktemp -d -t rea-macho-exports)" cd "$probe_root" mkdir home tmp cat > symbols.c <<'C' int first(void) __asm__("trailing\r"); int first(void) { return 42; } int second(void) __asm__("trailing"); int second(void) { return 7; } int main(void) { return first() + second(); } C xcrun clang symbols.c -o symbols ``` 3. Inspect the exact native name bytes and export addresses, then inspect the same executable with REA. ```sh xcrun nm -gjU --no-dyldinfo symbols | \ python3 -c 'import sys; print(repr(sys.stdin.buffer.read()))' xcrun dyld_info -exports symbols env HOME="$PWD/home" TMPDIR="$PWD/tmp" \ node "$rea_root/scripts/rea.mjs" inspect-macho "$PWD/symbols" --json ``` 4. Observe that native output preserves both names and separate addresses, while the successful REA result contains only `"trailing"`. The source-built executable is not run. The byte representation printed by the `nm` command distinguishes the name payload from its LF record terminator. ### Expected behavior The export inventory should retain both exact names as distinct entries. The carriage return is part of an observed Mach-O symbol name, so it should not become a record separator or cause the symbol to merge with the ordinary `"trailing"` export. The different native-tool addresses must remain associated with their respective names. `AGENTS.md:19,29` requires preservation of identity and source values across boundaries. `src/contracts/native/nativeToolContracts.ts:95-96` advertises export inspection. `tests/boundary/providers/native/nativeInventory.golden.test.ts:480-498` also pins preservation of exact nm-established export names containing display-like suffixes. ### Impact and scope - Impact level: P2. - Assessment status: supported. - Impact basis: A successful inspection silently omits a distinct observed export for a valid symbol-name input class. This loses symbol identity and its address from the returned inventory without changing the original executable. - Workaround status: available. - Workaround basis: Raw `nm` bytes retain both exact names, as demonstrated by the reproduction's byte representation. Direct `dyld_info` output retains the separate export addresses. These native outputs recover the facts but do not repair REA's normalized inventory. ### REA version 6.4.0 (main at 8dde6370d0809ad04cda3342d4e6f290f48aa063) ### Runtime and platform Node v26.10.0, macOS 27.0.1 arm64, Apple clang 21.0.0. The observed reproduction used the existing built runtime without source changes. ### Target and artifact context A source-built, unstripped Mach-O executable containing the distinct export names `"trailing"` and `"trailing\r"` at different addresses. Inspection is static, and the executable is not run. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence Raw `nm` output from the executed public CLI reproduction: ```text b'__mh_execute_header\n_main\ntrailing\ntrailing\r\n' ``` The raw distinct names, different export addresses, and successful response missing one symbol were observed with a source-built executable. Evidence provenance is observed, local verification reproduced the failure, and the reproduction is complete. Native offsets were `0x330` for `"trailing"` and `0x328` for `"trailing\r"`; REA returned only `"trailing"` at `"0x100000330"`. ### Typed outcome or recovery expected Inspect the same executable and require distinct `"trailing"` and `"trailing\r"` entries, with each address matching its native export-table entry. Keep the plain-name control working. A result that merely renames the carriage-return export or returns two copies of the ordinary name does not restore the identity contract. ### Analysis or suspected cause Source-derived reasoning: `src/native/NativeMachoInspection.ts:342-347` treats a literal CR followed by the tool's LF as CRLF framing. `src/native/parsers/dyldInfo.ts:19-20` also splits and trims these names. `src/native/NativeMachoInspection.ts:365-373` deduplicates the resulting collided identities. These source locations explain how the name byte can disappear before deduplication; restoration has not been established by a patch. ### Additional context - Related [#760](https://github.com/morluto/rea/issues/760) concerns line breaks in filenames corrupting thin architecture metadata, whereas this report concerns a carriage-return byte in an export name. - Related [#765](https://github.com/morluto/rea/issues/765) concerns whitespace in CFBundleExecutable names during plist decoding and target resolution, whereas this inspection succeeds and loses an export identity. - Related [#2473](https://github.com/morluto/rea/issues/2473) concerns excessive inline import/export output, whereas this report concerns two exact export names collapsing into one.
Author
Owner

@matbrgz commented on GitHub (Oct 20, 2026):

Cross-referencing with canonical issue #3384 ([Bug] Mach-O dependency inspection removes install-name whitespace, addressed in PR #4979) and sibling report #5680 ([Bug] inspect-macho rejects valid section and segment names equal to version).

Consolidated under Cluster CB (Mach-O Inspection Parser Fidelity & Identifier Normalization) in tracker #4330.

<!-- gh-comment-id:6303859560 --> @matbrgz commented on GitHub (Oct 20, 2026): Cross-referencing with canonical issue #3384 (`[Bug] Mach-O dependency inspection removes install-name whitespace`, addressed in PR #4979) and sibling report #5680 (`[Bug] inspect-macho rejects valid section and segment names equal to version`). Consolidated under **Cluster CB (Mach-O Inspection Parser Fidelity & Identifier Normalization)** in tracker #4330.
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#1257
No description provided.