[GH-ISSUE #4455] [Bug] PE resource mapping treats zero-extent sections as overlapping byte ranges #1767

Closed
opened 2026-10-16 21:10:01 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

  • Searched open and closed issues and PRs for empty/zero sections and PE resource mapping. Related #4373 concerns an empty payload colliding with a nonempty section; this report concerns a nonempty payload colliding with an empty section.
  • No credentials or private targets are included.

Area

Native analysis or decompilation

Observed behavior

On main fe2a398dd, adding a section with both VirtualSize and SizeOfRawData equal to zero inside an otherwise accepted resource interval makes the compiled inspect-pe-resources CLI reject the image with invalid_request / invalid_format: Resource interval intersects overlapping virtual sections. The zero-extent section contributes no bytes to that interval.

An executed compiled-parser probe also rejects an empty section inside a header-backed payload as Ambiguous resource header RVA mapping. Controls without the empty section complete successfully. This is static format inspection; no PE target was executed or loaded by Windows.

Reproduction

Build the checkout with its pinned Node 24.18.0 toolchain and npm run build:cached. From the checkout root, generate a disposable image using the existing independent byte fixture:

// node --input-type=module
import { mkdtemp, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { peResourceFixture } from './src/native/pe/PeResources.fixture.ts';
const f = peResourceFixture(false, [
  { type: 10, name: 1, language: 1033, payload: Buffer.from('DATA') },
]);
f.bytes.writeUInt16LE(2, 0x86);
const section = f.sectionAt + 40;
f.bytes.writeUInt32LE(0, section + 8);  // VirtualSize
f.bytes.writeUInt32LE(0x2001, section + 12);
f.bytes.writeUInt32LE(0, section + 16); // SizeOfRawData
const path = join(await mkdtemp(join(tmpdir(), 'rea-empty-section-')), 'image.exe');
await writeFile(path, f.bytes);
console.log(path);

Run node scripts/rea.mjs inspect-pe-resources <printed-absolute-path> --json. The compiled CLI exits 1 with the error above. Executed once on macOS arm64 with Node 24.18.0; the compiled-parser controls also ran once each.

Expected behavior

A section with zero declared virtual extent and zero file bytes should not introduce an overlapping byte range. Preserve rejection of actual overlapping positive extents and the existing empty-resource ownership rules. The mapper already computes this section's extent as zero and excludes it from point ownership, so its interval-overlap check should agree with its own parsed facts.

Impact and scope

Observed: static resource inspection is refused for the generated input, including through the compiled CLI. The same overlap predicate also rejects the header-backed control at the compiled-parser boundary. Real-image frequency, Windows loader acceptance, and MCP reproduction are NOT_ENOUGH_INFO; none were measured. No incorrect byte read or target execution is claimed.

Relevant output or evidence

{"kind":"normal","status":"complete","payload":{"offset":1112,"bytes":4}}
{"kind":"empty-section-in-resources","status":"rejected","reason":"Resource interval intersects overlapping virtual sections."}
{"kind":"empty-section-in-header","status":"rejected","reason":"Ambiguous resource header RVA mapping."}

Source-derived cause: both overlap predicates in PeResourceLayout.ts use strict endpoint inequalities without checking that the other section has a positive extent. An empty interval inside the requested range therefore satisfies both inequalities. Normalize interval intersection at this owner and reuse it for header and section checks.

Typed outcome or recovery expected

The empty-section controls should complete with unchanged resource locations, bytes, and digests. Positive-extent overlap controls must remain invalid_format. This issue is a sibling of #4373 and can share the same ownership-boundary correction.

Originally created by @morluto on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4455 ### Before submitting - [x] Searched open and closed issues and PRs for empty/zero sections and PE resource mapping. Related #4373 concerns an empty payload colliding with a nonempty section; this report concerns a nonempty payload colliding with an empty section. - [x] No credentials or private targets are included. ### Area Native analysis or decompilation ### Observed behavior On main `fe2a398dd`, adding a section with both `VirtualSize` and `SizeOfRawData` equal to zero inside an otherwise accepted resource interval makes the compiled `inspect-pe-resources` CLI reject the image with `invalid_request` / `invalid_format`: `Resource interval intersects overlapping virtual sections.` The zero-extent section contributes no bytes to that interval. An executed compiled-parser probe also rejects an empty section inside a header-backed payload as `Ambiguous resource header RVA mapping.` Controls without the empty section complete successfully. This is static format inspection; no PE target was executed or loaded by Windows. ### Reproduction Build the checkout with its pinned Node 24.18.0 toolchain and `npm run build:cached`. From the checkout root, generate a disposable image using the existing independent byte fixture: ```js // node --input-type=module import { mkdtemp, writeFile } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { peResourceFixture } from './src/native/pe/PeResources.fixture.ts'; const f = peResourceFixture(false, [ { type: 10, name: 1, language: 1033, payload: Buffer.from('DATA') }, ]); f.bytes.writeUInt16LE(2, 0x86); const section = f.sectionAt + 40; f.bytes.writeUInt32LE(0, section + 8); // VirtualSize f.bytes.writeUInt32LE(0x2001, section + 12); f.bytes.writeUInt32LE(0, section + 16); // SizeOfRawData const path = join(await mkdtemp(join(tmpdir(), 'rea-empty-section-')), 'image.exe'); await writeFile(path, f.bytes); console.log(path); ``` Run `node scripts/rea.mjs inspect-pe-resources <printed-absolute-path> --json`. The compiled CLI exits 1 with the error above. Executed once on macOS arm64 with Node 24.18.0; the compiled-parser controls also ran once each. ### Expected behavior A section with zero declared virtual extent and zero file bytes should not introduce an overlapping byte range. Preserve rejection of actual overlapping positive extents and the existing empty-resource ownership rules. The mapper already computes this section's extent as zero and excludes it from point ownership, so its interval-overlap check should agree with its own parsed facts. ### Impact and scope Observed: static resource inspection is refused for the generated input, including through the compiled CLI. The same overlap predicate also rejects the header-backed control at the compiled-parser boundary. Real-image frequency, Windows loader acceptance, and MCP reproduction are NOT_ENOUGH_INFO; none were measured. No incorrect byte read or target execution is claimed. ### Relevant output or evidence ```json {"kind":"normal","status":"complete","payload":{"offset":1112,"bytes":4}} {"kind":"empty-section-in-resources","status":"rejected","reason":"Resource interval intersects overlapping virtual sections."} {"kind":"empty-section-in-header","status":"rejected","reason":"Ambiguous resource header RVA mapping."} ``` Source-derived cause: both overlap predicates in [PeResourceLayout.ts](https://github.com/morluto/rea/blob/fe2a398dd/src/native/pe/PeResourceLayout.ts) use strict endpoint inequalities without checking that the other section has a positive extent. An empty interval inside the requested range therefore satisfies both inequalities. Normalize interval intersection at this owner and reuse it for header and section checks. ### Typed outcome or recovery expected The empty-section controls should complete with unchanged resource locations, bytes, and digests. Positive-extent overlap controls must remain `invalid_format`. This issue is a sibling of #4373 and can share the same ownership-boundary correction.
bsakaguchi 2026-10-16 21:10:01 +00:00
  • closed this issue
  • added the
    triage
    bug
    labels
Author
Owner

@qorvr commented on GitHub (Oct 16, 2026):

Root Cause

In src/native/pe/PeResourceLayout.ts:

section.rva < rva + size && section.rva + virtualExtent(section) > rva

When a PE binary contains a zero-extent section (where virtualExtent(section) === 0, such as empty alignment padding sections or unallocated sections), any non-empty resource interval [rva, rva + size) spanning past section.rva satisfied section.rva < rva + size and section.rva + 0 > rva as long as section.rva > rva.

This falsely triggered:

  1. peFailure("Resource interval intersects overlapping virtual sections.") when a zero-extent section RVA was located inside another section's admitted resource interval.
  2. peFailure("Ambiguous resource header RVA mapping.") when a zero-extent section RVA was inside the DOS/NT header span.

Expected Behavior

A zero-extent section occupies no virtual span (extent === 0) and cannot overlap or conflict with other virtual sections or header intervals. Interval overlap requires both intervals to have positive extent:

const intervalsOverlap = (
  startA: number,
  lenA: number,
  startB: number,
  lenB: number,
): boolean =>
  lenA > 0 && lenB > 0 && startA < startB + lenB && startB < startA + lenA;

Verification & Regression Tests

Added regression tests in src/native/pe/PeResourceParser.test.ts:

  1. does not treat a zero-extent section inside a resource interval as overlapping: ensures valid resources are parsed when an empty section's RVA sits inside the resource directory interval.
  2. does not treat a zero-extent section inside header mapping as ambiguous: ensures header-backed resources are resolved when an empty section's RVA falls inside the header span.

Before the fix, both test cases failed with Resource interval intersects overlapping virtual sections. and Ambiguous resource header RVA mapping.
With the fix, all 30 tests in src/native/pe/PeResourceParser.test.ts pass cleanly.

Ready Branch

Branch: qorvr:fix/pe-resource-layout-zero-extent-sections (commit 3e82f4468)
Formatted with oxfmt and fully validated with npm run check:fast against main (fe2a398dd).

(Standing by to open a PR as soon as our current 3 open PR limit allows, or maintainers may cherry-pick directly from the branch above.)

<!-- gh-comment-id:6215269255 --> @qorvr commented on GitHub (Oct 16, 2026): ### Root Cause In `src/native/pe/PeResourceLayout.ts`: ```typescript section.rva < rva + size && section.rva + virtualExtent(section) > rva ``` When a PE binary contains a zero-extent section (where `virtualExtent(section) === 0`, such as empty alignment padding sections or unallocated sections), any non-empty resource interval `[rva, rva + size)` spanning past `section.rva` satisfied `section.rva < rva + size` and `section.rva + 0 > rva` as long as `section.rva > rva`. This falsely triggered: 1. `peFailure("Resource interval intersects overlapping virtual sections.")` when a zero-extent section RVA was located inside another section's admitted resource interval. 2. `peFailure("Ambiguous resource header RVA mapping.")` when a zero-extent section RVA was inside the DOS/NT header span. ### Expected Behavior A zero-extent section occupies no virtual span (`extent === 0`) and cannot overlap or conflict with other virtual sections or header intervals. Interval overlap requires both intervals to have positive extent: ```typescript const intervalsOverlap = ( startA: number, lenA: number, startB: number, lenB: number, ): boolean => lenA > 0 && lenB > 0 && startA < startB + lenB && startB < startA + lenA; ``` ### Verification & Regression Tests Added regression tests in `src/native/pe/PeResourceParser.test.ts`: 1. `does not treat a zero-extent section inside a resource interval as overlapping`: ensures valid resources are parsed when an empty section's RVA sits inside the resource directory interval. 2. `does not treat a zero-extent section inside header mapping as ambiguous`: ensures header-backed resources are resolved when an empty section's RVA falls inside the header span. Before the fix, both test cases failed with `Resource interval intersects overlapping virtual sections.` and `Ambiguous resource header RVA mapping.` With the fix, all 30 tests in `src/native/pe/PeResourceParser.test.ts` pass cleanly. ### Ready Branch Branch: [`qorvr:fix/pe-resource-layout-zero-extent-sections`](https://github.com/qorvr/rea/tree/fix/pe-resource-layout-zero-extent-sections) (commit `3e82f4468`) Formatted with `oxfmt` and fully validated with `npm run check:fast` against `main` (`fe2a398dd`). (Standing by to open a PR as soon as our current 3 open PR limit allows, or maintainers may cherry-pick directly from the branch above.)
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#1767
No description provided.