fix(pe): classify valid DOS targets as unsupported (#7530)
fix(pe): classify confirmed DOS targets as unsupported Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
This commit is contained in:
1 parent
6fceda83d5
commit
21ad5b1f67
5 files changed
+115
-2
No files matched your search
@@ -1,3 +1,4 @@
|
||||
import { BinaryFormatFailure } from "../metadata/BinaryContainer.js";
|
||||
import {
|
||||
projectStableArtifactFailure,
|
||||
stableArtifactInputRecovery,
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisResourceConstraintError,
|
||||
AnalysisUnsupportedTargetError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import {
|
||||
MAX_INSPECT_PE_CERTIFICATE_ENTRIES,
|
||||
@@ -123,6 +125,14 @@ const peCertificatesFailurePrimary = (
|
||||
input: InspectPeCertificatesInput,
|
||||
) => {
|
||||
const { cause, origin } = facts;
|
||||
if (cause instanceof BinaryFormatFailure && cause.kind === "unsupported")
|
||||
return new AnalysisUnsupportedTargetError(
|
||||
OPERATION,
|
||||
input.path,
|
||||
cause.message,
|
||||
{ cause },
|
||||
);
|
||||
|
||||
if (
|
||||
origin === "owned-or-classified" &&
|
||||
cause instanceof ArtifactReaderFailure &&
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
readPeResourceEntryFacts,
|
||||
readPeResourceNameFacts,
|
||||
} from "./PeResourceTreeFacts.js";
|
||||
import { observeMzWindowsHeader } from "../../domain/dosMz.js";
|
||||
import { parseMzHeaderEvidence } from "../../domain/dosMz.js";
|
||||
import { BinaryFormatFailure } from "../metadata/BinaryContainer.js";
|
||||
import { readPeCoffHeader } from "../../domain/peCoffHeader.js";
|
||||
import { readPeSectionHeader } from "../../domain/peSectionHeader.js";
|
||||
import { ArtifactReaderFailure } from "../../artifacts/ArtifactReader.js";
|
||||
@@ -91,7 +92,13 @@ const intersectsResource = (
|
||||
export const readPeHeaders = (bytes: Buffer, fileSize = bytes.length) => {
|
||||
requirePeRange(bytes, 0, 64);
|
||||
if (bytes.readUInt16LE(0) !== 0x5a4d) peFailure("Expected an MZ PE image.");
|
||||
const observed = observeMzWindowsHeader(bytes, fileSize);
|
||||
const evidence = parseMzHeaderEvidence(bytes, fileSize);
|
||||
const observed = evidence.windowsHeader;
|
||||
if (observed.state === "absent" && evidence.dos.ok)
|
||||
throw new BinaryFormatFailure(
|
||||
"unsupported",
|
||||
"DOS MZ images do not contain PE resource or certificate tables",
|
||||
);
|
||||
if (observed.state === "candidate" || observed.state === "signature-needed")
|
||||
requirePeRange(bytes, observed.offset, 24);
|
||||
if (observed.state !== "declared" || observed.signature !== "PE")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BinaryFormatFailure } from "../metadata/BinaryContainer.js";
|
||||
import {
|
||||
projectStableArtifactFailure,
|
||||
stableArtifactInputRecovery,
|
||||
@@ -22,6 +23,7 @@ import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisResourceConstraintError,
|
||||
AnalysisUnsupportedTargetError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import {
|
||||
MAX_INSPECT_PE_DIRECTORY_ENTRIES,
|
||||
@@ -104,6 +106,14 @@ const peResourcesFailurePrimary = (
|
||||
) => {
|
||||
const { cause, origin } = facts;
|
||||
const operation = "inspect_pe_resources";
|
||||
if (cause instanceof BinaryFormatFailure && cause.kind === "unsupported")
|
||||
return new AnalysisUnsupportedTargetError(
|
||||
operation,
|
||||
input.path,
|
||||
cause.message,
|
||||
{ cause },
|
||||
);
|
||||
|
||||
if (
|
||||
origin === "owned-or-classified" &&
|
||||
cause instanceof ArtifactReaderFailure &&
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect, it } from "vitest";
|
||||
import { dosMz } from "../../domain/binaryTarget.fixture.js";
|
||||
import { inspectPeResourcesInputSchema } from "../../domain/native/peResources.js";
|
||||
import { inspectPeCertificatesInputSchema } from "../../domain/native/peCertificates.js";
|
||||
import { projectAnalysisError } from "../../domain/analysisErrorProjection.js";
|
||||
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
|
||||
import { PeResourcesProvider } from "./PeResourcesProvider.js";
|
||||
import { PeCertificatesProvider } from "./PeCertificatesProvider.js";
|
||||
|
||||
it.each(["resources", "certificates"] as const)(
|
||||
"classifies a valid DOS target as unsupported for PE %s",
|
||||
async (operation) => {
|
||||
const root = await createTestTempDirectory("rea-pe-dos-classification-");
|
||||
const path = join(root, "program.exe");
|
||||
const bytes = dosMz(1024);
|
||||
await writeFile(path, bytes);
|
||||
const provider =
|
||||
operation === "resources"
|
||||
? new PeResourcesProvider()
|
||||
: new PeCertificatesProvider();
|
||||
const input =
|
||||
operation === "resources"
|
||||
? inspectPeResourcesInputSchema.parse({ path })
|
||||
: inspectPeCertificatesInputSchema.parse({ path });
|
||||
try {
|
||||
const result = await provider.inspect(input);
|
||||
if (result.ok) throw new Error("Expected unsupported target");
|
||||
expect(projectAnalysisError(result.error)).toMatchObject({
|
||||
code: "unsupported_target",
|
||||
category: "unsupported_target",
|
||||
});
|
||||
expect(Buffer.compare(await readFile(path), bytes)).toBe(0);
|
||||
await writeFile(path, Buffer.from("MZ"));
|
||||
const malformed = await provider.inspect(input);
|
||||
if (malformed.ok) throw new Error("Expected malformed input");
|
||||
expect(projectAnalysisError(malformed.error)).toMatchObject({
|
||||
code: "invalid_request",
|
||||
category: "invalid_input",
|
||||
});
|
||||
} finally {
|
||||
await provider.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,40 @@
|
||||
import { readFile, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { expect } from "vitest";
|
||||
import { dosMz } from "../../../src/domain/binaryTarget.fixture.js";
|
||||
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest.for(["CLI", "stdio MCP"] as const)(
|
||||
"reports DOS as unsupported for PE resources and certificates through %s",
|
||||
async (surface, { cli }) => {
|
||||
const root = await createTestTempDirectory("rea-pe-dos-public-");
|
||||
const path = join(root, "dos.exe");
|
||||
const bytes = dosMz(1024);
|
||||
await writeFile(path, bytes);
|
||||
const mcp =
|
||||
surface === "stdio MCP" ? await connectLocalToolsMcp() : undefined;
|
||||
for (const operation of ["resources", "certificates"] as const) {
|
||||
let error: unknown;
|
||||
if (surface === "CLI") {
|
||||
const response = await cli.run({
|
||||
arguments: [`inspect-pe-${operation}`, path, "--json"],
|
||||
});
|
||||
expect(response.exitCode).toBe(1);
|
||||
error = response.json;
|
||||
} else {
|
||||
const response = await mcp!.call(`inspect_pe_${operation}`, { path });
|
||||
expect(response.isError).toBe(true);
|
||||
error = parseMcpToolError(response).error;
|
||||
}
|
||||
expect(error).toMatchObject({
|
||||
code: "unsupported_target",
|
||||
category: "unsupported_target",
|
||||
});
|
||||
expect(JSON.stringify(error)).not.toContain("inspect_input_contract");
|
||||
expect(Buffer.compare(await readFile(path), bytes)).toBe(0);
|
||||
}
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user