mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
fix(ghidra): attribute import policy conflicts to settings
This commit is contained in:
@@ -15,7 +15,10 @@ snapshot parser and a separate optional annotation extension. Check the
|
||||
[release boundary](installation.md#released-package-and-main) when using npm.
|
||||
|
||||
See [import language and analysis seeds](installation.md#ghidra-import-language-and-analysis-seeds)
|
||||
to override the header-detected language or seed analysis. An inaccessible language
|
||||
to override the header-detected language or seed analysis. An override that conflicts with a fixed raw, DOS, PSP, MIPS or RISC-V import
|
||||
is a configuration error naming the conflicting settings. It does not make an
|
||||
otherwise admitted target unsupported; REA preserves the requested settings.
|
||||
An inaccessible language
|
||||
catalog remains a read failure; it does not prove that a configured language is
|
||||
undefined. Installation `application.properties` has a fixed 1 MiB read allowance
|
||||
with no runtime override. The failure retains that allowance and directs recovery
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisUnsupportedTargetError,
|
||||
} from "../domain/analysisErrorCore.js";
|
||||
import { ConfigurationError } from "../domain/configurationErrors.js";
|
||||
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import { err, ok, type Result } from "../domain/result.js";
|
||||
@@ -74,6 +75,24 @@ export const ghidraPinnedLanguage = (
|
||||
ghidraRiscvLanguageOverride(target) ??
|
||||
ghidraPowerPcLanguage(target);
|
||||
|
||||
/** Policy conflicts belong to the selected settings, not to the target's support. */
|
||||
const languageOverrideConflict = (
|
||||
override: GhidraLanguageOverride,
|
||||
constraint: string,
|
||||
pinned?: GhidraLanguageOverride,
|
||||
): ConfigurationError =>
|
||||
new ConfigurationError(constraint, {
|
||||
settings: [
|
||||
...(pinned?.languageId === override.languageId
|
||||
? []
|
||||
: [{ setting: "REA_GHIDRA_LANGUAGE_ID", constraint }]),
|
||||
...(override.compilerSpecId === undefined ||
|
||||
pinned?.compilerSpecId === override.compilerSpecId
|
||||
? []
|
||||
: [{ setting: "REA_GHIDRA_COMPILER_SPEC_ID", constraint }]),
|
||||
],
|
||||
});
|
||||
|
||||
/** Resolve version-bound, deterministic semantics before Ghidra imports a target. */
|
||||
export const resolveGhidraAnalysisProfile = async (
|
||||
target: BinaryTarget,
|
||||
@@ -108,10 +127,9 @@ export const resolveGhidraAnalysisProfile = async (
|
||||
// A caller-selected generic processor cannot override this verified PSP loader.
|
||||
if (isGhidraPspTarget(target) && languageOverride !== undefined)
|
||||
return err(
|
||||
new AnalysisUnsupportedTargetError(
|
||||
"resolve_analysis_profile",
|
||||
target.path,
|
||||
"The bounded PSP Allegrex profile does not accept REA_GHIDRA_LANGUAGE_ID overrides.",
|
||||
languageOverrideConflict(
|
||||
languageOverride,
|
||||
"The bounded PSP Allegrex profile does not accept processor or compiler specification overrides.",
|
||||
),
|
||||
);
|
||||
const pspExtension = isGhidraPspTarget(target)
|
||||
@@ -132,10 +150,9 @@ export const resolveGhidraAnalysisProfile = async (
|
||||
if (languageOverride !== undefined)
|
||||
return Promise.resolve(
|
||||
err(
|
||||
new AnalysisUnsupportedTargetError(
|
||||
"resolve_analysis_profile",
|
||||
target.path,
|
||||
"REA_GHIDRA_LANGUAGE_ID cannot override the admitted raw-binary import.",
|
||||
languageOverrideConflict(
|
||||
languageOverride,
|
||||
"Processor or compiler specification overrides cannot replace the admitted raw-binary import.",
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -187,14 +204,14 @@ export const resolveGhidraAnalysisProfile = async (
|
||||
languageOverride.compilerSpecId !== pinnedLanguage.compilerSpecId))
|
||||
)
|
||||
return err(
|
||||
new AnalysisUnsupportedTargetError(
|
||||
"resolve_analysis_profile",
|
||||
target.path,
|
||||
languageOverrideConflict(
|
||||
languageOverride,
|
||||
`The caller-selected processor or compiler specification is outside the verified ${
|
||||
target.architecture === "riscv"
|
||||
? "generic little-endian RISC-V"
|
||||
: "endian-matched MIPS32"
|
||||
} profile. The requested override has not been changed.`,
|
||||
pinnedLanguage,
|
||||
),
|
||||
);
|
||||
const selectedLanguage = languageOverride ?? pinnedLanguage;
|
||||
@@ -202,10 +219,9 @@ export const resolveGhidraAnalysisProfile = async (
|
||||
if (dos && languageOverride !== undefined)
|
||||
return Promise.resolve(
|
||||
err(
|
||||
new AnalysisUnsupportedTargetError(
|
||||
"resolve_analysis_profile",
|
||||
target.path,
|
||||
"REA_GHIDRA_LANGUAGE_ID cannot override the admitted DOS real-mode import.",
|
||||
languageOverrideConflict(
|
||||
languageOverride,
|
||||
"Processor or compiler specification overrides cannot replace the admitted DOS real-mode import.",
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -399,6 +399,32 @@ describe("Ghidra language override profile", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["raw-binary", "dos-mz", "dos-com"] as const)(
|
||||
"attributes a fixed %s import override conflict to the named settings",
|
||||
async (format) => {
|
||||
const target: BinaryTarget =
|
||||
format === "raw-binary"
|
||||
? { ...pe, format, entryPoint: "0x0", entryPointSource: "0x0" }
|
||||
: { ...pe, format };
|
||||
const resolved = await provider({
|
||||
REA_GHIDRA_LANGUAGE_ID: "x86:LE:32:default",
|
||||
REA_GHIDRA_COMPILER_SPEC_ID: "borlandcpp",
|
||||
}).resolveAnalysisProfile(target);
|
||||
if (resolved.ok) throw new Error("Expected an override policy conflict");
|
||||
expect(projectAnalysisError(resolved.error)).toMatchObject({
|
||||
code: "configuration_invalid",
|
||||
details: {
|
||||
settings: [
|
||||
{ setting: "REA_GHIDRA_LANGUAGE_ID" },
|
||||
{ setting: "REA_GHIDRA_COMPILER_SPEC_ID" },
|
||||
],
|
||||
},
|
||||
});
|
||||
const automatic = await provider({}).resolveAnalysisProfile(target);
|
||||
expect(automatic.ok).toBe(true);
|
||||
},
|
||||
);
|
||||
|
||||
it("compares committed and configured selections exactly", () => {
|
||||
expect(sameGhidraLanguageOverride(undefined, undefined)).toBe(true);
|
||||
expect(
|
||||
|
||||
@@ -514,10 +514,15 @@ describe("MIPS16 shared-image profile", () => {
|
||||
const ghidra = new GhidraProvider(parsed.value, silentLogger, {}, host);
|
||||
const resolved = await ghidra.resolveAnalysisProfile(shared());
|
||||
expect(resolved.ok).toBe(false);
|
||||
if (!resolved.ok)
|
||||
if (!resolved.ok) {
|
||||
expect(resolved.error.message).toContain(
|
||||
"requested override has not been changed",
|
||||
);
|
||||
expect(resolved.error).toMatchObject({
|
||||
_tag: "ConfigurationError",
|
||||
settings: [{ setting: "REA_GHIDRA_LANGUAGE_ID" }],
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -192,6 +192,22 @@ describe("bounded Ghidra RISC-V admission", () => {
|
||||
expect(resolved.ok).toBe(false);
|
||||
if (resolved.ok) throw new Error("expected a refusal");
|
||||
expect(resolved.error.message).toContain("generic little-endian RISC-V");
|
||||
expect(resolved.error).toMatchObject({
|
||||
_tag: "ConfigurationError",
|
||||
settings: [{ setting: "REA_GHIDRA_LANGUAGE_ID" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("attributes a compiler-only pin conflict to its setting", async () => {
|
||||
const resolved = await ghidra({
|
||||
REA_GHIDRA_LANGUAGE_ID: "RISCV:LE:64:default",
|
||||
REA_GHIDRA_COMPILER_SPEC_ID: "windows",
|
||||
}).resolveAnalysisProfile(target());
|
||||
if (resolved.ok) throw new Error("expected a configuration conflict");
|
||||
expect(resolved.error).toMatchObject({
|
||||
_tag: "ConfigurationError",
|
||||
settings: [{ setting: "REA_GHIDRA_COMPILER_SPEC_ID" }],
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps a caller-selected language that matches the pin as configured", async () => {
|
||||
|
||||
Reference in New Issue
Block a user