mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 19:23:24 +00:00
refactor(verify): give the switch, Swift and address-context Ghidra workspaces to VerifierFixture (#11121)
* refactor(verify): give the switch, Swift and address-context Ghidra workspaces to VerifierFixture * fix(verify): carry the retained Ghidra workspace on each verifier's final failure
This commit is contained in:
1 parent
44d359bd05
commit
aec679584a
4 files changed
+75
-86
No files matched your search
@@ -1,8 +1,7 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { mkdir, mkdtemp, readFile, readdir, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { mkdir, readFile, readdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
@@ -14,6 +13,7 @@ import {
|
||||
requireMcpOutput,
|
||||
} from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { VerifierFixture } from "./lib/verifier-fixture.mjs";
|
||||
|
||||
if (process.argv.length !== 2 || process.platform !== "linux")
|
||||
throw new Error("Run this ELF verifier on Linux without arguments.");
|
||||
@@ -22,7 +22,12 @@ if (!process.env.GHIDRA_INSTALL_DIR)
|
||||
const exec = promisify(execFile);
|
||||
const run = createVerifierRun();
|
||||
const root = fileURLToPath(new URL("..", import.meta.url));
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-address-context-"));
|
||||
// Ghidra children use the workspace as TMPDIR; a plain root admits them on every host.
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-address-context-",
|
||||
private: false,
|
||||
});
|
||||
const workspace = workspaceFixture.path;
|
||||
const runtime = join(workspace, "runtime");
|
||||
await mkdir(runtime);
|
||||
const path = join(workspace, "pie.elf");
|
||||
@@ -302,47 +307,34 @@ try {
|
||||
|
||||
const cleanupFailures = [];
|
||||
// Await provider-owned cleanup before the SDK can terminate its MCP parent.
|
||||
if (connected) {
|
||||
try {
|
||||
await call("close_binary");
|
||||
} catch (cause) {
|
||||
cleanupFailures.push(cause);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await client.close();
|
||||
} catch (cause) {
|
||||
cleanupFailures.push(cause);
|
||||
}
|
||||
try {
|
||||
await transport.close();
|
||||
} catch (cause) {
|
||||
cleanupFailures.push(cause);
|
||||
}
|
||||
try {
|
||||
// The workspace is removed only after every cleanup and check confirms.
|
||||
if (connected) workspaceFixture.releaseAfter(() => call("close_binary"));
|
||||
workspaceFixture.releaseAfter(() => client.close());
|
||||
workspaceFixture.releaseAfter(() => transport.close());
|
||||
workspaceFixture.releaseAfter(async () => {
|
||||
assert.deepEqual(await readdir(runtime), []);
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
});
|
||||
try {
|
||||
await workspaceFixture.release();
|
||||
} catch (cause) {
|
||||
cleanupFailures.push(cause);
|
||||
}
|
||||
if (cleanupFailures.length === 0) {
|
||||
try {
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
} catch (cause) {
|
||||
cleanupFailures.push(cause);
|
||||
}
|
||||
}
|
||||
if (cleanupFailures.length > 0) {
|
||||
process.stderr.write(
|
||||
`Retained verifier workspace after cleanup failure: ${workspace}\n`,
|
||||
);
|
||||
throw new AggregateError(
|
||||
primaryFailure === undefined
|
||||
? cleanupFailures
|
||||
: [primaryFailure.cause, ...cleanupFailures],
|
||||
"Ghidra address-context verifier cleanup failed",
|
||||
throw Object.assign(
|
||||
new AggregateError(
|
||||
primaryFailure === undefined
|
||||
? cleanupFailures
|
||||
: [primaryFailure.cause, ...cleanupFailures],
|
||||
"Ghidra address-context verifier cleanup failed",
|
||||
),
|
||||
// The caller can retry the retained workspace from the final failure.
|
||||
workspaceFixture.released ? {} : { cleanupLeases: [workspaceFixture] },
|
||||
);
|
||||
}
|
||||
if (primaryFailure !== undefined) throw primaryFailure.cause;
|
||||
|
||||
@@ -1,19 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
import {
|
||||
closeVerifierResources,
|
||||
withVerifierCleanup,
|
||||
} from "./lib/verifier-cleanup.mjs";
|
||||
import { withVerifierCleanup } from "./lib/verifier-cleanup.mjs";
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile } from "node:child_process";
|
||||
import {
|
||||
copyFile,
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
readdir,
|
||||
rm,
|
||||
} from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { copyFile, mkdir, readFile, readdir } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
@@ -27,6 +16,10 @@ import {
|
||||
} from "../dist/process/ProviderProcess.js";
|
||||
import { requireMcpOperationResult } from "./lib/mcp-verifier-results.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
|
||||
const execute = promisify(execFile);
|
||||
const run = createVerifierRun();
|
||||
@@ -47,7 +40,12 @@ try {
|
||||
{ cause },
|
||||
);
|
||||
}
|
||||
const root = await mkdtemp(join(tmpdir(), "rea-ghidra-swift-"));
|
||||
// Ghidra children use the workspace as TMPDIR; a plain root admits them on every host.
|
||||
const rootFixture = await VerifierFixture.open({
|
||||
prefix: "rea-ghidra-swift-",
|
||||
private: false,
|
||||
});
|
||||
const root = rootFixture.path;
|
||||
const client = new Client({ name: "ghidra-swift-verifier", version: "1" });
|
||||
const transport = new StdioClientTransport({
|
||||
command: process.execPath,
|
||||
@@ -79,7 +77,19 @@ let connected = false;
|
||||
let verifierRun;
|
||||
let literalReport;
|
||||
let decoderCleaned = true;
|
||||
await withVerifierCleanup(async () => {
|
||||
// The root is removed only after the session, client and decoder confirm.
|
||||
rootFixture.releaseAfter(async () => {
|
||||
if (connected) await call("close_binary", {});
|
||||
});
|
||||
rootFixture.releaseAfter(() => client.close());
|
||||
rootFixture.releaseAfter(() => transport.close());
|
||||
rootFixture.releaseAfter(async () => {
|
||||
assert.equal(decoderCleaned, true, "Owned decoder cleanup remains pending");
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
});
|
||||
await withVerifierFixture(rootFixture, async () => {
|
||||
await execute("swiftc", ["-O", "-g", source, "-o", binary], {
|
||||
timeout: 60_000,
|
||||
});
|
||||
@@ -193,22 +203,7 @@ await withVerifierCleanup(async () => {
|
||||
}, [() => call("close_binary", {})]);
|
||||
}
|
||||
literalReport = await verifyStringLiterals();
|
||||
}, [
|
||||
async () => {
|
||||
await closeVerifierResources([
|
||||
async () => {
|
||||
if (connected) await call("close_binary", {});
|
||||
},
|
||||
() => client.close(),
|
||||
() => transport.close(),
|
||||
]);
|
||||
assert.equal(decoderCleaned, true, "Owned decoder cleanup remains pending");
|
||||
verifierRun = await completeVerifierRun(run);
|
||||
assert.equal(verifierRun.process_lineage.status, "verified");
|
||||
assert.deepEqual(verifierRun.process_lineage.descendants, []);
|
||||
await rm(root, { recursive: true, force: true });
|
||||
},
|
||||
]);
|
||||
});
|
||||
console.log(
|
||||
JSON.stringify(
|
||||
{
|
||||
|
||||
@@ -10,13 +10,10 @@ import { createHash } from "node:crypto";
|
||||
import {
|
||||
copyFile,
|
||||
mkdir,
|
||||
mkdtemp,
|
||||
readFile,
|
||||
readdir,
|
||||
rm,
|
||||
writeFile,
|
||||
} from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { parseArgs, promisify } from "node:util";
|
||||
@@ -33,6 +30,10 @@ import {
|
||||
} from "./verify-real-ghidra-switch-assertions.mjs";
|
||||
import { createVerifierRun, completeVerifierRun } from "./lib/verifier-run.mjs";
|
||||
import { verifySwitchEvidenceControls } from "./verify-real-ghidra-switch-controls.mjs";
|
||||
import {
|
||||
VerifierFixture,
|
||||
withVerifierFixture,
|
||||
} from "./lib/verifier-fixture.mjs";
|
||||
|
||||
// Narrow compiler-shape lane; no cross compiler or game artifact prerequisites.
|
||||
const { values } = parseArgs({
|
||||
@@ -88,7 +89,12 @@ if (!compilerOnly) {
|
||||
assert.equal(installation.status, "available", JSON.stringify(installation));
|
||||
}
|
||||
const run = createVerifierRun();
|
||||
const workspace = await mkdtemp(join(tmpdir(), "rea-switch-proof-"));
|
||||
// Ghidra children use the workspace as TMPDIR; a plain root admits them on every host.
|
||||
const workspaceFixture = await VerifierFixture.open({
|
||||
prefix: "rea-switch-proof-",
|
||||
private: false,
|
||||
});
|
||||
const workspace = workspaceFixture.path;
|
||||
const runtime = join(workspace, "runtime");
|
||||
await mkdir(runtime);
|
||||
const source = join(workspace, "switch.c");
|
||||
@@ -96,7 +102,18 @@ await writeFile(source, buildSwitchSource());
|
||||
const flags = ["-O2", "-g", "-fno-pie", "-no-pie"];
|
||||
const results = [];
|
||||
let productionControls = null;
|
||||
await withVerifierCleanup(async () => {
|
||||
// The workspace is removed only once no owned runtime or process survives.
|
||||
workspaceFixture.releaseAfter(async () => {
|
||||
assert.deepEqual(
|
||||
(await readdir(runtime)).filter((name) => name.startsWith("rea-ghidra-")),
|
||||
[],
|
||||
"Owned Ghidra runtime survived close",
|
||||
);
|
||||
const cleanup = await completeVerifierRun(run);
|
||||
assert.equal(cleanup.process_lineage.status, "verified");
|
||||
assert.deepEqual(cleanup.process_lineage.descendants, []);
|
||||
});
|
||||
await withVerifierFixture(workspaceFixture, async () => {
|
||||
for (const compiler of ["gcc", "clang"]) {
|
||||
const original = join(workspace, `${compiler}-optimized`);
|
||||
await exec(commands[compiler], [...flags, source, "-o", original]);
|
||||
@@ -170,19 +187,7 @@ await withVerifierCleanup(async () => {
|
||||
});
|
||||
}
|
||||
}
|
||||
}, [
|
||||
async () => {
|
||||
assert.deepEqual(
|
||||
(await readdir(runtime)).filter((name) => name.startsWith("rea-ghidra-")),
|
||||
[],
|
||||
"Owned Ghidra runtime survived close",
|
||||
);
|
||||
const cleanup = await completeVerifierRun(run);
|
||||
assert.equal(cleanup.process_lineage.status, "verified");
|
||||
assert.deepEqual(cleanup.process_lineage.descendants, []);
|
||||
await rm(workspace, { recursive: true, force: true });
|
||||
},
|
||||
]);
|
||||
});
|
||||
const lifecycle = await completeVerifierRun(run);
|
||||
if (lifecycle.process_lineage.status === "verified")
|
||||
assert.deepEqual(lifecycle.process_lineage.descendants, []);
|
||||
|
||||
@@ -359,7 +359,6 @@
|
||||
"src/server/createServer.ts": 1
|
||||
},
|
||||
"hand-caught-configuration-error": {
|
||||
"src/application/binary/BinarySession.ts": 3,
|
||||
"src/application/McpClientRegistration.ts": 1,
|
||||
"src/application/McpRegistrationCatalog.ts": 1,
|
||||
"src/application/McpRegistrationGit.ts": 1,
|
||||
@@ -367,6 +366,7 @@
|
||||
"src/application/SupportedClients.ts": 1,
|
||||
"src/application/Uninstall.ts": 2,
|
||||
"src/application/WebScriptExportService.ts": 1,
|
||||
"src/application/binary/BinarySession.ts": 3,
|
||||
"src/ghidra/GhidraProviderClient.ts": 1,
|
||||
"src/ida/IdaConfiguration.ts": 1
|
||||
},
|
||||
@@ -418,12 +418,9 @@
|
||||
"scripts/verify-pe-load-config.mjs": 1,
|
||||
"scripts/verify-published-package.mjs": 1,
|
||||
"scripts/verify-real-cutter.mjs": 1,
|
||||
"scripts/verify-real-ghidra-address-context.mjs": 1,
|
||||
"scripts/verify-real-ghidra-checkpoints.mjs": 1,
|
||||
"scripts/verify-real-ghidra-function-bundle.mjs": 1,
|
||||
"scripts/verify-real-ghidra-project-coverage.mjs": 1,
|
||||
"scripts/verify-real-ghidra-swift.mjs": 1,
|
||||
"scripts/verify-real-ghidra-switch.mjs": 1,
|
||||
"scripts/verify-real-ghidra.mjs": 1,
|
||||
"scripts/verify-real-hopper-callers.mjs": 1,
|
||||
"scripts/verify-real-hopper-deadlines.mjs": 1,
|
||||
|
||||
Reference in new issue
Block a user