Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
beaf4040bc | ||
|
|
7d97187ec4 |
No files matched your search
@@ -210,7 +210,8 @@ and language. Other resource payload formats remain opaque.
|
||||
`max_file_bytes` defaults to 64 MiB (maximum 512 MiB); `max_entries` defaults to
|
||||
16384, the largest stock Windows resource image measured with headroom
|
||||
(maximum 65536). CLI options are `--max-file-bytes` and `--max-entries`.
|
||||
Metadata is bounded to 8 MiB; exceeding any budget fails without truncation.
|
||||
Metadata is bounded to a fixed 8 MiB, reported as `max_metadata_bytes`;
|
||||
exceeding any budget fails without truncation.
|
||||
Large Evidence envelopes may require a caller-configured MCP transport receive
|
||||
buffer because envelope copies add overhead. Requests honor cancellation during
|
||||
snapshot reading, directory traversal and payload hashing. The operation reads
|
||||
|
||||
@@ -42,6 +42,13 @@ const managedSnapshotCapacityError = (
|
||||
"memory",
|
||||
"Managed static inspection requires an eagerly retained PE snapshot within its input byte budget.",
|
||||
{ max_snapshot_bytes: MAX_MANAGED_SNAPSHOT_BYTES },
|
||||
{
|
||||
// The snapshot budget is fixed, so neither waiting nor more worker or
|
||||
// host memory admits the same input.
|
||||
retryable: false,
|
||||
remediationAction:
|
||||
"max_snapshot_bytes is a fixed managed-inspection snapshot budget; worker limits and available host memory do not raise it. Select a complete managed assembly within the budget; retrying the unchanged artifact will fail again.",
|
||||
},
|
||||
);
|
||||
|
||||
export const readManagedSnapshot = async (
|
||||
|
||||
@@ -296,6 +296,13 @@ const collectFlutterLibraries = async (
|
||||
max_flutter_abis: MAX_FLUTTER_ABIS,
|
||||
max_library_path_bytes: MAX_FLUTTER_LIBRARY_PATH_BYTES,
|
||||
},
|
||||
{
|
||||
// Both inventory bounds are fixed; neither waiting nor more worker
|
||||
// or host memory admits the same package.
|
||||
retryable: false,
|
||||
remediationAction:
|
||||
"max_flutter_abis and max_library_path_bytes are fixed Flutter library inventory budgets; worker limits and available host memory do not raise them. Select a complete APK within those budgets, such as a per-ABI split APK; retrying the unchanged package will fail again.",
|
||||
},
|
||||
);
|
||||
libraries.push({
|
||||
entry,
|
||||
|
||||
@@ -62,7 +62,14 @@ const failureForPrimary = (cause: unknown, path: string): AnalysisError => {
|
||||
cause.boundary === "search" ? "cpu" : "memory",
|
||||
`Selected artifact ${path}: ${cause.message}`,
|
||||
{ boundary: cause.boundary, maximum_bytes: cause.maximum_bytes },
|
||||
{ cause },
|
||||
{
|
||||
cause,
|
||||
// Every boundary is a fixed parser budget with no caller override, so
|
||||
// neither waiting nor more worker or host memory admits the same file.
|
||||
retryable: false,
|
||||
remediationAction:
|
||||
"The reported boundary's maximum_bytes is a fixed Go build-info parser budget; worker limits and available host memory do not raise it. Select another complete Go binary whose metadata fits the budget; retrying the unchanged artifact will fail again.",
|
||||
},
|
||||
);
|
||||
if (cause instanceof GoBinaryFormatFailure)
|
||||
return cause.kind === "unsupported"
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { truncate, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it, onTestFinished } from "vitest";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import {
|
||||
NativeFixtureRunner,
|
||||
nativeMachoTarget,
|
||||
} from "../../tests/fixtures/nativeCommands.js";
|
||||
import { machoWithPropertyAttributes } from "../../tests/fixtures/objcPropertyMacho.js";
|
||||
import { objcRecordBudgetMacho } from "../../tests/fixtures/objcRecordBudgetMacho.js";
|
||||
import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js";
|
||||
import { decodeAppleDispatchMetadata } from "./AppleDispatchMetadata.js";
|
||||
import { NativeMacOSProvider } from "./NativeMacOSProvider.js";
|
||||
|
||||
const base = 0x100000000n;
|
||||
const fixture = (
|
||||
@@ -1375,3 +1384,34 @@ describe("malformed Objective-C protocol method lists", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Apple dispatch snapshot budget", () => {
|
||||
it("refuses a target above the fixed snapshot budget without worker memory advice", async () => {
|
||||
const root = await createTestTempDirectory("rea-dispatch-snapshot-");
|
||||
const path = join(root, "large.macho");
|
||||
await writeFile(path, Buffer.alloc(0));
|
||||
await truncate(path, 64 * 1024 * 1024 + 1);
|
||||
const client = new NativeMacOSProvider(
|
||||
{},
|
||||
new NativeFixtureRunner({}),
|
||||
"darwin",
|
||||
).createClient(nativeMachoTarget(path));
|
||||
onTestFinished(async () => {
|
||||
await client.close();
|
||||
});
|
||||
const result = await client.execute("inspect_native_dispatch_metadata", {});
|
||||
if (result.ok) throw new Error("Expected a snapshot budget refusal");
|
||||
const projected = projectAnalysisError(result.error);
|
||||
expect(projected).toMatchObject({
|
||||
code: "resource_constraint",
|
||||
retryable: false,
|
||||
details: {
|
||||
resource: "memory",
|
||||
reported_limits: { input_bytes: 64 * 1024 * 1024 },
|
||||
},
|
||||
});
|
||||
// The snapshot budget is fixed; worker or host memory cannot admit it.
|
||||
expect(projected.remediation.action).toContain("input_bytes");
|
||||
expect(projected.remediation.action).not.toMatch(/sufficient memory/iu);
|
||||
});
|
||||
});
|
||||
@@ -217,6 +217,13 @@ export const inspectAppleDispatchMetadata = async (
|
||||
"memory",
|
||||
"Apple static metadata input exceeds its 64 MiB snapshot budget",
|
||||
{ input_bytes: maximumBytes },
|
||||
{
|
||||
// The snapshot budget is fixed, so neither waiting nor more worker or
|
||||
// host memory admits the same target.
|
||||
retryable: false,
|
||||
remediationAction:
|
||||
"input_bytes is the fixed 64 MiB static dispatch-metadata snapshot budget; worker limits and available host memory do not raise it. Select a complete Mach-O within the budget; retrying the unchanged target will fail again.",
|
||||
},
|
||||
);
|
||||
const cancelled = () =>
|
||||
new AnalysisCancelledError("inspect_native_dispatch_metadata", {
|
||||
|
||||
@@ -19,7 +19,8 @@ type Range = { offset: number; bytes: number };
|
||||
type OccupiedRange = Range & {
|
||||
kind: "directory" | "name" | "data-entry" | "payload";
|
||||
};
|
||||
const METADATA_BYTES = 8 * 1024 * 1024;
|
||||
/** Fixed complete-result metadata budget; callers cannot raise it. */
|
||||
export const PE_RESOURCE_METADATA_BYTES = 8 * 1024 * 1024;
|
||||
const limit = (message: string, boundary: ArtifactReaderBoundary): never => {
|
||||
throw new ArtifactReaderFailure("limit", message, { boundary });
|
||||
};
|
||||
@@ -51,7 +52,7 @@ export const parsePeResources = async (
|
||||
let examined = 0;
|
||||
let metadataBytes = 0;
|
||||
const ensureCapacity = (projectedBytes: number): void => {
|
||||
if (metadataBytes + projectedBytes > METADATA_BYTES)
|
||||
if (metadataBytes + projectedBytes > PE_RESOURCE_METADATA_BYTES)
|
||||
limit(
|
||||
"PE resource metadata exceeds the 8 MiB complete-result allocation budget.",
|
||||
"memory",
|
||||
@@ -63,7 +64,9 @@ export const parsePeResources = async (
|
||||
};
|
||||
const account = (value: unknown): void => {
|
||||
// Rows are retained in arrays; reserve a separator along with each row.
|
||||
accountBytes(jsonBytes(value, METADATA_BYTES - metadataBytes - 1) + 1);
|
||||
accountBytes(
|
||||
jsonBytes(value, PE_RESOURCE_METADATA_BYTES - metadataBytes - 1) + 1,
|
||||
);
|
||||
};
|
||||
const reserveDecodedName = (utf16Units: number): void => {
|
||||
// A JSON string can need six ASCII bytes per UTF-16 code unit (for example,
|
||||
@@ -254,7 +257,9 @@ export const parsePeResources = async (
|
||||
],
|
||||
};
|
||||
assertArtifactReadActive(signal);
|
||||
if (jsonBytes(report, METADATA_BYTES) > METADATA_BYTES)
|
||||
if (
|
||||
jsonBytes(report, PE_RESOURCE_METADATA_BYTES) > PE_RESOURCE_METADATA_BYTES
|
||||
)
|
||||
limit("PE resource result exceeds the 8 MiB allocation budget.", "memory");
|
||||
return report;
|
||||
};
|
||||
|
||||
@@ -172,3 +172,35 @@ describe("PE resource provider budget reporting", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("PE resource provider fixed metadata budget", () => {
|
||||
it("reports the fixed metadata budget without worker memory advice", async () => {
|
||||
const root = await createTestTempDirectory("rea-pe-metadata-budget-");
|
||||
const path = join(root, "resource.exe");
|
||||
const name = "n".repeat(200);
|
||||
const resources = Array.from({ length: 6000 }, (_, index) => ({
|
||||
type: 10,
|
||||
name: `${name}${String(index).padStart(4, "0")}`,
|
||||
language: 1033,
|
||||
payload: Buffer.alloc(0),
|
||||
}));
|
||||
await writeFile(
|
||||
path,
|
||||
peResourceFixture(false, resources, 10 * 1024 * 1024).bytes,
|
||||
);
|
||||
const result = await inspect(path);
|
||||
if (result.ok) throw new Error("Expected a refusal");
|
||||
const projected = projectAnalysisError(result.error);
|
||||
expect(projected).toMatchObject({
|
||||
code: "resource_constraint",
|
||||
retryable: false,
|
||||
details: {
|
||||
resource: "memory",
|
||||
reported_limits: { max_metadata_bytes: 8 * 1024 * 1024 },
|
||||
},
|
||||
});
|
||||
// Neither caller bounds nor worker or host memory raise the fixed budget.
|
||||
expect(projected.remediation.action).toContain("max_metadata_bytes");
|
||||
expect(projected.remediation.action).not.toMatch(/sufficient memory/iu);
|
||||
});
|
||||
});
|
||||
@@ -25,7 +25,10 @@ import {
|
||||
import { err, ok } from "../../domain/result.js";
|
||||
import { ProviderCleanupError } from "../../domain/providerCleanupError.js";
|
||||
import { PE_RESOURCES_PROVIDER } from "../../application/InvestigationProviders.js";
|
||||
import { parsePeResources } from "./PeResourceParser.js";
|
||||
import {
|
||||
parsePeResources,
|
||||
PE_RESOURCE_METADATA_BYTES,
|
||||
} from "./PeResourceParser.js";
|
||||
|
||||
/** Portable parser over one bounded, identity-checked regular file. */
|
||||
export class PeResourcesProvider implements PeResourcesPort {
|
||||
@@ -126,19 +129,22 @@ const peResourcesFailurePrimary = (
|
||||
{
|
||||
max_file_bytes: input.max_file_bytes,
|
||||
max_entries: input.max_entries,
|
||||
...(cause.boundary === "memory"
|
||||
? { max_metadata_bytes: PE_RESOURCE_METADATA_BYTES }
|
||||
: {}),
|
||||
},
|
||||
{
|
||||
cause,
|
||||
// Both bounds are caller-selected and deterministic for the same
|
||||
// input, so waiting for an active analysis cannot help.
|
||||
...(cause.boundary === undefined || cause.boundary === "memory"
|
||||
// Every bound is deterministic for the same input, so waiting for an
|
||||
// active analysis or adding worker or host memory cannot help.
|
||||
...(cause.boundary === undefined
|
||||
? {}
|
||||
: {
|
||||
retryable: false,
|
||||
remediationAction: inputBudgetRemediation(
|
||||
cause.boundary,
|
||||
input,
|
||||
),
|
||||
remediationAction:
|
||||
cause.boundary === "memory"
|
||||
? "max_metadata_bytes is a fixed PE resource metadata budget; max_entries, max_file_bytes, worker limits, and available host memory do not raise it. Select a complete PE image whose resource metadata fits the budget; retrying the unchanged image will fail again."
|
||||
: inputBudgetRemediation(cause.boundary, input),
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -186,7 +186,18 @@ const failureForPrimary = (cause: unknown, path: string): AnalysisError => {
|
||||
cause.constraint.resource,
|
||||
`Selected artifact ${path}: ${cause.message}`,
|
||||
cause.constraint.reported_limits,
|
||||
{ cause },
|
||||
{
|
||||
cause,
|
||||
// Section and inflated-JSON sizes are fixed reader budgets; worker
|
||||
// limits and host memory cannot admit the same dependency section.
|
||||
...(cause.constraint.resource === "memory"
|
||||
? {
|
||||
retryable: false,
|
||||
remediationAction:
|
||||
"maximum_bytes is a fixed Rust dependency-section budget; worker limits and available host memory do not raise it. Select another complete Rust binary whose dependency section fits the budget; retrying the unchanged artifact will fail again.",
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
);
|
||||
if (cause instanceof RustAuditableFormatFailure)
|
||||
return cause.kind === "unsupported"
|
||||
|
||||
@@ -164,6 +164,38 @@ cliTest("reports malformed zlib as invalid input", async ({ cli }) => {
|
||||
});
|
||||
});
|
||||
|
||||
cliTest(
|
||||
"refuses an oversized dependency section with fixed-budget advice",
|
||||
async ({ cli }) => {
|
||||
const root = await createTestTempDirectory("rea-rust-cli-section-");
|
||||
const path = join(root, "oversized-section.elf");
|
||||
// Keep the ELF headers and declare a file-backed .dep-v0 one byte above
|
||||
// the fixed 1 MiB compressed-section budget.
|
||||
const limit = 1024 * 1024;
|
||||
const bytes = Buffer.alloc(1024 + limit + 1);
|
||||
createRustImage({ format: "elf" }).copy(bytes);
|
||||
bytes.writeBigUInt64LE(BigInt(limit + 1), 384 + 32);
|
||||
await writeFile(path, bytes);
|
||||
const result = await cli.run({
|
||||
arguments: ["inspect-rust-binary", path, "--json"],
|
||||
});
|
||||
expect(result.exitCode).toBe(1);
|
||||
expect(result.json).toMatchObject({
|
||||
code: "resource_constraint",
|
||||
retryable: false,
|
||||
remediation: { action: expect.stringContaining("maximum_bytes") },
|
||||
details: {
|
||||
resource: "memory",
|
||||
reported_limits: { maximum_bytes: 1024 * 1024 },
|
||||
},
|
||||
});
|
||||
// The section budget is fixed; worker or host memory cannot admit it.
|
||||
expect(result.json).not.toMatchObject({
|
||||
remediation: { action: expect.stringMatching(/sufficient memory/iu) },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
cliTest(
|
||||
"releases a failed Rust descriptor close before projecting the CLI failure",
|
||||
async ({ cli }) => {
|
||||
|
||||
@@ -1245,7 +1245,15 @@ it.each(["identify_flutter_build", "inspect_dart_aot"] as const)(
|
||||
onTestFinished(() => provider.close());
|
||||
const result = await provider.execute({ operation, input: { path } });
|
||||
if (result.ok) throw new Error("ABI expansion must be refused");
|
||||
expect(projectAnalysisError(result.error).code).toBe("resource_constraint");
|
||||
const projected = projectAnalysisError(result.error);
|
||||
expect(projected).toMatchObject({
|
||||
code: "resource_constraint",
|
||||
retryable: false,
|
||||
details: { reported_limits: { max_flutter_abis: 32 } },
|
||||
});
|
||||
// The inventory bound is fixed; worker or host memory cannot admit it.
|
||||
expect(projected.remediation.action).toContain("max_flutter_abis");
|
||||
expect(projected.remediation.action).not.toMatch(/sufficient memory/iu);
|
||||
expect(extract).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
AnalysisResourceConstraintError,
|
||||
AnalysisUnsupportedTargetError,
|
||||
} from "../../../src/domain/analysisErrorCore.js";
|
||||
import { projectAnalysisError } from "../../../src/domain/analysisErrorProjection.js";
|
||||
import {
|
||||
createGoBinaryDiagnosticFixtures,
|
||||
createGoBinaryFixture,
|
||||
@@ -67,6 +68,11 @@ it("preserves the actual build-info byte guard as a resource failure", async ()
|
||||
boundary: "build-info",
|
||||
maximum_bytes: 1024 * 1024,
|
||||
});
|
||||
const projected = projectAnalysisError(result.error);
|
||||
expect(projected.retryable).toBe(false);
|
||||
// The parser budget is fixed; worker or host memory cannot admit it.
|
||||
expect(projected.remediation.action).toContain("maximum_bytes");
|
||||
expect(projected.remediation.action).not.toMatch(/sufficient memory/iu);
|
||||
});
|
||||
|
||||
it.skipIf(process.platform === "win32")(
|
||||
|
||||
@@ -664,6 +664,11 @@ describe("managed comparison snapshot admission and cleanup", () => {
|
||||
max_snapshot_bytes: MAX_MANAGED_SNAPSHOT_BYTES,
|
||||
},
|
||||
});
|
||||
const projected = projectAnalysisError(result.error);
|
||||
expect(projected.retryable).toBe(false);
|
||||
// The snapshot budget is fixed; worker or host memory cannot admit it.
|
||||
expect(projected.remediation.action).toContain("max_snapshot_bytes");
|
||||
expect(projected.remediation.action).not.toMatch(/sufficient memory/iu);
|
||||
expect(read).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
read.mockRestore();
|
||||
|
||||
Reference in new issue
Block a user