Compare commits

..
8 changed files with 96 additions and 87 deletions

No files matched your search

+25
View File
@@ -6,6 +6,31 @@ import {
} from "../config/runtimeAnalysisSettings.js";
import { parseConfig } from "../config/parseConfig.js";
it.each([
["inspect_native_api", [64, 7]],
["trace_native_ui_action", []],
] as const)(
"reports the decompiler budgets consumed by %s",
(operation, values) => {
const store = new AnalysisSettings({
REA_GHIDRA_DECOMPILE_TIMEOUT_SECONDS: "7",
REA_GHIDRA_DECOMPILE_MAX_PAYLOAD_MIB: "64",
});
expect(
store
.inspect({
provider: "ghidra",
operation,
settings: [
"ghidra.decompile_max_payload_mib",
"ghidra.decompile_timeout_seconds",
],
})
.settings.map(({ value }) => value),
).toEqual(values);
},
);
it("updates atomically, restores launch values, and refuses stale or invalid writes", () => {
const store = new AnalysisSettings({ REA_ASAR_MAX_HEADER_BYTES: "20000000" });
const inspect = () => store.inspect({ settings: ["asar.max_header_bytes"] });
@@ -157,64 +157,60 @@ it("retains the acquired observation when cancellation arrives during inspection
}
});
it.each([
"different-path",
"invalid-digest",
"outside-range",
"invalid-hex",
"fractional-bytes",
])("rejects provider evidence at the next boundary: %s", async (problem) => {
const fixture = binaryLayoutFixture();
if (problem === "different-path") fixture.artifact.path = "/different.elf";
if (problem === "invalid-digest") fixture.artifact.sha256 = "unobserved";
if (problem === "fractional-bytes") fixture.artifact.bytes = 1.5;
if (problem === "outside-range" || problem === "invalid-hex")
fixture.segments.push({
index: 0,
type: "PT_LOAD",
offset: problem === "invalid-hex" ? "not-hex" : "0x80",
header_location: { offset: "0x0", bytes: "0x38" },
file_backing: "file",
file_size: "0x1",
memory_size: "0x1",
virtual_address: "0x20000000000001",
physical_address: "0x0",
alignment: "0x1",
flags: "0x5",
permissions: { read: true, write: false, execute: true },
it.each(["different-path", "invalid-digest", "outside-range"])(
"rejects provider evidence at the next boundary: %s",
async (problem) => {
const fixture = binaryLayoutFixture();
if (problem === "different-path") fixture.artifact.path = "/different.elf";
if (problem === "invalid-digest") fixture.artifact.sha256 = "unobserved";
if (problem === "outside-range")
fixture.segments.push({
index: 0,
type: "PT_LOAD",
offset: "0x80",
header_location: { offset: "0x0", bytes: "0x38" },
file_backing: "file",
file_size: "0x1",
memory_size: "0x1",
virtual_address: "0x20000000000001",
physical_address: "0x0",
alignment: "0x1",
flags: "0x5",
permissions: { read: true, write: false, execute: true },
});
const service = new BinaryLayoutService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: BINARY_LAYOUT_TEST_PROVIDER,
inspect: () => Promise.resolve(ok(fixture)),
});
const service = new BinaryLayoutService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: BINARY_LAYOUT_TEST_PROVIDER,
inspect: () => Promise.resolve(ok(fixture)),
});
expect(
await service.inspect({ path: "/artifacts/source-owned.elf" }),
).toMatchObject({
ok: false,
error: {
_tag: "AnalysisOutputError",
capturedOutput: fixture.diagnostics,
},
});
const controller = new AbortController();
const lateInvalid = await new BinaryLayoutService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: BINARY_LAYOUT_TEST_PROVIDER,
inspect: () => {
controller.abort();
return Promise.resolve(ok(fixture));
},
}).inspect(
{ path: "/artifacts/source-owned.elf" },
{ signal: controller.signal },
);
expect(lateInvalid).toMatchObject({
ok: false,
error: { _tag: "AnalysisOutputError" },
});
});
expect(
await service.inspect({ path: "/artifacts/source-owned.elf" }),
).toMatchObject({
ok: false,
error: {
_tag: "AnalysisOutputError",
capturedOutput: fixture.diagnostics,
},
});
const controller = new AbortController();
const lateInvalid = await new BinaryLayoutService({
inspectAndClose(input, options) {
return this.inspect(input, options);
},
identity: BINARY_LAYOUT_TEST_PROVIDER,
inspect: () => {
controller.abort();
return Promise.resolve(ok(fixture));
},
}).inspect(
{ path: "/artifacts/source-owned.elf" },
{ signal: controller.signal },
);
expect(lateInvalid).toMatchObject({
ok: false,
error: { _tag: "AnalysisOutputError" },
});
},
);
@@ -169,8 +169,6 @@ it.each([
"outside-range",
"owner-base64",
"descriptor-base64",
"invalid-hex",
"fractional-bytes",
])("rejects provider evidence at the next boundary: %s", async (problem) => {
const fixture = recordedCrashFixture();
if (problem === "different-path") fixture.artifact.path = "/different.elf";
@@ -178,8 +176,6 @@ it.each([
if (problem === "owner-base64") fixture.notes[0]!.owner_bytes_base64 = "A";
if (problem === "descriptor-base64")
fixture.notes[0]!.descriptor_bytes_base64 = "A";
if (problem === "invalid-hex") fixture.segments[0]!.offset = "not-hex";
if (problem === "fractional-bytes") fixture.artifact.bytes = 1.5;
if (problem === "outside-range")
fixture.segments.push({
index: 0,
+12 -14
View File
@@ -57,6 +57,16 @@ const timer = {
unit: "ms",
appliesAt: "next_operation",
} as const;
/** Operations that invoke Ghidra's decompiler directly or through a workflow. */
const ghidraDecompileOperations = [
"procedure_pseudo_code",
"analyze_function",
"inspect_native_api",
"decompile_all",
"trace_native_values",
] as const;
/** Metadata aggregates existing owner definitions; no environment mutation or engine discovery. */
export const ANALYSIS_SETTINGS = {
"ghidra.decompile_max_payload_mib": {
@@ -67,14 +77,7 @@ export const ANALYSIS_SETTINGS = {
unit: "MiB",
appliesAt: "next_operation",
provider: "ghidra",
operations: [
"procedure_pseudo_code",
"analyze_function",
"inspect_native_api",
"decompile_all",
"trace_native_values",
"trace_native_ui_action",
],
operations: ghidraDecompileOperations,
},
"asar.max_listing_bytes": {
environment: "REA_ASAR_MAX_LISTING_BYTES",
@@ -212,12 +215,7 @@ export const ANALYSIS_SETTINGS = {
unit: "seconds",
appliesAt: "next_operation",
provider: "ghidra",
operations: [
"procedure_pseudo_code",
"analyze_function",
"decompile_all",
"trace_native_values",
],
operations: ghidraDecompileOperations,
},
"jadx.operation_timeout_ms": {
...timer,
-2
View File
@@ -260,8 +260,6 @@ describe("build bundle download installs", () => {
},
}).success;
expect(install({})).toBe(true);
expect(install({ url: "not-a-url" })).toBe(false);
expect(install({ url: "https://" })).toBe(false);
expect(install({ program: "../bin/sh" })).toBe(false);
expect(install({ program: "/usr/bin/notes" })).toBe(false);
expect(install({ program: "notes//notes" })).toBe(false);
+1 -1
View File
@@ -7,7 +7,7 @@ export const COMPUTER_ARCHITECTURES = ["amd64", "arm64"] as const;
/** A URL agentenv downloads from: http or https in lowercase, without a user name or password. */
export const downloadUrlSchema = z
.url({ protocol: /^https?$/u, abort: true })
.url({ protocol: /^https?$/u })
.regex(/^https?:\/\//u)
.refine(
(url) => {
+1 -3
View File
@@ -6,9 +6,7 @@ import { pwntoolsProducerLimitsField } from "./pwntoolsResourceLimits.js";
import { absoluteLocalPathStringSchema } from "../localPath.js";
import { canonicalBase64Schema } from "../stringPatterns.js";
const unsignedHex = z
.string()
.regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/, { abort: true });
const unsignedHex = z.string().regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/);
const index = z.int().nonnegative();
const scalar = z.union([z.string(), z.int()]);
const range = z.strictObject({ offset: unsignedHex, bytes: unsignedHex });
+1 -3
View File
@@ -7,9 +7,7 @@ import { absoluteLocalPathStringSchema } from "../localPath.js";
import { canonicalBase64Schema } from "../stringPatterns.js";
import { recordedNoteCoverageIssues } from "./recordedCrashNoteCoverage.js";
const hex = z
.string()
.regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/, { abort: true });
const hex = z.string().regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/);
const index = z.int().nonnegative();
const signed32 = z.int().min(-2147483648).max(2147483647);
const base64 = canonicalBase64Schema;