mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
* fix(ghidra): commit analysis profiles to the bridge script sources Snapshot reuse for Ghidra depends on the analysis profile digest, but the profile committed only to hand-bumped semantic markers. A bridge change that forgot to bump its marker left earlier snapshots admissible (#3664). Add ghidraBridgeSourcesDigest, a SHA-256 over every packaged bridge source by name, and commit it as bridge_sources_sha256 in every Ghidra profile, for new imports and existing projects alike. A changed script now changes the profile digest without anyone remembering a marker. Existing snapshots are not reused once, after upgrading. GhidraRawProfileDigest.test.ts pinned literal digests that every bridge or marker change had to re-record; it now checks the committed parameters and that the digest is the profile owner's digest of them. A new test checks that the source list covers every packaged .java file. * fix(ghidra): frame bridge digest metadata as a tuple
94 lines
3.8 KiB
TypeScript
94 lines
3.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { parseConfig } from "../config/parseConfig.js";
|
|
import type { BinaryTarget } from "../domain/binaryTargetTypes.js";
|
|
import { silentLogger } from "../logger.js";
|
|
import { ghidraProfileInstallationHost as host } from "./GhidraInstallation.fixture.js";
|
|
import { GhidraProvider } from "./GhidraProvider.js";
|
|
import { ghidraBridgeSourcesDigest } from "./GhidraBridgeSources.js";
|
|
import { createAnalysisProfile } from "../domain/analysisProfile.js";
|
|
|
|
// A profile digest is the digest of its committed parameters; these tests pin
|
|
// the parameters (semantic revisions and the bridge sources) rather than a
|
|
// literal hash that every bridge or marker change would have to re-record.
|
|
const expectCommittedProfile = (
|
|
profile: ReturnType<typeof createAnalysisProfile> | null | undefined,
|
|
): void => {
|
|
if (profile == null) throw new Error("Expected an analysis profile");
|
|
expect(profile.parameters).toMatchObject({
|
|
bridge_sources_sha256: ghidraBridgeSourcesDigest(),
|
|
});
|
|
expect(profile.digest).toBe(
|
|
createAnalysisProfile(profile.provider, profile.parameters).digest,
|
|
);
|
|
};
|
|
|
|
const configFor = (environment: NodeJS.ProcessEnv) => {
|
|
const parsed = parseConfig({ GHIDRA_INSTALL_DIR: "/ghidra", ...environment });
|
|
if (!parsed.ok) throw parsed.error;
|
|
return parsed.value;
|
|
};
|
|
|
|
const raw = (architecture: "6502" | "x86", entryPoint: string): BinaryTarget =>
|
|
({
|
|
path: "/tmp/raw.bin",
|
|
sha256: "a".repeat(64),
|
|
kind: "executable",
|
|
format: "raw-binary",
|
|
architecture,
|
|
availableArchitectures: [architecture],
|
|
entryPoint,
|
|
entryPointSource: entryPoint,
|
|
}) as unknown as BinaryTarget;
|
|
|
|
describe("raw-binary analysis profile commitments", () => {
|
|
it("commits the 6502 raw profile to the current shared policy", async () => {
|
|
const ghidra = new GhidraProvider(configFor({}), silentLogger, {}, host);
|
|
const resolved = await ghidra.resolveAnalysisProfile(raw("6502", "0x8001"));
|
|
if (!resolved.ok) throw resolved.error;
|
|
expectCommittedProfile(resolved.value.profile);
|
|
expect(resolved.value.profile?.parameters).toMatchObject({
|
|
target_format: "raw-binary",
|
|
loader: "BinaryLoader",
|
|
language_id: "6502:LE:16:default",
|
|
language_selection: "caller-supplied-v1",
|
|
load_image_evidence: "independent-raw-mapping-context-v4",
|
|
load_image_observations:
|
|
"source-mappings-entry-context-relocation-symbol-v3",
|
|
instruction_status: "unlisted-and-containing-code-data-v3",
|
|
inventory_search_projection: "provider-inventory-facts-v1",
|
|
value_flow_effect_scope: "pcode-load-store-explicit-v1",
|
|
native_value_flow_projection: "accepted-runtime-budgets-v1",
|
|
byte_search_boundary: "contiguous-eligible-initialized-blocks-v1",
|
|
byte_search_coverage: "separate-loaded-overlays-v1",
|
|
data_type_observations: "bit-field-base-and-lookup-v1",
|
|
instruction_references:
|
|
"exact-address-incoming-without-synthetic-entry-v3",
|
|
analyzer_preset: "ghidra-default",
|
|
});
|
|
});
|
|
|
|
it("keeps the disabled-analyzer raw digest and its analysis options", async () => {
|
|
const ghidra = new GhidraProvider(
|
|
configFor({
|
|
REA_GHIDRA_DISABLED_ANALYZERS: "Aggressive Instruction Finder",
|
|
}),
|
|
silentLogger,
|
|
{},
|
|
host,
|
|
);
|
|
const resolved = await ghidra.resolveAnalysisProfile(raw("x86", "0x1000"));
|
|
if (!resolved.ok) throw resolved.error;
|
|
expectCommittedProfile(resolved.value.profile);
|
|
expect(resolved.value.profile?.parameters).toMatchObject({
|
|
inventory_search_projection: "provider-inventory-facts-v1",
|
|
value_flow_effect_scope: "pcode-load-store-explicit-v1",
|
|
native_value_flow_projection: "accepted-runtime-budgets-v1",
|
|
byte_search_coverage: "separate-loaded-overlays-v1",
|
|
analysis_options: {
|
|
disabled_analyzers: ["Aggressive Instruction Finder"],
|
|
},
|
|
});
|
|
});
|
|
});
|