Skip to content

PE: Resolve the machine type of a ReadyToRun image built for another system - #757

Open
zardus wants to merge 1 commit into
masterfrom
feature/fix-cle-readytorun-machine
Open

PE: Resolve the machine type of a ReadyToRun image built for another system#757
zardus wants to merge 1 commit into
masterfrom
feature/fix-cle-readytorun-machine

Conversation

@zardus

@zardus zardus commented Aug 17, 2026

Copy link
Copy Markdown
Member

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

A .NET ReadyToRun assembly published for a target other than Windows cannot be
loaded at all: arch_from_id raises ArchNotFound for machine type 0xfd1d.
The ReadyToRun compiler exclusive-ors the COFF machine type with a constant
naming the target operating system so the Windows loader refuses a file that is
not for it, and 0xfd1d is IMAGE_FILE_MACHINE_AMD64 ^ 0x7b79.

The backend now recognises those constants, which the .NET runtime defines as
IMAGE_FILE_MACHINE_NATIVE_OS_OVERRIDE in src/coreclr/inc/pedecoder.h. It
tries them only when the raw machine type is unknown and the image really is
ReadyToRun, so a corrupt Machine field elsewhere cannot be rescued by an
exclusive-or that happens to land somewhere valid. os stays "windows".

Fixes #751. Needs angr/binaries#183 for the fixture. Validation: #757 (comment)

@zardus

zardus commented Aug 17, 2026

Copy link
Copy Markdown
Member Author

THIS MESSAGE WAS GENERATED BY AN AUTOMATED PROCESS

Validation record for head 5eacf2dfc9415f2ef1d4d660e5d884f05e87c677 against baseline 45c6509c753d07f740099035cd41f7f473dc6f31.

  • Regression: pytest tests/test_pe.py::TestPEBackend::test_readytorun_machine_os_override — fails against baseline cle with archinfo.arch.ArchNotFound: Can't find architecture info for architecture 0xfd1d, passes on head
  • Focused: pytest tests/ in cle — 226 passed, 9 skipped on head; 225 passed, 9 skipped on baseline
  • Lint/type: ruff check, ruff format --check, and run-ci-diff-checks.py --repository cle (the merge-base pylint and pyright comparison the hosted jobs apply) — all changed files improve or hold
  • Workspace gate: run-all-tests.sh --jobs 2 in an ANGR_FEATURE shell with cle, binaries and angr adopted — all selected suites passed: workspace checks, the test-inputs fixture check, every configured pre-commit hook, the per-feature instancing suite, cle 235 passed 9 skipped, angr 2,471 passed 46 skipped 2 xfailed 260 subtests, angr Rust 35 passed. archinfo, pypcode, pyvex, claripy and angr-management are skipped as unadopted and untouched. A first run at the default four workers lost tests/procedures/libc/test_strtol.py to SIGKILL; memory.events recorded exactly one new oom_kill, and the test passes alone in 140s, so the run was repeated at two workers

Corpus A/B, 1,557 PE objects, one process per object:

Baseline Head
objects with machine 0xfd1d 37 37
of those, loading 0 37
ArchNotFound over the whole population 47 10
objects whose loaded memory, relocations, symbols, entry point or hints changed 37
  • Every difference: the 37 objects that previously raised ArchNotFound now load as AMD64. Nothing else in the population changes; the 1,520 objects whose machine type pefile resolves take the unchanged branch and are byte-identical.
  • All 37 carry a 72-byte IMAGE_COR20_HEADER whose ManagedNativeHeader points at RTR\0, and all 37 use the Linux override 0x7b79. No other object in the population reaches the new code.
  • The 10 remaining ArchNotFound are LOONGARCH64 (0x6264), which pefile resolves and archinfo has no architecture for. Unchanged, and not something this change can address.
  • Once loaded, those 37 images contribute 138,411 base relocations, which PE: Build base relocations on every architecture the backend resolves #755 builds and applies; this change only resolves the architecture.
  • What this does not buy: CFGFast still recovers nothing from these images with its defaults, because a ReadyToRun assembly has no entry point, no symbols and no exception directory to seed from, and a forced complete scan mostly decodes the ReadyToRun metadata rather than the native code. Indexing the native methods needs the RuntimeFunctions section of the ReadyToRun header, which nothing here reads. The claim is that the file opens and resolves AMD64, not that analysis of it works.

Reproduction without the corpus:

$ dotnet new classlib -o readytorun --framework net9.0 && cd readytorun
$ dotnet publish -c Release -r linux-x64 --self-contained false -p:PublishReadyToRun=true
$ python -c 'import cle; cle.Loader("bin/Release/net9.0/linux-x64/publish/readytorun.dll")'

Caveats: the corpus is a private dataset, so objects are named by machine type and count rather than by path; the fixture in angr/binaries#183 is built by exactly the recipe above with .NET SDK 9.0.316. Only the Linux override is exercised by real images here; the other five constants come from the .NET runtime's pedecoder.h. PE.__init__ still sets os = "windows" for these images.

CI on this PR: every job that resolves the referenced sibling pull request is
green — ci / Build, ci / Lint, ci / Typecheck, ci / Decompiler Snapshot Testing and all ten ci / Test shards. Test macos-15 fails with
1 failed, 225 passed, 9 skipped and Test windows-2022 is cancelled with it,
because cle's own matrix job checks out angr/binaries with no ref: and so
gets master, which does not yet carry tests/x86_64/readytorun_linux_x64.dll.
The failure is FileNotFoundError naming that path; the Windows job reports
cancelled rather than failure because the matrix does not set
fail-fast: false. Both clear when angr/binaries#183 merges, or when #738
teaches that matrix the sibling resolution the ci / * jobs already do. The
local workspace gate cannot see this: it runs against the adopted binaries
worktree, which has the fixtures.

@angr-bot

Copy link
Copy Markdown
Member

Corpus decompilation diffs can be found at angr/dec-snapshots@master...angr/cle_757

…system

A .NET ReadyToRun assembly published for a target other than Windows could not be
loaded at all: arch_from_id raised ArchNotFound for machine type 0xfd1d. The
ReadyToRun compiler exclusive-ors the COFF machine type with a constant naming the
target operating system so that the Windows loader refuses a file that is not for
it, and 0xfd1d is IMAGE_FILE_MACHINE_AMD64 ^ 0x7b79.

Recognise those constants, which the .NET runtime defines as
IMAGE_FILE_MACHINE_NATIVE_OS_OVERRIDE in src/coreclr/inc/pedecoder.h, when the raw
machine type is unknown and the image really is ReadyToRun.
@zardus
zardus force-pushed the feature/fix-cle-readytorun-machine branch from 5eacf2d to ecae05b Compare August 18, 2026 17:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A .NET ReadyToRun image built for a non-Windows target fails to load: its Machine field carries an OS override

2 participants