Files
reverse-engineering-anything/src/ghidra/GhidraRawProfileDigest.test.ts
T
morluto 3fbdf0057b fix(ghidra): commit analysis profiles to the bridge script sources (#10562)
* 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
2026-11-14 14:51:19 -05:00

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"],
},
});
});
});