[GH-ISSUE #6649] [Bug] Mach-O build metadata labels legacy Intel simulator images as device platforms #4028

Closed
opened 2026-10-31 00:46:11 +00:00 by bsakaguchi · 1 comment
Owner

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

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

The production inspectNativeMacho boundary returns build_metadata.items[0].platform: "IPHONEOS" for synthetic otool output describing an x86_64 image with LC_VERSION_MIN_IPHONEOS. It marks that collection exhaustive with no limitations. The corresponding Apple platform is iOS Simulator. The same boundary returns device-family labels for the equivalent legacy Intel tvOS and watchOS cases. This is a source-boundary reproduction with synthetic native-command captures; no macOS command, CLI, or MCP execution was performed.

Reproduction

From a source checkout, I ran this reduced parser probe with the pinned Node runtime:

node --experimental-transform-types --input-type=module <<'JS'
import { parseOtoolLoadCommands } from './src/native/parsers/otool.ts';
const text = `Mach header
magic cputype cpusubtype caps filetype ncmds sizeofcmds flags
0xfeedfacf 16777223 3 0x00 2 1 16 0x0
Load command 0
cmd LC_VERSION_MIN_IPHONEOS
cmdsize 16
version 12.0
sdk 12.1
`;
console.log(JSON.stringify(parseOtoolLoadCommands(text).builds));
JS

Observed output:

[{"platform":"IPHONEOS","minimum_os":"12.0","sdk":"12.1","tools":[]}]

A separate probe replayed generated captures through inspectNativeMacho, and passed generated Mach-O headers to REA's dependency reader as a control. Both the main parser and an extracted release parser produced the same failing labels. Each case was executed once in that comparison; the reduced iOS parser probe also reproduced once.

Expected behavior

Legacy version-minimum commands must be interpreted together with the CPU type so Intel simulator images remain distinguishable from device images. Apple's platform decoder explicitly applies this rule to iOS, tvOS, and watchOS. REA's dependency reader implements the same distinction.

For the reduced case, normalized platform metadata should identify iOS Simulator, consistent with IOSSIMULATOR for a modern LC_BUILD_VERSION platform 7 control. Device cases must remain identified as device platforms.

Impact and scope

An analyst receives the wrong platform classification for legacy Intel simulator binaries, even though the required CPU identity is available. This can mislead platform-specific compatibility analysis. The reproduced scope is generated legacy x86_64 iOS, tvOS, and watchOS metadata. A generated arm64 iOS device control and a modern x86_64 iOS simulator control distinguished the inputs correctly in the dependency reader; the modern simulator control also normalized correctly in native inspection. Population frequency is unknown. This is a metadata correctness issue with no demonstrated security impact.

REA version

Main source revision 23def04096869b85c4d3c14a73ed09168483c9f6, package 6.7.0. The parser extracted from published release tag rea-agents-6.7.0 at 27dc42d10249f0410904a75c0997842eb03b931c also reproduced the failure; the installed release CLI was not tested.

Runtime and platform

Node.js v24.22.1, Linux x64, using Node's TypeScript transformation. NOT_ENOUGH_INFO: macOS/Xcode tool versions and live tool behavior were not measured because native command captures were synthetic.

Target and artifact context

Generated Mach-O header/load-command fixtures and matching generated otool text; no application execution or real application samples. The reduced case is a 64-bit Intel header with one 16-byte LC_VERSION_MIN_IPHONEOS command.

Interface

Other — production native inspection and parser boundaries, called directly from Node.

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

  • Expected source: Apple's platform decoder and REA's dependency reader, linked above.
  • Failure source: The executed parser probe prints IPHONEOS; the production native inspection probe returns the same value with exhaustive: true and limitations: [] for build metadata. The normalization branch derives every legacy platform from the command suffix without using CPU identity.
  • Evidence provenance: observed synthetic source-boundary execution; platform semantics are supported by the linked source.
  • Local verification: reproduced at the parser and production native-inspection boundary. No live macOS tool, CLI, or MCP reproduction was run.
  • Reproduction completeness: complete for these source boundaries; live macOS command generation remains untested.

The generated comparison produced:

Input                                 native inspection platform   dependency platform
x86_64 LC_VERSION_MIN_IPHONEOS         IPHONEOS                     7 (iOS simulator)
arm64  LC_VERSION_MIN_IPHONEOS         IPHONEOS                     2 (iOS device)
x86_64 LC_VERSION_MIN_TVOS             TVOS                         8 (tvOS simulator)
x86_64 LC_VERSION_MIN_WATCHOS          WATCHOS                      9 (watchOS simulator)
x86_64 LC_BUILD_VERSION platform 7    IOSSIMULATOR                 7 (iOS simulator)

Typed outcome or recovery expected

Successful metadata should preserve the established simulator/device distinction. Restoration should make the legacy Intel simulator cases agree with the modern simulator control, while retaining the device control's device identity and preserving the original load-command fields.

Analysis or suspected cause

Source-derived: the legacy branch uses only kind.slice("LC_VERSION_MIN_".length). CPU type is decoded earlier in the parser but is not provided to that branch. The resulting builds are published as exhaustive build metadata.

Additional context

Related report #5191 concerns numeric LC_BUILD_VERSION platform codes and other format/slice presentation differences. This report concerns CPU-dependent interpretation of LC_VERSION_MIN_*; modern platform 7 already normalizes correctly in the tested source. Open and closed searches for the legacy command, minimum/simulator terms, and build-metadata/IPHONEOS terms found no exact match. NOT_ENOUGH_INFO: the last known good release and first introduction of the legacy behavior were not established.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: The executed native inspection boundary publishes incorrect platform metadata for bounded legacy simulator inputs, with no build-metadata limitation; modern metadata is unaffected in the control.
  • Workaround status: available
  • Workaround basis: The selected Intel architecture and retained legacy load command permit the simulator identity to be determined manually using Apple's rules. The executed dependency-reader control independently recovered the correct platform without changing the fixture bytes.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6649 ## 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 The production `inspectNativeMacho` boundary returns `build_metadata.items[0].platform: "IPHONEOS"` for synthetic `otool` output describing an x86_64 image with `LC_VERSION_MIN_IPHONEOS`. It marks that collection exhaustive with no limitations. The corresponding Apple platform is iOS Simulator. The same boundary returns device-family labels for the equivalent legacy Intel tvOS and watchOS cases. This is a source-boundary reproduction with synthetic native-command captures; no macOS command, CLI, or MCP execution was performed. ## Reproduction From a source checkout, I ran this reduced parser probe with the pinned Node runtime: ```sh node --experimental-transform-types --input-type=module <<'JS' import { parseOtoolLoadCommands } from './src/native/parsers/otool.ts'; const text = `Mach header magic cputype cpusubtype caps filetype ncmds sizeofcmds flags 0xfeedfacf 16777223 3 0x00 2 1 16 0x0 Load command 0 cmd LC_VERSION_MIN_IPHONEOS cmdsize 16 version 12.0 sdk 12.1 `; console.log(JSON.stringify(parseOtoolLoadCommands(text).builds)); JS ``` Observed output: ```json [{"platform":"IPHONEOS","minimum_os":"12.0","sdk":"12.1","tools":[]}] ``` A separate probe replayed generated captures through `inspectNativeMacho`, and passed generated Mach-O headers to REA's dependency reader as a control. Both the main parser and an extracted release parser produced the same failing labels. Each case was executed once in that comparison; the reduced iOS parser probe also reproduced once. ## Expected behavior Legacy version-minimum commands must be interpreted together with the CPU type so Intel simulator images remain distinguishable from device images. Apple's [platform decoder](https://github.com/apple-oss-distributions/dyld/blob/fd8d0c4d52320ebf64db34f3cb280310d905c5ae/mach_o/UnsafeHeader.cpp#L1164-L1183) explicitly applies this rule to iOS, tvOS, and watchOS. REA's [dependency reader implements the same distinction](https://github.com/morluto/rea/blob/23def04096869b85c4d3c14a73ed09168483c9f6/src/artifacts/apple/MachoLoadCommandReader.ts#L73-L95). For the reduced case, normalized platform metadata should identify iOS Simulator, consistent with `IOSSIMULATOR` for a modern `LC_BUILD_VERSION` platform 7 control. Device cases must remain identified as device platforms. ## Impact and scope An analyst receives the wrong platform classification for legacy Intel simulator binaries, even though the required CPU identity is available. This can mislead platform-specific compatibility analysis. The reproduced scope is generated legacy x86_64 iOS, tvOS, and watchOS metadata. A generated arm64 iOS device control and a modern x86_64 iOS simulator control distinguished the inputs correctly in the dependency reader; the modern simulator control also normalized correctly in native inspection. Population frequency is unknown. This is a metadata correctness issue with no demonstrated security impact. ## REA version Main source revision `23def04096869b85c4d3c14a73ed09168483c9f6`, package `6.7.0`. The parser extracted from published release tag `rea-agents-6.7.0` at `27dc42d10249f0410904a75c0997842eb03b931c` also reproduced the failure; the installed release CLI was not tested. ## Runtime and platform Node.js `v24.22.1`, Linux x64, using Node's TypeScript transformation. NOT_ENOUGH_INFO: macOS/Xcode tool versions and live tool behavior were not measured because native command captures were synthetic. ## Target and artifact context Generated Mach-O header/load-command fixtures and matching generated `otool` text; no application execution or real application samples. The reduced case is a 64-bit Intel header with one 16-byte `LC_VERSION_MIN_IPHONEOS` command. ## Interface Other — production native inspection and parser boundaries, called directly from Node. ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence - Expected source: Apple's platform decoder and REA's dependency reader, linked above. - Failure source: The executed parser probe prints `IPHONEOS`; the production native inspection probe returns the same value with `exhaustive: true` and `limitations: []` for build metadata. The [normalization branch](https://github.com/morluto/rea/blob/23def04096869b85c4d3c14a73ed09168483c9f6/src/native/parsers/otool.ts#L385-L412) derives every legacy platform from the command suffix without using CPU identity. - Evidence provenance: observed synthetic source-boundary execution; platform semantics are supported by the linked source. - Local verification: reproduced at the parser and production native-inspection boundary. No live macOS tool, CLI, or MCP reproduction was run. - Reproduction completeness: complete for these source boundaries; live macOS command generation remains untested. The generated comparison produced: ```text Input native inspection platform dependency platform x86_64 LC_VERSION_MIN_IPHONEOS IPHONEOS 7 (iOS simulator) arm64 LC_VERSION_MIN_IPHONEOS IPHONEOS 2 (iOS device) x86_64 LC_VERSION_MIN_TVOS TVOS 8 (tvOS simulator) x86_64 LC_VERSION_MIN_WATCHOS WATCHOS 9 (watchOS simulator) x86_64 LC_BUILD_VERSION platform 7 IOSSIMULATOR 7 (iOS simulator) ``` ## Typed outcome or recovery expected Successful metadata should preserve the established simulator/device distinction. Restoration should make the legacy Intel simulator cases agree with the modern simulator control, while retaining the device control's device identity and preserving the original load-command fields. ## Analysis or suspected cause Source-derived: the legacy branch uses only `kind.slice("LC_VERSION_MIN_".length)`. CPU type is decoded earlier in the parser but is not provided to that branch. The resulting builds are [published as exhaustive build metadata](https://github.com/morluto/rea/blob/23def04096869b85c4d3c14a73ed09168483c9f6/src/native/NativeMachoInspection.ts#L325). ## Additional context Related report [#5191](https://github.com/morluto/rea/issues/5191) concerns numeric `LC_BUILD_VERSION` platform codes and other format/slice presentation differences. This report concerns CPU-dependent interpretation of `LC_VERSION_MIN_*`; modern platform 7 already normalizes correctly in the tested source. Open and closed searches for the legacy command, minimum/simulator terms, and build-metadata/IPHONEOS terms found no exact match. NOT_ENOUGH_INFO: the last known good release and first introduction of the legacy behavior were not established. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: The executed native inspection boundary publishes incorrect platform metadata for bounded legacy simulator inputs, with no build-metadata limitation; modern metadata is unaffected in the control. - Workaround status: available - Workaround basis: The selected Intel architecture and retained legacy load command permit the simulator identity to be determined manually using Apple's rules. The executed dependency-reader control independently recovered the correct platform without changing the fixture bytes.
Author
Owner

@Sriram-PR commented on GitHub (Oct 23, 2026):

I'll take this one. Reproduced on main (a281455f) with your probe: x86_64 with LC_VERSION_MIN_IPHONEOS, TVOS or WATCHOS comes back as IPHONEOS / TVOS / WATCHOS, same for i386 watchOS, while arm64 is correctly the device. The LC_VERSION_MIN_ branch in otool.ts just takes the platform from the command name and never looks at the header cputype.

Plan: follow dyld's rule from UnsafeHeader.cpp (forEachPlatform): IPHONEOS and WATCHOS on x86_64 or i386, and TVOS on x86_64, report IOSSIMULATOR / WATCHOSSIMULATOR / TVOSSIMULATOR, which are the same names LC_BUILD_VERSION already uses. arm64 and MACOSX stay as they are. Parser tests for the three simulator cases, i386 tvOS staying TVOS, and the arm64 controls.

<!-- gh-comment-id:6363490664 --> @Sriram-PR commented on GitHub (Oct 23, 2026): I'll take this one. Reproduced on main (a281455f) with your probe: x86_64 with LC_VERSION_MIN_IPHONEOS, TVOS or WATCHOS comes back as IPHONEOS / TVOS / WATCHOS, same for i386 watchOS, while arm64 is correctly the device. The `LC_VERSION_MIN_` branch in `otool.ts` just takes the platform from the command name and never looks at the header cputype. Plan: follow dyld's rule from `UnsafeHeader.cpp` (`forEachPlatform`): IPHONEOS and WATCHOS on x86_64 or i386, and TVOS on x86_64, report IOSSIMULATOR / WATCHOSSIMULATOR / TVOSSIMULATOR, which are the same names LC_BUILD_VERSION already uses. arm64 and MACOSX stay as they are. Parser tests for the three simulator cases, i386 tvOS staying TVOS, and the arm64 controls.
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#4028
No description provided.