[PR #6165] fix(domain): reject ELF files whose program header table runs past the end #7178

Open
opened 2026-11-01 21:45:56 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/6165
Author: @Nitin13-git
Created: 10/21/2026
Status: 🔄 Open

Base: main ← Head: fix/elf-program-header-table-admission


📝 Commits (2)

  • 31c8c55 fix(domain): reject ELF files whose program header table runs past the end
  • 2b1e04c fix(domain): reject program header entries narrower than a program header

📊 Changes

3 files changed (+144 additions, -1 deletions)

View changed files

➕ src/domain/binaryTarget.elfProgramHeaders.test.ts (+92 -0)
📝 src/domain/binaryTarget.ts (+10 -1)
📝 src/domain/elfFixedHeader.ts (+42 -0)

📄 Description

Summary

Refs #6161. An ELF cut inside its program header table, or declaring a table offset beyond the file, was admitted and then failed in the provider as a generic execution_failure ("Run rea doctor..."). Admission now refuses it as target_unavailable, the way truncated Mach-O load commands already are. This is a partial step for #6161, not a full fix (see Change and scope).

Problem and expected behavior

parseElf (src/domain/binaryTarget.ts) used only the fixed header from readElfFixedHeader, so nothing compared e_phoff, e_phentsize and e_phnum with the file size. Ghidra then failed to import (for example "No memory blocks were defined" or "Invalid index: -256") and REA reported an environment failure. Expected: a file whose program header table cannot be read is reported as an unsupported target with the declared and actual sizes.

Change and scope

  • src/domain/elfFixedHeader.ts: new checkElfProgramHeaderTable compares the declared table extent (offset plus entry size times count, computed with BigInt) with the containing file size, for ELF32 and ELF64 in both byte orders. The message names the declared entries, entry size, offset and file size.
  • An entry width below a program header (56 bytes for ELF64, 32 for ELF32) is refused before the extent is computed, so a zero or undersized width cannot make a table at the end of the file look in bounds.
  • src/domain/binaryTarget.ts: parseElf calls it after the architecture check, so the existing precedence for an unsupported architecture is unchanged.
  • An absent table (e_phnum 0) and PN_XNUM (0xffff, where the real count lives in a section header a prefix does not show) are not judged.
  • Not included, and left open on purpose: truncation of the ELF body. A cut at 2,000 or 8,000 bytes of a 26,936-byte ELF still has a program header table inside the file, so it is still admitted and analyzed with no truncation notice (the main subject of #6161). Whether to refuse such files or record a limitation is a maintainer decision (a hard refusal could block deliberately malformed samples), so I did not guess. Section headers are not checked for the same reason.
  • Scope decision: not applicable (tightens an existing admission check; no new capability).

Contract and boundary impact

  • Semantic owner and earliest changed stage: domain target admission (binaryTarget.ts, elfFixedHeader.ts).
  • Contribution scope fit and linked maintainer decision: not applicable; draft because #6161 asks a design question.
  • CLI and MCP/tool-catalog contract: none. Some previously admitted malformed files now return the existing target_unavailable code.
  • Provider, bridge, target-format, or platform compatibility: all ELF architectures (the check runs before the per-architecture branches).
  • Evidence, artifact, provenance, or reconstruction contract: unchanged for valid files.
  • Generated metadata or package impact: none.

Expected effects:

  • Read existing files or captured data.

Evidence and regression coverage

  • New src/domain/binaryTarget.elfProgramHeaders.test.ts (10 tests: ELF32/ELF64 in both byte orders with exact-boundary acceptance, the message text, a 64-bit offset beyond 2^63, entry widths below a program header, absent and PN_XNUM tables, and error precedence). With the source files restored to origin/main, 8 of the 10 fail; the other 2 (absent or PN_XNUM table not judged, unsupported architecture reported first) are guards that pass either way. The 37 existing tests in binaryTarget.test.ts pass in both cases.
  • False-positive check: the admission function was run on 1,003 real x86-64 ELF files (system programs, shared libraries, the JDK, Ghidra's decompiler) extracted from an Ubuntu 22.04 image, using a 4,096-byte prefix as the resolver does; all were admitted and the new check refused none.
  • Real files through the public CLI (macOS arm64, Node 24.18.0, Ghidra 12.1.4), ELF = /usr/bin/true from Ubuntu 22.04 (26,936 bytes, e_phnum 13): before, a 64-byte cut gave execution_failure; now 64, 120 and 500-byte cuts and a file with e_phoff 0xffffffffffffff00 give target_unavailable: truncated ELF program header table: the header declares 13 entries of 56 bytes at offset .... The whole file still analyzes (135 procedures). A cut at 2,000 and 8,000 bytes is still admitted (unchanged, see above).
  • Remaining proof gaps: only one real ELF, x86-64 and little-endian, was run end to end; other architectures and byte orders are covered by synthetic header tests only.

Validation performed

Run on macOS arm64, Node 24.18.0:

  • npm run test:local -- src/domain/binaryTarget.elfProgramHeaders.test.ts src/domain/binaryTarget.test.ts: 47 passed (10 + 37)
  • Related unit tests that build ELF bytes (GhidraProvider.behavior, GhidraInstallation, BinarySessionLiveness, binaryTarget.powerpc/riscv/mips, elfFixedHeader, dosMz, plus the two files above): 341 passed
  • npm run test:focused on tests/boundary/filesystem/{binaryTarget,mipsAbiFlags,mipsSharedLoad,desktopApplication,zipDetection}.test.ts, tests/boundary/go/goBinaryImage.test.ts, tests/boundary/rust/rustBinaryBudget.test.ts: 327 passed
  • npm run check:fast: exit 0
  • Not run: npm run check:pr, Windows or Linux hosts.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. A malformed ELF that was previously admitted and failed in the provider is now refused earlier with a specific reason.
  • Real Hopper/Ghidra, browser, or OS coverage: Ghidra 12.1.4 on macOS arm64 only, as above.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: not applicable; read-only header arithmetic with BigInt, no new file reads.

Author readiness

Human owner:

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • Scope/authority and applicable lifecycle verification resolved.
  • Latest-head CI and actionable review findings assessed.

🔄 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/6165 **Author:** [@Nitin13-git](https://github.com/Nitin13-git) **Created:** 10/21/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `fix/elf-program-header-table-admission` --- ### 📝 Commits (2) - [`31c8c55`](https://github.com/morluto/rea/commit/31c8c556679ef30c4dddbaa9f1604364be1b78fd) fix(domain): reject ELF files whose program header table runs past the end - [`2b1e04c`](https://github.com/morluto/rea/commit/2b1e04cd74a4aa89e97f9b496c17271b1ed18d2e) fix(domain): reject program header entries narrower than a program header ### 📊 Changes **3 files changed** (+144 additions, -1 deletions) <details> <summary>View changed files</summary> ➕ `src/domain/binaryTarget.elfProgramHeaders.test.ts` (+92 -0) 📝 `src/domain/binaryTarget.ts` (+10 -1) 📝 `src/domain/elfFixedHeader.ts` (+42 -0) </details> ### 📄 Description ## Summary Refs #6161. An ELF cut inside its program header table, or declaring a table offset beyond the file, was admitted and then failed in the provider as a generic `execution_failure` ("Run `rea doctor`..."). Admission now refuses it as `target_unavailable`, the way truncated Mach-O load commands already are. This is a partial step for #6161, not a full fix (see Change and scope). ## Problem and expected behavior `parseElf` (`src/domain/binaryTarget.ts`) used only the fixed header from `readElfFixedHeader`, so nothing compared `e_phoff`, `e_phentsize` and `e_phnum` with the file size. Ghidra then failed to import (for example "No memory blocks were defined" or "Invalid index: -256") and REA reported an environment failure. Expected: a file whose program header table cannot be read is reported as an unsupported target with the declared and actual sizes. ## Change and scope - `src/domain/elfFixedHeader.ts`: new `checkElfProgramHeaderTable` compares the declared table extent (offset plus entry size times count, computed with `BigInt`) with the containing file size, for ELF32 and ELF64 in both byte orders. The message names the declared entries, entry size, offset and file size. - An entry width below a program header (56 bytes for ELF64, 32 for ELF32) is refused before the extent is computed, so a zero or undersized width cannot make a table at the end of the file look in bounds. - `src/domain/binaryTarget.ts`: `parseElf` calls it after the architecture check, so the existing precedence for an unsupported architecture is unchanged. - An absent table (`e_phnum` 0) and `PN_XNUM` (`0xffff`, where the real count lives in a section header a prefix does not show) are not judged. - Not included, and left open on purpose: truncation of the ELF body. A cut at 2,000 or 8,000 bytes of a 26,936-byte ELF still has a program header table inside the file, so it is still admitted and analyzed with no truncation notice (the main subject of #6161). Whether to refuse such files or record a limitation is a maintainer decision (a hard refusal could block deliberately malformed samples), so I did not guess. Section headers are not checked for the same reason. - Scope decision: not applicable (tightens an existing admission check; no new capability). ## Contract and boundary impact - Semantic owner and earliest changed stage: domain target admission (`binaryTarget.ts`, `elfFixedHeader.ts`). - Contribution scope fit and linked maintainer decision: not applicable; draft because #6161 asks a design question. - CLI and MCP/tool-catalog contract: none. Some previously admitted malformed files now return the existing `target_unavailable` code. - Provider, bridge, target-format, or platform compatibility: all ELF architectures (the check runs before the per-architecture branches). - Evidence, artifact, provenance, or reconstruction contract: unchanged for valid files. - Generated metadata or package impact: none. Expected effects: - [x] Read existing files or captured data. ## Evidence and regression coverage - New `src/domain/binaryTarget.elfProgramHeaders.test.ts` (10 tests: ELF32/ELF64 in both byte orders with exact-boundary acceptance, the message text, a 64-bit offset beyond `2^63`, entry widths below a program header, absent and `PN_XNUM` tables, and error precedence). With the source files restored to `origin/main`, 8 of the 10 fail; the other 2 (absent or `PN_XNUM` table not judged, unsupported architecture reported first) are guards that pass either way. The 37 existing tests in `binaryTarget.test.ts` pass in both cases. - False-positive check: the admission function was run on 1,003 real x86-64 ELF files (system programs, shared libraries, the JDK, Ghidra's decompiler) extracted from an Ubuntu 22.04 image, using a 4,096-byte prefix as the resolver does; all were admitted and the new check refused none. - Real files through the public CLI (macOS arm64, Node 24.18.0, Ghidra 12.1.4), ELF = `/usr/bin/true` from Ubuntu 22.04 (26,936 bytes, `e_phnum` 13): before, a 64-byte cut gave `execution_failure`; now 64, 120 and 500-byte cuts and a file with `e_phoff` 0xffffffffffffff00 give `target_unavailable: truncated ELF program header table: the header declares 13 entries of 56 bytes at offset ...`. The whole file still analyzes (135 procedures). A cut at 2,000 and 8,000 bytes is still admitted (unchanged, see above). - Remaining proof gaps: only one real ELF, x86-64 and little-endian, was run end to end; other architectures and byte orders are covered by synthetic header tests only. ## Validation performed Run on macOS arm64, Node 24.18.0: - `npm run test:local -- src/domain/binaryTarget.elfProgramHeaders.test.ts src/domain/binaryTarget.test.ts`: 47 passed (10 + 37) - Related unit tests that build ELF bytes (`GhidraProvider.behavior`, `GhidraInstallation`, `BinarySessionLiveness`, `binaryTarget.powerpc/riscv/mips`, `elfFixedHeader`, `dosMz`, plus the two files above): 341 passed - `npm run test:focused` on `tests/boundary/filesystem/{binaryTarget,mipsAbiFlags,mipsSharedLoad,desktopApplication,zipDetection}.test.ts`, `tests/boundary/go/goBinaryImage.test.ts`, `tests/boundary/rust/rustBinaryBudget.test.ts`: 327 passed - `npm run check:fast`: exit 0 - Not run: `npm run check:pr`, Windows or Linux hosts. ## Compatibility, safety, and release - Breaking changes or migration steps: none. A malformed ELF that was previously admitted and failed in the provider is now refused earlier with a specific reason. - Real Hopper/Ghidra, browser, or OS coverage: Ghidra 12.1.4 on macOS arm64 only, as above. - Package or release metadata impact: none. - Security, privacy, process, or containment review: not applicable; read-only header arithmetic with `BigInt`, no new file reads. ## Author readiness Human owner: - [ ] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [ ] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [ ] I declare this PR ready for initial maintainer review. ## Before merge - [ ] Scope/authority and applicable lifecycle verification resolved. - [ ] Latest-head CI and actionable review findings assessed. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
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#7178
No description provided.