mirror of
https://github.com/morluto/rea.git
synced 2026-11-12 09:52:27 +00:00
fix(ghidra): validate seed outcome conservation (#9969)
This commit is contained in:
1 parent
b4654b493d
commit
d3eb3f6478
2 files changed
+114
-14
No files matched your search
@@ -17,8 +17,13 @@ import { parseConfig } from "../config/parseConfig.js";
|
||||
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import { silentLogger } from "../logger.js";
|
||||
import {
|
||||
GHIDRA_SESSION_CAPABILITIES,
|
||||
parseGhidraSessionInfo,
|
||||
} from "./GhidraSessionValues.js";
|
||||
import {
|
||||
ghidraSeedFailure,
|
||||
ghidraSeedReportSchema,
|
||||
ghidraSeedLimitations,
|
||||
parseGhidraSeeds,
|
||||
resolveGhidraAnalysisSeeds,
|
||||
@@ -252,6 +257,79 @@ describe("Ghidra seed session verification", () => {
|
||||
unmapped: 0,
|
||||
};
|
||||
|
||||
it.each([100, 0])(
|
||||
"rejects a handshake whose outcome counters report %i created functions",
|
||||
(function_created) => {
|
||||
const expected = {
|
||||
runId: "d6fcbb66-e829-4ff6-a535-0035aec63139",
|
||||
providerVersion: "12.1.4",
|
||||
profileDigest: "a".repeat(64),
|
||||
targetSha256: "b".repeat(64),
|
||||
};
|
||||
const parsed = parseGhidraSessionInfo(
|
||||
{
|
||||
name: "REA Ghidra bridge",
|
||||
run_id: expected.runId,
|
||||
profile_digest: expected.profileDigest,
|
||||
provider: { id: "ghidra", version: expected.providerVersion },
|
||||
read_only: false,
|
||||
analysis_complete: true,
|
||||
analysis_timed_out: false,
|
||||
capabilities: [...GHIDRA_SESSION_CAPABILITIES],
|
||||
target: {
|
||||
name: "fixture",
|
||||
language_id: "x86:LE:64:default",
|
||||
compiler_spec_id: "gcc",
|
||||
image_base: "0x0",
|
||||
default_address_space: "ram",
|
||||
sha256: expected.targetSha256,
|
||||
},
|
||||
analysis_seeds: { ...report, function_created },
|
||||
},
|
||||
expected,
|
||||
);
|
||||
expect(parsed).toMatchObject({
|
||||
ok: false,
|
||||
error: { code: "handshake_identity" },
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ function_created: 3, function_failed: 0, code_decoded: 0 },
|
||||
{ function_created: 0, function_failed: 0, code_decoded: 3 },
|
||||
{ function_created: 0, function_failed: 0, label_applied: 3 },
|
||||
])(
|
||||
"rejects outcomes attributed to more seeds of a kind than committed: %j",
|
||||
(changed) => {
|
||||
const misattributed = { ...report, ...changed };
|
||||
expect(ghidraSeedReportSchema.safeParse(misattributed).success).toBe(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
ghidraSeedFailure(commitment, misattributed, "xrefs"),
|
||||
).toBeDefined();
|
||||
},
|
||||
);
|
||||
|
||||
it("admits producer outcomes including aggregate unmapped seeds without inventing their kinds", () => {
|
||||
for (const outcome of [
|
||||
report,
|
||||
{
|
||||
...report,
|
||||
function_created: 0,
|
||||
function_failed: 0,
|
||||
code_decoded: 0,
|
||||
label_applied: 0,
|
||||
unmapped: 4,
|
||||
},
|
||||
{ ...report, function_created: 0, code_decoded: 0, unmapped: 2 },
|
||||
]) {
|
||||
expect(ghidraSeedReportSchema.safeParse(outcome).success).toBe(true);
|
||||
expect(ghidraSeedFailure(commitment, outcome, "xrefs")).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("accepts exactly the committed seeds and reports their outcome", () => {
|
||||
expect(ghidraSeedFailure(commitment, report, "xrefs")).toBeUndefined();
|
||||
expect(ghidraSeedFailure(undefined, undefined, "xrefs")).toBeUndefined();
|
||||
|
||||
@@ -37,19 +37,33 @@ export const ghidraSeedCommitmentSchema = z.strictObject({
|
||||
export type GhidraSeedCommitment = z.infer<typeof ghidraSeedCommitmentSchema>;
|
||||
|
||||
/** What the pre-analysis script applied, as reported by the bridge handshake. */
|
||||
export const ghidraSeedReportSchema = z.strictObject({
|
||||
format: z.literal(GHIDRA_SEED_FORMAT),
|
||||
sha256: digest,
|
||||
entries: count,
|
||||
function_created: count,
|
||||
function_existing: count,
|
||||
function_failed: count,
|
||||
code_decoded: count,
|
||||
code_failed: count,
|
||||
label_applied: count,
|
||||
label_failed: count,
|
||||
unmapped: count,
|
||||
});
|
||||
export const ghidraSeedReportSchema = z
|
||||
.strictObject({
|
||||
format: z.literal(GHIDRA_SEED_FORMAT),
|
||||
sha256: digest,
|
||||
entries: count,
|
||||
function_created: count,
|
||||
function_existing: count,
|
||||
function_failed: count,
|
||||
code_decoded: count,
|
||||
code_failed: count,
|
||||
label_applied: count,
|
||||
label_failed: count,
|
||||
unmapped: count,
|
||||
})
|
||||
.refine(
|
||||
(report) =>
|
||||
report.function_created +
|
||||
report.function_existing +
|
||||
report.function_failed +
|
||||
report.code_decoded +
|
||||
report.code_failed +
|
||||
report.label_applied +
|
||||
report.label_failed +
|
||||
report.unmapped ===
|
||||
report.entries,
|
||||
{ message: "Seed outcomes must account for exactly the reported entries." },
|
||||
);
|
||||
export type GhidraSeedReport = z.infer<typeof ghidraSeedReportSchema>;
|
||||
|
||||
type SeedKind = "function" | "code" | "label";
|
||||
@@ -267,7 +281,15 @@ export const ghidraSeedFailure = (
|
||||
: report === undefined
|
||||
? "The Ghidra session did not report the committed analysis seeds."
|
||||
: report.sha256 !== commitment.sha256 ||
|
||||
report.entries !== totalEntries(commitment)
|
||||
report.entries !== totalEntries(commitment) ||
|
||||
report.function_created +
|
||||
report.function_existing +
|
||||
report.function_failed >
|
||||
commitment.entries.function ||
|
||||
report.code_decoded + report.code_failed >
|
||||
commitment.entries.code ||
|
||||
report.label_applied + report.label_failed >
|
||||
commitment.entries.label
|
||||
? "The Ghidra session applied analysis seeds that differ from the committed profile."
|
||||
: undefined;
|
||||
return reason === undefined
|
||||
|
||||
Reference in new issue
Block a user