mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
fix(pe): distinguish unsupported overlapping header layouts
This commit is contained in:
1 parent
77ebfb5f8a
commit
d9d77726ff
8 files changed
+80
-9
No files matched your search
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user