fix(formats): preserve high bits in Windows signature checks
Decode MZ and Windows signature bytes without ASCII high-bit masking. Move malformed signature cases into the shared Go diagnostics fixtures so provider, compiled CLI, and MCP workflows cover NE, LE, LX, and PE lookalikes; remove the superseded provider-only cases. Validation: runtime build, check:ci, and focused DOS/Go/target/CLI/MCP tests. Windows-only reference-import cases await CI.
This commit is contained in:
parent
fd9588089d
commit
ac57c88efe
|
|
@ -170,18 +170,26 @@ describe("MZ Windows header observations", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("keeps a complete non-Windows signature candidate as DOS", () => {
|
||||
const bytes = dosMz(64);
|
||||
const offset = 32;
|
||||
bytes.writeUInt32LE(offset, 60);
|
||||
bytes.write("PEZZ", offset, "ascii");
|
||||
it.each(["PEZZ", "NE", "LE", "LX", "PE"])(
|
||||
"keeps a non-Windows %s signature candidate as DOS",
|
||||
(signature) => {
|
||||
const bytes = dosMz(64);
|
||||
const offset = 32;
|
||||
bytes.writeUInt32LE(offset, 60);
|
||||
const candidate = Buffer.from(
|
||||
[...Buffer.from(signature)].map((byte) =>
|
||||
signature === "PEZZ" ? byte : byte | 0x80,
|
||||
),
|
||||
);
|
||||
bytes.set(candidate, offset);
|
||||
|
||||
expect(observeMzWindowsHeader(bytes)).toEqual({ state: "absent" });
|
||||
expect(parseExecutableHeader(bytes, "x64")).toMatchObject({
|
||||
ok: true,
|
||||
value: { format: "dos-mz", architecture: "x86" },
|
||||
});
|
||||
});
|
||||
expect(observeMzWindowsHeader(bytes)).toEqual({ state: "absent" });
|
||||
expect(parseExecutableHeader(bytes, "x64")).toMatchObject({
|
||||
ok: true,
|
||||
value: { format: "dos-mz", architecture: "x86" },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("accepts a short-header candidate only when it points at a Windows signature", () => {
|
||||
const stub = dosMz(64);
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ export const observeMzWindowsHeader = (
|
|||
const observedPePrefix =
|
||||
available !== undefined &&
|
||||
available.length >= 2 &&
|
||||
available.toString("ascii", 0, 2) === "PE";
|
||||
available.toString("latin1", 0, 2) === "PE";
|
||||
if (fileSize - offset < (observedPePrefix ? 4 : 2))
|
||||
return { state: "absent" };
|
||||
return { state: "signature-needed", offset };
|
||||
|
|
@ -72,7 +72,7 @@ const windowsHeaderSignature = (
|
|||
bytes: Buffer | undefined,
|
||||
): "PE" | "NE" | "LE" | "LX" | "other" | "unobserved" => {
|
||||
if (bytes === undefined || bytes.length < 2) return "unobserved";
|
||||
const prefix = bytes.toString("ascii", 0, 2);
|
||||
const prefix = bytes.toString("latin1", 0, 2);
|
||||
if (prefix === "NE" || prefix === "LE" || prefix === "LX") return prefix;
|
||||
if (prefix !== "PE") return "other";
|
||||
if (bytes.length < 4) return "unobserved";
|
||||
|
|
@ -91,7 +91,7 @@ export const parseDosMzHeader = (
|
|||
fileSize = bytes.length,
|
||||
signatureAtOffset?: Buffer,
|
||||
): Result<DosMzHeader, string> => {
|
||||
if (bytes.length < 28 || bytes.toString("ascii", 0, 2) !== "MZ")
|
||||
if (bytes.length < 28 || bytes.toString("latin1", 0, 2) !== "MZ")
|
||||
return err("invalid or truncated DOS MZ header");
|
||||
if (!Number.isSafeInteger(fileSize) || fileSize < bytes.length)
|
||||
return err("invalid DOS MZ file length");
|
||||
|
|
|
|||
|
|
@ -49,20 +49,6 @@ it.each([32, 64] as const)(
|
|||
},
|
||||
);
|
||||
|
||||
it.each([{ signature: [0xce, 0xc5] }, { signature: [0xcc, 0xd8] }])(
|
||||
"rejects high-bit lookalikes of Windows signatures %j",
|
||||
async ({ signature }) => {
|
||||
const root = await createTestTempDirectory("rea-go-pe-signature-");
|
||||
const fixture = createGoBinaryFixture({ format: "pe" });
|
||||
fixture.bytes.set(signature, 128);
|
||||
const path = join(root, "corrupt-signature.exe");
|
||||
await writeFile(path, fixture.bytes);
|
||||
const result = await new GoBinaryProvider().inspect({ path });
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) expect(result.error).toBeInstanceOf(AnalysisInputError);
|
||||
},
|
||||
);
|
||||
|
||||
it("preserves the actual build-info byte guard as a resource failure", async () => {
|
||||
const fixture = createGoBinaryFixture();
|
||||
fixture.bytes.set([0x81, 0x80, 0x40], fixture.headerOffset + 32);
|
||||
|
|
|
|||
|
|
@ -203,6 +203,14 @@ export const createGoBinaryDiagnosticFixtures = () => {
|
|||
}
|
||||
const damagedPe = createGoBinaryFixture({ format: "pe" }).bytes;
|
||||
damagedPe.write("NO", 128, "ascii");
|
||||
const highBitSignatures = ["NE", "LE", "LX", "PE"].map((signature) => {
|
||||
const bytes = createGoBinaryFixture({ format: "pe" }).bytes;
|
||||
bytes.set(
|
||||
[...Buffer.from(signature)].map((byte) => byte | 0x80),
|
||||
128,
|
||||
);
|
||||
return { name: `high-bit-${signature.toLowerCase()}`, bytes };
|
||||
});
|
||||
const absentDirectories = createGoBinaryFixture({ format: "pe" }).bytes;
|
||||
absentDirectories.writeUInt32LE(1, 152 + 108);
|
||||
const shortOptionalHeaders = [0, 1].map((size) => {
|
||||
|
|
@ -220,6 +228,7 @@ export const createGoBinaryDiagnosticFixtures = () => {
|
|||
})),
|
||||
...[
|
||||
{ name: "damaged-pe", bytes: damagedPe },
|
||||
...highBitSignatures,
|
||||
{ name: "absent-directories", bytes: absentDirectories },
|
||||
...shortOptionalHeaders,
|
||||
].map((fixture) => ({
|
||||
|
|
|
|||
Loading…
Reference in New Issue