[PR #1985] [MERGED] fix(managed): admit ReadyToRun images built for non-Windows hosts #4950

Closed
opened 2026-10-31 00:57:17 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/1985
Author: @JayOfTheKeyboard
Created: 10/11/2026
Status: ✅ Merged
Merged: 10/12/2026
Merged by: @morluto

Base: main ← Head: fix/managed-readytorun-os-machine


📝 Commits (1)

  • 60edd8e fix(managed): admit ReadyToRun images built for non-Windows hosts

📊 Changes

6 files changed (+168 additions, -40 deletions)

View changed files

📝 src/domain/binaryTarget.test.ts (+35 -0)
📝 src/domain/binaryTarget.ts (+6 -20)
➕ src/domain/peMachine.ts (+50 -0)
📝 src/dotnet/ManagedArtifactFailures.test.ts (+8 -0)
📝 src/dotnet/ManagedPeReader.ts (+14 -20)
➕ tests/boundary/providers/managed/managedNativeOsMachine.test.ts (+55 -0)

📄 Description

Summary

REA rejects every .NET ReadyToRun assembly compiled for Linux, macOS or the BSDs with unsupported PE architecture. On a Linux .NET install that covers most of the shared framework, so inspect-artifact and all three managed inspection tools refuse files like System.Private.CoreLib.dll. No issue is linked: I found this by running REA over the assemblies on my machine, and the fix is small enough to explain here.

Problem and expected behavior

ReadyToRun images built for a non-Windows host do not store a plain COFF machine. The .NET toolchain XORs it with an OS constant, IMAGE_FILE_MACHINE_NATIVE_OS_OVERRIDE, defined in dotnet/runtime src/coreclr/inc/pedecoder.h:

Host Override x64 machine on disk
Linux 0x7B79 0xFD1D
Apple 0x4644 0xC020
FreeBSD 0xADC4 0x2BA0
OpenBSD 0xADC5 0x2BA1
NetBSD 0x1993 0x9FF7
SunOS 0x1992 0x9FF6

parsePeRecord in src/domain/binaryTarget.ts and architectureFor in src/dotnet/ManagedPeReader.ts each had their own switch over the five Windows machine values. Anything else was rejected before the CLI header was read.

Measured on Fedora 44 with the distribution's .NET 8.0.31 SDK, on main at 1d4c4eb0:

  • 424 of the 2,451 PE DLLs under /usr/lib64/dotnet have machine 0xFD1D. That is 89 of the 168 in shared/Microsoft.NETCore.App (including System.Private.CoreLib.dll), all 139 in shared/Microsoft.AspNetCore.App, and SDK assemblies such as Microsoft.Build.Framework.dll. The other 2,027 are AnyCPU IL (0x014C).
  • Out of a random sample of 40 PE files from the machine, inspect-artifact and inspect-managed-members each failed on 9 and inspect-managed-native-boundaries on 7. Every failure was this bug.
$ rea inspect-managed-artifact /usr/lib64/dotnet/shared/Microsoft.NETCore.App/8.0.31/System.Linq.Parallel.dll --format json
{"code":"target_unavailable", "message":"Cannot open artifact: unsupported PE architecture. ...",
 "details":{"reason":"unsupported PE architecture", ...}}

Expected: these are managed PE files, and REA should inspect their metadata the same way it does for a Windows build.

Change and scope

  • New src/domain/peMachine.ts owns COFF machine decoding. decodePeMachine tries the plain value first, then the six documented overrides, and returns { architecture, nativeOsOverride }. No override value decodes to a second valid machine, and none collides with a plain one.
  • parsePeRecord and ManagedPeReader both call it. Their duplicated switches are removed.
  • An override machine is accepted only when the image has a CLI directory, because only ReadyToRun output uses it. A native PE carrying 0xFD1D is still rejected, with the same error text as before.
  • ManagedPeLayout.machine already keeps the raw value, so pe.machine_hex still reports 0xfd1d.

Not changed, and yours to decide:

  • The target OS is not recorded as its own field. Adding one would change the Evidence contract. The raw machine stays in the managed inspection result, so the OS can be derived from it.
  • Each caller enforces the CLI-directory guard itself. parsePeRecord needs the architecture before it can find the data directories, so it cannot pass the managed flag into the decoder. Folding the guard into the decoder would mean reordering that function's header checks.
  • I did not try Ghidra or IDA on these images. Admission now reaches them as managed x86_64 PE files. On Windows the Ghidra provider already refuses managed images, and IDA uses its CLI loader.

Contract and boundary impact

  • Semantic owner and earliest changed stage: PE machine decoding at target admission (src/domain/peMachine.ts), used by parsePeRecord and readManagedPeLayout.
  • CLI and MCP/tool-catalog contract: none. No schemas change.
  • Provider, bridge, target-format, or platform compatibility: ReadyToRun PE files built for non-Windows hosts are now admitted as format: "pe", managed: true. Windows machine values behave exactly as before.
  • Evidence, artifact, provenance, or reconstruction contract: none. The raw machine is still reported.
  • Process execution, authorization, cleanup, or containment impact: none.
  • Generated metadata, package, or installation impact: none.

Evidence and regression coverage

  • Tests added or updated:

    • src/domain/binaryTarget.test.ts: x86_64 for all six hosts and arm64 for Linux and Apple, on a managed PE32+ header. Also checks that an override machine on a native image is still rejected.
    • tests/boundary/providers/managed/managedNativeOsMachine.test.ts: Linux x86, Linux ARM, Apple ARM and FreeBSD x86 ReadyToRun fixtures go through parseBinaryTarget and all three managed operations. Asserts pe.machine, pe.architecture and managed_architecture.
    • src/dotnet/ManagedArtifactFailures.test.ts: the managed reader rejects an override machine when the image has no CLI directory.
  • Base reproduction: on the parent commit, the new header and boundary cases fail with unsupported PE architecture. Mutations each turn at least one test red: removing either guard, skipping the override loop, or dropping any one of the six constants.

  • User-visible CLI/MCP output, with the fix, on System.Private.CoreLib.dll:

    subject.architecture                       x86_64
    normalized_result.pe.machine_hex           0xfd1d
    normalized_result.pe.architecture          x86_64
    normalized_result.classification.status    managed
    normalized_result.classification.managed_architecture  x86_64
    normalized_result.metadata.status          complete
    
  • Remaining proof gaps: no macOS, BSD or arm64 ReadyToRun binaries were run. Those cases are covered by fixtures only. Linux x64 was run against real files.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npm run test:focused -- over every src/domain and src/dotnet test, tests/boundary/providers/managed/*, tests/acceptance/managedCliOutput.test.ts and tests/boundary/mcp/managedMcp.test.ts: 147 files, 2,172 tests passed.
  • npm run check:fast: passed.
  • npm run verify:test-discovery: "Discovered all 882 test files exactly once."
  • Real files, Linux x64, built CLI: 30 randomly chosen 0xFD1D assemblies all succeed with inspect-managed-members. The 40-file random sample now passes inspect-artifact, inspect-managed-members and inspect-managed-native-boundaries with no failures from this cause.

Compatibility, safety, and release

  • Breaking changes or migration steps: none.
  • Real Hopper/Ghidra, browser, or OS coverage: Linux x64 (Fedora 44) only. No provider runs.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: parsing only. The new branch runs only after an existing plain-machine lookup fails, and it accepts nothing on native images.

Review checklist

  • This PR addresses a concrete problem or an agreed enhancement.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 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/1985 **Author:** [@JayOfTheKeyboard](https://github.com/JayOfTheKeyboard) **Created:** 10/11/2026 **Status:** ✅ Merged **Merged:** 10/12/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/managed-readytorun-os-machine` --- ### 📝 Commits (1) - [`60edd8e`](https://github.com/morluto/rea/commit/60edd8ec82592ae0c6be5c8749a91c0b095694d8) fix(managed): admit ReadyToRun images built for non-Windows hosts ### 📊 Changes **6 files changed** (+168 additions, -40 deletions) <details> <summary>View changed files</summary> 📝 `src/domain/binaryTarget.test.ts` (+35 -0) 📝 `src/domain/binaryTarget.ts` (+6 -20) ➕ `src/domain/peMachine.ts` (+50 -0) 📝 `src/dotnet/ManagedArtifactFailures.test.ts` (+8 -0) 📝 `src/dotnet/ManagedPeReader.ts` (+14 -20) ➕ `tests/boundary/providers/managed/managedNativeOsMachine.test.ts` (+55 -0) </details> ### 📄 Description ## Summary REA rejects every .NET ReadyToRun assembly compiled for Linux, macOS or the BSDs with `unsupported PE architecture`. On a Linux .NET install that covers most of the shared framework, so `inspect-artifact` and all three managed inspection tools refuse files like `System.Private.CoreLib.dll`. No issue is linked: I found this by running REA over the assemblies on my machine, and the fix is small enough to explain here. ## Problem and expected behavior ReadyToRun images built for a non-Windows host do not store a plain COFF machine. The .NET toolchain XORs it with an OS constant, `IMAGE_FILE_MACHINE_NATIVE_OS_OVERRIDE`, defined in [dotnet/runtime `src/coreclr/inc/pedecoder.h`](https://github.com/dotnet/runtime/blob/main/src/coreclr/inc/pedecoder.h): | Host | Override | x64 machine on disk | | --- | --- | --- | | Linux | `0x7B79` | `0xFD1D` | | Apple | `0x4644` | `0xC020` | | FreeBSD | `0xADC4` | `0x2BA0` | | OpenBSD | `0xADC5` | `0x2BA1` | | NetBSD | `0x1993` | `0x9FF7` | | SunOS | `0x1992` | `0x9FF6` | `parsePeRecord` in `src/domain/binaryTarget.ts` and `architectureFor` in `src/dotnet/ManagedPeReader.ts` each had their own switch over the five Windows machine values. Anything else was rejected before the CLI header was read. Measured on Fedora 44 with the distribution's .NET 8.0.31 SDK, on `main` at `1d4c4eb0`: - 424 of the 2,451 PE DLLs under `/usr/lib64/dotnet` have machine `0xFD1D`. That is 89 of the 168 in `shared/Microsoft.NETCore.App` (including `System.Private.CoreLib.dll`), all 139 in `shared/Microsoft.AspNetCore.App`, and SDK assemblies such as `Microsoft.Build.Framework.dll`. The other 2,027 are AnyCPU IL (`0x014C`). - Out of a random sample of 40 PE files from the machine, `inspect-artifact` and `inspect-managed-members` each failed on 9 and `inspect-managed-native-boundaries` on 7. Every failure was this bug. ```text $ rea inspect-managed-artifact /usr/lib64/dotnet/shared/Microsoft.NETCore.App/8.0.31/System.Linq.Parallel.dll --format json {"code":"target_unavailable", "message":"Cannot open artifact: unsupported PE architecture. ...", "details":{"reason":"unsupported PE architecture", ...}} ``` Expected: these are managed PE files, and REA should inspect their metadata the same way it does for a Windows build. ## Change and scope - New `src/domain/peMachine.ts` owns COFF machine decoding. `decodePeMachine` tries the plain value first, then the six documented overrides, and returns `{ architecture, nativeOsOverride }`. No override value decodes to a second valid machine, and none collides with a plain one. - `parsePeRecord` and `ManagedPeReader` both call it. Their duplicated switches are removed. - An override machine is accepted only when the image has a CLI directory, because only ReadyToRun output uses it. A native PE carrying `0xFD1D` is still rejected, with the same error text as before. - `ManagedPeLayout.machine` already keeps the raw value, so `pe.machine_hex` still reports `0xfd1d`. Not changed, and yours to decide: - The target OS is not recorded as its own field. Adding one would change the Evidence contract. The raw machine stays in the managed inspection result, so the OS can be derived from it. - Each caller enforces the CLI-directory guard itself. `parsePeRecord` needs the architecture before it can find the data directories, so it cannot pass the managed flag into the decoder. Folding the guard into the decoder would mean reordering that function's header checks. - I did not try Ghidra or IDA on these images. Admission now reaches them as managed x86_64 PE files. On Windows the Ghidra provider already refuses managed images, and IDA uses its CLI loader. ## Contract and boundary impact - Semantic owner and earliest changed stage: PE machine decoding at target admission (`src/domain/peMachine.ts`), used by `parsePeRecord` and `readManagedPeLayout`. - CLI and MCP/tool-catalog contract: none. No schemas change. - Provider, bridge, target-format, or platform compatibility: ReadyToRun PE files built for non-Windows hosts are now admitted as `format: "pe"`, `managed: true`. Windows machine values behave exactly as before. - Evidence, artifact, provenance, or reconstruction contract: none. The raw machine is still reported. - Process execution, authorization, cleanup, or containment impact: none. - Generated metadata, package, or installation impact: none. ## Evidence and regression coverage - Tests added or updated: - `src/domain/binaryTarget.test.ts`: x86_64 for all six hosts and arm64 for Linux and Apple, on a managed PE32+ header. Also checks that an override machine on a native image is still rejected. - `tests/boundary/providers/managed/managedNativeOsMachine.test.ts`: Linux x86, Linux ARM, Apple ARM and FreeBSD x86 ReadyToRun fixtures go through `parseBinaryTarget` and all three managed operations. Asserts `pe.machine`, `pe.architecture` and `managed_architecture`. - `src/dotnet/ManagedArtifactFailures.test.ts`: the managed reader rejects an override machine when the image has no CLI directory. - Base reproduction: on the parent commit, the new header and boundary cases fail with `unsupported PE architecture`. Mutations each turn at least one test red: removing either guard, skipping the override loop, or dropping any one of the six constants. - User-visible CLI/MCP output, with the fix, on `System.Private.CoreLib.dll`: ```text subject.architecture x86_64 normalized_result.pe.machine_hex 0xfd1d normalized_result.pe.architecture x86_64 normalized_result.classification.status managed normalized_result.classification.managed_architecture x86_64 normalized_result.metadata.status complete ``` - Remaining proof gaps: no macOS, BSD or arm64 ReadyToRun binaries were run. Those cases are covered by fixtures only. Linux x64 was run against real files. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npm run test:focused --` over every `src/domain` and `src/dotnet` test, `tests/boundary/providers/managed/*`, `tests/acceptance/managedCliOutput.test.ts` and `tests/boundary/mcp/managedMcp.test.ts`: 147 files, 2,172 tests passed. - `npm run check:fast`: passed. - `npm run verify:test-discovery`: "Discovered all 882 test files exactly once." - Real files, Linux x64, built CLI: 30 randomly chosen `0xFD1D` assemblies all succeed with `inspect-managed-members`. The 40-file random sample now passes `inspect-artifact`, `inspect-managed-members` and `inspect-managed-native-boundaries` with no failures from this cause. ## Compatibility, safety, and release - Breaking changes or migration steps: none. - Real Hopper/Ghidra, browser, or OS coverage: Linux x64 (Fedora 44) only. No provider runs. - Package or release metadata impact: none. - Security, privacy, process, or containment review: parsing only. The new branch runs only after an existing plain-machine lookup fails, and it accepts nothing on native images. ## Review checklist - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 00:57:17 +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#4950
No description provided.