[GH-ISSUE #3872] [Bug] inspect-macho reports a dylib's own LC_ID_DYLIB install name as a dependency #1494

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

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

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 on a Mach-O dylib that carries the normal LC_ID_DYLIB identification command lists that command's install name as a dependency, inflating the dependency count and letting consumers infer a self-dependency. The independent trace-dylib-resolution command on the same image correctly reports that name as the image identity and not as a dependency.

The source-built dylib with one real dependency returns two exhaustive dependencies; the extra row is the image's own @rpath/libprobe.dylib install name with kind: "LC_ID_DYLIB".

Reproduction

Build a one-symbol dylib with a known install name and inspect it; the dylib is only inspected, never loaded or executed.

work="$(mktemp -d)"
printf '%s\n' 'int exported_value(void) { return 42; }' > "$work/library.c"
xcrun clang -dynamiclib "$work/library.c" \
  -Wl,-install_name,@rpath/libprobe.dylib -o "$work/libprobe.dylib"
rea inspect-macho "$work/libprobe.dylib" --format json \
  | jq '.normalized_result.dependencies | {items: [.items[] | {path, kind}], total, exhaustive}'

Observed projection, exit status 0:

{
  "items": [
    {"path": "@rpath/libprobe.dylib", "kind": "LC_ID_DYLIB"},
    {"path": "/usr/lib/libSystem.B.dylib", "kind": "LC_LOAD_DYLIB"}
  ],
  "total": 2,
  "exhaustive": true
}

The independent control exited 0 and reported the correct set:

rea trace-dylib-resolution "$work/libprobe.dylib" --format json \
  | jq '.normalized_result | {install_name: .images[0].slices[0].install_name, dependencies: [.edges[].install_name]}'
{
  "install_name": "@rpath/libprobe.dylib",
  "dependencies": ["/usr/lib/libSystem.B.dylib"]
}

Expected behavior

src/contracts/native/nativeToolContracts.ts:95-96 advertises Mach-O dependency inspection, and a dylib's LC_ID_DYLIB identifies that image rather than a library it loads. REA's own load-command reader draws this distinction at src/artifacts/apple/MachoLoadCommandReader.ts:379-381, storing the command in slice.install_name and returning without adding a dependency. The normalized dependency collection should contain only the actual load dependencies while LC_ID_DYLIB remains in the raw load-command evidence.

Impact and scope

Any Mach-O dylib carrying its normal identification command gains a false dependency and an inflated, still-exhaustive dependency count in successful inspection Evidence, so consumers building dependency inventories can infer a self-dependency.

Workaround: available. trace-dylib-resolution returned the correct dependency set on the fixture, and the erroneous row keeps its distinguishing kind: "LC_ID_DYLIB", so callers can filter it.

Reporter's impact estimate: P2 (supported).

REA version

6.5.0, built from source at main commit 034342ba28 (latest npm release is 6.5.0)

Runtime and platform

Node.js v26.10.0; macOS 27.0.1 arm64

Target and artifact context

Mach-O dynamic library (dylib)

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

  • Expected source: src/contracts/native/nativeToolContracts.ts:95-96 advertises dependency inspection, and src/artifacts/apple/MachoLoadCommandReader.ts:379-381 shows the intended distinction that LC_ID_DYLIB is the image identity, not a dependency.
  • Failure source: the observed output above, with root cause at src/native/parsers/otool.ts:46-48 including LC_ID_DYLIB in the set reused by collectDependency and src/native/parsers/otool.ts:394-399 appending the identification command to the dependency collection.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

Typed outcome or recovery expected

The normalized dependency collection should have listed only the actual LC_LOAD_DYLIB dependencies (total 1) with LC_ID_DYLIB kept in raw load-command evidence, not an extra LC_ID_DYLIB self-dependency row (total 2).

Restoration check:

Failing observable: inspect-macho on the fixture dylib returns total: 2 with an LC_ID_DYLIB row for @rpath/libprobe.dylib. Passing observable: the same inspection returns only /usr/lib/libSystem.B.dylib as a dependency (total: 1), matching trace-dylib-resolution, while LC_ID_DYLIB remains in the raw load-command evidence. Control: trace-dylib-resolution on the same fixture already returns the correct single dependency.

Analysis or suspected cause

src/native/parsers/otool.ts:46-48 includes LC_ID_DYLIB in the command set reused by collectDependency, so src/native/parsers/otool.ts:394-399 appends the identification command to the dependency collection; REA's independent reader at src/artifacts/apple/MachoLoadCommandReader.ts:379-381 already treats LC_ID_DYLIB as image identity, matching trace-dylib-resolution.

Additional context

Open #3384 concerns whitespace loss in dependency install names, a different failure; no issue reports the LC_ID_DYLIB self-dependency row.

This report came from AI-assisted source review with local reproduction where stated above; the evidence labels distinguish observed runs from source-only reasoning.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3872 ### 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` on a Mach-O dylib that carries the normal `LC_ID_DYLIB` identification command lists that command's install name as a dependency, inflating the dependency count and letting consumers infer a self-dependency. The independent `trace-dylib-resolution` command on the same image correctly reports that name as the image identity and not as a dependency. The source-built dylib with one real dependency returns two exhaustive dependencies; the extra row is the image's own `@rpath/libprobe.dylib` install name with `kind: "LC_ID_DYLIB"`. ### Reproduction Build a one-symbol dylib with a known install name and inspect it; the dylib is only inspected, never loaded or executed. ```sh work="$(mktemp -d)" printf '%s\n' 'int exported_value(void) { return 42; }' > "$work/library.c" xcrun clang -dynamiclib "$work/library.c" \ -Wl,-install_name,@rpath/libprobe.dylib -o "$work/libprobe.dylib" rea inspect-macho "$work/libprobe.dylib" --format json \ | jq '.normalized_result.dependencies | {items: [.items[] | {path, kind}], total, exhaustive}' ``` Observed projection, exit status 0: ```json { "items": [ {"path": "@rpath/libprobe.dylib", "kind": "LC_ID_DYLIB"}, {"path": "/usr/lib/libSystem.B.dylib", "kind": "LC_LOAD_DYLIB"} ], "total": 2, "exhaustive": true } ``` The independent control exited 0 and reported the correct set: ```sh rea trace-dylib-resolution "$work/libprobe.dylib" --format json \ | jq '.normalized_result | {install_name: .images[0].slices[0].install_name, dependencies: [.edges[].install_name]}' ``` ```json { "install_name": "@rpath/libprobe.dylib", "dependencies": ["/usr/lib/libSystem.B.dylib"] } ``` ### Expected behavior src/contracts/native/nativeToolContracts.ts:95-96 advertises Mach-O dependency inspection, and a dylib's `LC_ID_DYLIB` identifies that image rather than a library it loads. REA's own load-command reader draws this distinction at src/artifacts/apple/MachoLoadCommandReader.ts:379-381, storing the command in `slice.install_name` and returning without adding a dependency. The normalized dependency collection should contain only the actual load dependencies while `LC_ID_DYLIB` remains in the raw load-command evidence. ### Impact and scope Any Mach-O dylib carrying its normal identification command gains a false dependency and an inflated, still-`exhaustive` dependency count in successful inspection Evidence, so consumers building dependency inventories can infer a self-dependency. Workaround: available. `trace-dylib-resolution` returned the correct dependency set on the fixture, and the erroneous row keeps its distinguishing `kind: "LC_ID_DYLIB"`, so callers can filter it. Reporter's impact estimate: P2 (supported). ### REA version 6.5.0, built from source at main commit 034342ba28c8e56c3746be92b78aa7299afa02bd (latest npm release is 6.5.0) ### Runtime and platform Node.js v26.10.0; macOS 27.0.1 arm64 ### Target and artifact context Mach-O dynamic library (dylib) ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence - Expected source: src/contracts/native/nativeToolContracts.ts:95-96 advertises dependency inspection, and src/artifacts/apple/MachoLoadCommandReader.ts:379-381 shows the intended distinction that `LC_ID_DYLIB` is the image identity, not a dependency. - Failure source: the observed output above, with root cause at src/native/parsers/otool.ts:46-48 including `LC_ID_DYLIB` in the set reused by `collectDependency` and src/native/parsers/otool.ts:394-399 appending the identification command to the dependency collection. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete ### Typed outcome or recovery expected The normalized dependency collection should have listed only the actual LC_LOAD_DYLIB dependencies (total 1) with LC_ID_DYLIB kept in raw load-command evidence, not an extra LC_ID_DYLIB self-dependency row (total 2). Restoration check: Failing observable: `inspect-macho` on the fixture dylib returns `total: 2` with an `LC_ID_DYLIB` row for `@rpath/libprobe.dylib`. Passing observable: the same inspection returns only `/usr/lib/libSystem.B.dylib` as a dependency (`total: 1`), matching `trace-dylib-resolution`, while `LC_ID_DYLIB` remains in the raw load-command evidence. Control: `trace-dylib-resolution` on the same fixture already returns the correct single dependency. ### Analysis or suspected cause src/native/parsers/otool.ts:46-48 includes LC_ID_DYLIB in the command set reused by collectDependency, so src/native/parsers/otool.ts:394-399 appends the identification command to the dependency collection; REA's independent reader at src/artifacts/apple/MachoLoadCommandReader.ts:379-381 already treats LC_ID_DYLIB as image identity, matching trace-dylib-resolution. ### Additional context Open #3384 concerns whitespace loss in dependency install names, a different failure; no issue reports the `LC_ID_DYLIB` self-dependency row. This report came from AI-assisted source review with local reproduction where stated above; the evidence labels distinguish observed runs from source-only reasoning.
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#1494
No description provided.