mirror of
https://github.com/morluto/rea
synced 2026-10-26 06:16:17 -04:00
11fe5a13f8
* fix(native): retain failed pwntools process and runtime cleanup Keep failed worker/debugger cleanup owners and their private files until verified process shutdown and successful directory removal. Retry cleanup before another inspection and expose shutdown through the shared services, CLI and MCP server. Serialize acquisition without delaying cancellation of queued callers, and preserve primary diagnostics when CLI cleanup fails. The original decoder reproduction left a live owned Node child with its runtime deleted and no retained cleanup owner; the regression now verifies retention, admission blocking, retries and eventual process/file cleanup. Validation: 10 lifecycle/public CLI/MCP tests, 20 service tests, test discovery 814/814, build:cached and full check including architecture guards. Actual POSIX process/filesystem boundaries used injected Node workers on macOS; real Linux pwntools/GDB provider parsing was not exercised. * test(native): verify retained recorded-crash cleanup lifecycle * fix(native): finalize diagnostic cleanup before publishing results * fix(native): retire decoder stages before reading runtime evidence --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
147 lines
4.5 KiB
TypeScript
147 lines
4.5 KiB
TypeScript
import { expect, it } from "vitest";
|
|
import { BinaryLayoutService } from "./BinaryLayoutService.js";
|
|
import {
|
|
binaryLayoutFixture,
|
|
BINARY_LAYOUT_TEST_PROVIDER,
|
|
} from "../../../tests/fixtures/binaryDiagnostics/layout.js";
|
|
import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCore.js";
|
|
import { parseEvidence } from "../../domain/evidence.js";
|
|
import { err, ok } from "../../domain/result.js";
|
|
|
|
it.each([
|
|
{ path: "relative.elf" },
|
|
{ path: "/selected.elf", approval: true },
|
|
{ path: 123 },
|
|
{},
|
|
])(
|
|
"rejects malformed selection before starting the provider: %j",
|
|
async (input) => {
|
|
const service = new BinaryLayoutService({
|
|
inspectAndClose(input, options) {
|
|
return this.inspect(input, options);
|
|
},
|
|
identity: BINARY_LAYOUT_TEST_PROVIDER,
|
|
inspect: () => {
|
|
throw new Error("must not run");
|
|
},
|
|
});
|
|
expect(await service.inspect(input)).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "AnalysisInputError" },
|
|
});
|
|
},
|
|
);
|
|
|
|
it("records the decoded layout once in its Evidence", async () => {
|
|
const layout = binaryLayoutFixture();
|
|
const service = new BinaryLayoutService({
|
|
inspectAndClose(input, options) {
|
|
return this.inspect(input, options);
|
|
},
|
|
identity: BINARY_LAYOUT_TEST_PROVIDER,
|
|
inspect: () => Promise.resolve(ok(layout)),
|
|
});
|
|
const result = await service.inspect({ path: layout.artifact.path });
|
|
if (!result.ok) throw result.error;
|
|
const evidence = parseEvidence(result.value);
|
|
expect(evidence.normalized_result).toEqual(layout);
|
|
expect(evidence.raw_result).toBeNull();
|
|
});
|
|
|
|
it("preserves a provider's actionable unsupported reason", async () => {
|
|
const failure = new AnalysisCapabilityUnavailableError(
|
|
"fixture-layout",
|
|
"inspect_binary_layout",
|
|
"ELF core belongs to recorded-crash analysis.",
|
|
);
|
|
const service = new BinaryLayoutService({
|
|
inspectAndClose(input, options) {
|
|
return this.inspect(input, options);
|
|
},
|
|
identity: BINARY_LAYOUT_TEST_PROVIDER,
|
|
inspect: () => Promise.resolve(err(failure)),
|
|
});
|
|
expect(await service.inspect({ path: "/source.elf" })).toEqual(err(failure));
|
|
});
|
|
|
|
it("cancels before invocation and after an acquired observation", async () => {
|
|
const controller = new AbortController();
|
|
const service = new BinaryLayoutService({
|
|
inspectAndClose(input, options) {
|
|
return this.inspect(input, options);
|
|
},
|
|
identity: BINARY_LAYOUT_TEST_PROVIDER,
|
|
inspect: () => {
|
|
controller.abort();
|
|
return Promise.resolve(ok(binaryLayoutFixture()));
|
|
},
|
|
});
|
|
expect(
|
|
await service.inspect(
|
|
{ path: "/artifacts/source-owned.elf" },
|
|
{ signal: controller.signal },
|
|
),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
_tag: "AnalysisCancelledError",
|
|
capturedOutput: binaryLayoutFixture().diagnostics,
|
|
},
|
|
});
|
|
const untouched = new BinaryLayoutService({
|
|
inspectAndClose(input, options) {
|
|
return this.inspect(input, options);
|
|
},
|
|
identity: BINARY_LAYOUT_TEST_PROVIDER,
|
|
inspect: () => {
|
|
throw new Error("must not run");
|
|
},
|
|
});
|
|
expect(
|
|
await untouched.inspect(
|
|
{ path: "/source.elf" },
|
|
{ signal: controller.signal },
|
|
),
|
|
).toMatchObject({ ok: false, error: { _tag: "AnalysisCancelledError" } });
|
|
});
|
|
|
|
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)),
|
|
});
|
|
expect(
|
|
await service.inspect({ path: "/artifacts/source-owned.elf" }),
|
|
).toMatchObject({
|
|
ok: false,
|
|
error: {
|
|
_tag: "AnalysisOutputError",
|
|
capturedOutput: fixture.diagnostics,
|
|
},
|
|
});
|
|
},
|
|
);
|