fix(pe): distinguish unsupported overlapping header layouts

This commit is contained in:
morluto committed 2026-10-24 04:14:17 +08:00
1 parent 77ebfb5f8a
commit d9d77726ff
8 files changed
+80 -9

No files matched your search

+7
View File
@@ -968,6 +968,13 @@ dossiers, instructions, recovered data types, measured load mappings, loaded
memory bytes, and observed file offsets. Independent load-image attestation
supports DOS MZ, explicitly selected COM, and explicit raw-binary imports;
PE returns measurements with an unsupported attestation status.
Target admission preserves recognized PE/NE/LE/LX signatures separately from
reader layout support. A Windows header overlapping the first 64 DOS-header
bytes remains an unsupported layout in this release; it is not reported as an
invalid offset. Tiny PE files with short optional headers have additional
unverified loader requirements. Unconfirmed nonzero DOS padding remains an
unresolved Windows-header candidate, with independently validated DOS facts in
the target error diagnostics; those facts do not prove a Windows header absent.
On Linux and macOS, `annotate_native_function` also edits a function name and/or
entry comments atomically and returns refreshed analysis, and `set_address_name`
and `set_addresses_names` name a function entry or any other mapped address
+10 -2
View File
@@ -45,6 +45,7 @@ import {
} from "../domain/rawBinary.js";
import type { BinaryArchitecture } from "../domain/binaryTargetTypes.js";
import {
mzWindowsHeaderLayoutLimitation,
observeMzWindowsHeader,
parseMzHeaderEvidence,
type DosMzHeader,
@@ -786,8 +787,15 @@ const readExecutableMetadata = async (
}
const { windowsHeader } = evidence;
if (windowsHeader.state === "declared") {
if (windowsHeader.offset < 64)
return err("invalid Windows new-header offset in MZ image");
const limitation = mzWindowsHeaderLayoutLimitation(windowsHeader);
if (limitation !== undefined)
return err({
reason: limitation,
diagnostics: {
unsupported_layout: true,
mz_windows_header: { ...windowsHeader },
},
});
return readPeMetadata(
handle,
windowsHeader.offset,
+4 -1
View File
@@ -11,6 +11,7 @@ import type {
import { err, ok, type Result } from "./result.js";
import { readElfFixedHeader } from "./elfFixedHeader.js";
import {
mzWindowsHeaderLayoutLimitation,
parseMzHeaderEvidence,
type MzWindowsHeaderSignatureEvidence,
} from "./dosMz.js";
@@ -453,7 +454,9 @@ const parseMz = (
return err("MZ Windows new-header candidate is unresolved");
if (windowsHeader.state === "declared") {
const { offset } = windowsHeader;
if (offset < 64 || offset > bytes.length - 24)
const limitation = mzWindowsHeaderLayoutLimitation(windowsHeader);
if (limitation !== undefined) return err(limitation);
if (offset > bytes.length - 24)
return err("invalid or truncated Windows executable header in MZ image");
if (["NE", "LE", "LX"].includes(windowsHeader.signature))
return err(
+14
View File
@@ -167,6 +167,20 @@ describe("DOS MZ load-module validation", () => {
});
describe("MZ Windows header observations", () => {
it("keeps a confirmed overlapping PE distinct from malformed header offsets", () => {
const bytes = pe(0x014c, 4);
expect(observeMzWindowsHeader(bytes)).toEqual({
state: "declared",
offset: 4,
signature: "PE",
});
expect(parseExecutableHeader(bytes, "x64")).toEqual({
ok: false,
error:
"Unsupported overlapping PE header at file offset 4; this reader requires the Windows header outside the 64-byte DOS header",
});
});
it.each(["NE", "LE", "LX"])(
"distinguishes unsupported %s from DOS",
(signature) => {
+8
View File
@@ -45,6 +45,14 @@ export interface MzWindowsHeaderSignatureEvidence {
readonly bytes: Buffer;
}
/** A reader's layout limitation does not invalidate a recognized Windows signature. */
export const mzWindowsHeaderLayoutLimitation = (
header: Extract<MzWindowsHeaderObservation, { state: "declared" }>,
): string | undefined =>
header.offset < 64
? `Unsupported overlapping ${header.signature} header at file offset ${String(header.offset)}; this reader requires the Windows header outside the 64-byte DOS header`
: undefined;
/**
* Observe the Windows new-header pointer without mistaking short DOS module
* bytes for e_lfanew. A nonzero pointer in a complete DOS header is only a
+13
View File
@@ -1,11 +1,24 @@
import { expect, it } from "vitest";
import { readGoBinaryImage } from "../../go/GoBinaryImage.js";
import { readPeImage } from "./PeImage.js";
import { pe } from "../../domain/binaryTarget.fixture.js";
import { BinaryFormatFailure } from "./BinaryContainer.js";
import {
createGoBinaryFixture,
setGoPeVirtualSize,
} from "../../../tests/fixtures/go/image.js";
it("classifies a confirmed overlapping PE as an unsupported layout", () => {
try {
readPeImage(pe(0x014c, 4));
throw new Error("Expected an unsupported PE layout");
} catch (cause) {
expect(cause).toBeInstanceOf(BinaryFormatFailure);
expect(cause).toMatchObject({ kind: "unsupported" });
expect((cause as Error).message).toContain("overlapping PE header");
}
});
it("keeps a DOS-valid but unsigned Windows-header candidate unresolved", () => {
const bytes = Buffer.alloc(128);
bytes.write("MZ", 0, "ascii");
+7 -6
View File
@@ -1,5 +1,8 @@
import { readPeSectionHeader } from "./NativeSectionDirectory.js";
import { parseMzHeaderEvidence } from "../../domain/dosMz.js";
import {
mzWindowsHeaderLayoutLimitation,
parseMzHeaderEvidence,
} from "../../domain/dosMz.js";
import {
declaredFileRange,
BinaryFormatFailure,
@@ -27,11 +30,9 @@ const peHeaderOffset = (reader: BinaryReader): number => {
);
}
const { offset: pe } = windowsHeader;
if (pe < 64)
throw new BinaryFormatFailure(
"malformed",
"PE signature overlaps the DOS header",
);
const limitation = mzWindowsHeaderLayoutLimitation(windowsHeader);
if (limitation !== undefined)
throw new BinaryFormatFailure("unsupported", limitation);
reader.range(pe, 24, "PE signature and COFF header");
if (["NE", "LE", "LX"].includes(windowsHeader.signature))
throw new BinaryFormatFailure(
@@ -1139,6 +1139,23 @@ describe("binary Info.plist app bundle targets", () => {
});
describe("DOS binary target I/O", () => {
it("retains the declared PE signature when its overlapping layout is unsupported", async () => {
const directory = await createTestTempDirectory("rea-overlapping-pe-");
const path = join(directory, "overlap.exe");
await writeFile(path, pe(0x014c, 4));
const result = await parseBinaryTarget(path);
expect(result).toMatchObject({
ok: false,
error: {
reason: expect.stringContaining("Unsupported overlapping PE header"),
diagnostics: {
unsupported_layout: true,
mz_windows_header: { state: "declared", offset: 4, signature: "PE" },
},
},
});
});
it("retains valid DOS facts while refusing automatic selection for an unresolved pointer", async () => {
const directory = await createTestTempDirectory("rea-ambiguous-mz-target-");
const path = join(directory, "legacy.exe");