mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(firmware): recommend the operation-specific engine (#8668)
This commit is contained in:
1 parent
2d11fd92d2
commit
510e83eadc
7 files changed
+87
-9
No files matched your search
@@ -26,6 +26,9 @@ REA does not install these tools, their dependencies or system packages. It
|
||||
checks the tool's `--version` banner when executing a request. Binwalk must
|
||||
print `binwalk <version>` and Unblob must print the version alone. Inspection and
|
||||
extraction have independent prerequisites; one does not require the other.
|
||||
Capability discovery recommends `REA_BINWALK_COMMAND` for missing inspection
|
||||
and `REA_UNBLOB_COMMAND` for missing extraction. Configuring the other engine
|
||||
does not enable the unavailable operation.
|
||||
Both require util-linux `prlimit`, normally `/usr/bin/prlimit`; override its
|
||||
absolute path with `REA_FIRMWARE_PRLIMIT_COMMAND` if needed. `rea doctor` does
|
||||
not currently inspect firmware readiness. Engine selection/version checks run
|
||||
|
||||
@@ -880,6 +880,62 @@ describe("trace_feature requirements", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("firmware operation recovery", () => {
|
||||
it.each([
|
||||
[false, false],
|
||||
[true, false],
|
||||
[false, true],
|
||||
[true, true],
|
||||
])(
|
||||
"keeps inspection=%s and extraction=%s independent",
|
||||
(inspection, extraction) => {
|
||||
const inventory = buildCapabilityInventory(status(), {
|
||||
processCaptureEnabled: false,
|
||||
firmwareInspectionEnabled: inspection,
|
||||
firmwareExtractionEnabled: extraction,
|
||||
});
|
||||
for (const [name, enabled, command, otherCommand] of [
|
||||
[
|
||||
"inspect_firmware_regions",
|
||||
inspection,
|
||||
"REA_BINWALK_COMMAND",
|
||||
"REA_UNBLOB_COMMAND",
|
||||
],
|
||||
[
|
||||
"extract_firmware",
|
||||
extraction,
|
||||
"REA_UNBLOB_COMMAND",
|
||||
"REA_BINWALK_COMMAND",
|
||||
],
|
||||
] as const) {
|
||||
const tool = inventory.find((candidate) => candidate.name === name);
|
||||
expect(tool).toMatchObject({
|
||||
available: enabled,
|
||||
reason: enabled ? "available" : "provider_missing",
|
||||
});
|
||||
if (enabled) {
|
||||
expect(tool?.remediation).toBeNull();
|
||||
} else {
|
||||
expect(tool?.remediation).toContain(command);
|
||||
expect(tool?.remediation).not.toContain(otherCommand);
|
||||
expect(tool?.remediation).toContain("prlimit");
|
||||
expect(tool?.remediation).toContain("procps ps");
|
||||
expect(tool?.remediation?.includes("external extractor")).toBe(
|
||||
name === "extract_firmware",
|
||||
);
|
||||
}
|
||||
}
|
||||
const byName = (left: ToolAvailability, right: ToolAvailability) =>
|
||||
left.name.localeCompare(right.name);
|
||||
expect(
|
||||
expandToolAvailability(compactToolAvailability(inventory)).toSorted(
|
||||
byName,
|
||||
),
|
||||
).toEqual(inventory.toSorted(byName));
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const HOST_RESTRICTED_TOOLS = [
|
||||
["inspect_packet_capture", "packetCapture", "packetCaptureEnabled"],
|
||||
["inspect_packet_fields", "packetCapture", "packetCaptureEnabled"],
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { PCAP_CAPTURE_UNSUPPORTED_HOST_REASON } from "../domain/pcap/pcapAnalysis.js";
|
||||
import { DEFAULT_CALL_RELATIONSHIP_DIRECTION } from "../contracts/enhancedInputs.js";
|
||||
import { FIRMWARE_TOOL_REQUIREMENTS } from "../contracts/firmware/firmwareToolRequirements.js";
|
||||
import {
|
||||
FIRMWARE_OPERATION_ENGINES,
|
||||
FIRMWARE_TOOL_REQUIREMENTS,
|
||||
} from "../contracts/firmware/firmwareToolRequirements.js";
|
||||
import { CALL_RELATIONSHIP_TRAVERSALS } from "./CallRelationshipTraversal.js";
|
||||
import { NATIVE_DISPATCH_METADATA_SOURCES } from "./native/NativeDispatchMetadataInspection.js";
|
||||
import { NATIVE_UI_ACTION_OPERATIONS } from "./native/NativeUiActionTrace.js";
|
||||
@@ -595,15 +598,19 @@ const workflowAvailabilityFor = ({
|
||||
"On Linux x64, set absolute REA_PWNTOOLS_PYTHON to caller-supplied Python with pwntools 4.15.0. No active binary target is required.",
|
||||
);
|
||||
if (kind === "firmware-provider") {
|
||||
const { binwalk, unblob } = FIRMWARE_TOOL_REQUIREMENTS;
|
||||
const inspection = name === "inspect_firmware_regions";
|
||||
const operation = inspection
|
||||
? "inspect_firmware_regions"
|
||||
: "extract_firmware";
|
||||
const requirement =
|
||||
FIRMWARE_TOOL_REQUIREMENTS[FIRMWARE_OPERATION_ENGINES[operation]];
|
||||
return hostRestrictedAvailability(
|
||||
policy,
|
||||
inspection ? "firmwareInspection" : "firmwareExtraction",
|
||||
inspection
|
||||
? policy.firmwareInspectionEnabled
|
||||
: policy.firmwareExtractionEnabled,
|
||||
`On Linux, provide an absolute ${binwalk.commandEnvironment} (${binwalk.name} ${binwalk.line}, verified with ${binwalk.version}) or ${unblob.commandEnvironment} (${unblob.name} ${unblob.line}, verified with ${unblob.version}) path and util-linux prlimit. Extraction also requires the selected format’s external extractor.`,
|
||||
`On Linux, provide an absolute ${requirement.commandEnvironment} (${requirement.requirement}) path, util-linux prlimit and procps ps.${inspection ? "" : " Extraction also requires the selected format’s external extractor."}`,
|
||||
);
|
||||
}
|
||||
if (kind === "android-provider")
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FIRMWARE_OPERATION_ENGINES } from "../contracts/firmware/firmwareToolRequirements.js";
|
||||
import { projectCliAnalysisResult } from "./projectCliAnalysisResult.js";
|
||||
import { z } from "incur";
|
||||
import { FirmwareAnalysisService } from "../application/firmware/FirmwareAnalysisService.js";
|
||||
@@ -36,7 +37,7 @@ export const registerFirmwareCommands = (
|
||||
callerPath,
|
||||
() => service.execute(operation, input, { signal }),
|
||||
service,
|
||||
operation === "inspect_firmware_regions" ? "binwalk" : "unblob",
|
||||
FIRMWARE_OPERATION_ENGINES[operation],
|
||||
operation,
|
||||
signal,
|
||||
).then(projectCliAnalysisResult),
|
||||
|
||||
@@ -5,7 +5,10 @@ import {
|
||||
import type { ToolContract } from "../toolContractTypes.js";
|
||||
import { evidenceResultOf } from "../toolOutputSchemaPrimitives.js";
|
||||
import { toolContractMetadata } from "../toolEffects.js";
|
||||
import { FIRMWARE_TOOL_REQUIREMENTS } from "./firmwareToolRequirements.js";
|
||||
import {
|
||||
FIRMWARE_OPERATION_ENGINES,
|
||||
FIRMWARE_TOOL_REQUIREMENTS,
|
||||
} from "./firmwareToolRequirements.js";
|
||||
|
||||
/** Explicit local firmware observations and extraction, backed by caller-supplied tools. */
|
||||
export const FIRMWARE_TOOL_CONTRACTS = [
|
||||
@@ -13,7 +16,7 @@ export const FIRMWARE_TOOL_CONTRACTS = [
|
||||
name: "inspect_firmware_regions",
|
||||
...toolContractMetadata("inspect_firmware_regions"),
|
||||
kind: "firmware-provider",
|
||||
description: `Inspect signature candidates in a local firmware file with ${FIRMWARE_TOOL_REQUIREMENTS.binwalk.requirement}. Returns offsets, reported lengths, descriptions and raw confidence values. Length validation and runtime addresses remain unknown. No extraction or target execution. Linux, caller-supplied tool, util-linux prlimit and procps ps required; 128 MiB input limit, one worker. Adjust analysis_timeout_ms for the analysis wall-clock budget (default 120000); fixed per-process limits are unchanged.`,
|
||||
description: `Inspect signature candidates in a local firmware file with ${FIRMWARE_TOOL_REQUIREMENTS[FIRMWARE_OPERATION_ENGINES.inspect_firmware_regions].requirement}. Returns offsets, reported lengths, descriptions and raw confidence values. Length validation and runtime addresses remain unknown. No extraction or target execution. Linux, caller-supplied tool, util-linux prlimit and procps ps required; 128 MiB input limit, one worker. Adjust analysis_timeout_ms for the analysis wall-clock budget (default 120000); fixed per-process limits are unchanged.`,
|
||||
inputSchema: firmwareInputSchemas.inspect_firmware_regions,
|
||||
outputSchema: evidenceResultOf(
|
||||
firmwareResultSchemas.inspect_firmware_regions,
|
||||
@@ -29,7 +32,7 @@ export const FIRMWARE_TOOL_CONTRACTS = [
|
||||
name: "extract_firmware",
|
||||
...toolContractMetadata("extract_firmware"),
|
||||
kind: "firmware-provider",
|
||||
description: `Extract caller-selected firmware bytes with ${FIRMWARE_TOOL_REQUIREMENTS.unblob.requirement} into an absent output directory. Returns verified child paths/digests, task derivations, unknown chunks and uncovered selected input ranges, extraction diagnostics and explicit extraction_gaps rows naming unextracted regions, missing extractor dependencies and a continuation_id referencing shared extraction_continuations recovery instructions. Preserves the default sandbox; publishes regular files only. Linux, caller-supplied tool, format-specific extractors, util-linux prlimit and procps ps required. No mounts or extracted-code execution. Select a returned native file with open_binary for Ghidra analysis; decompressed offsets are not original-file addresses.`,
|
||||
description: `Extract caller-selected firmware bytes with ${FIRMWARE_TOOL_REQUIREMENTS[FIRMWARE_OPERATION_ENGINES.extract_firmware].requirement} into an absent output directory. Returns verified child paths/digests, task derivations, unknown chunks and uncovered selected input ranges, extraction diagnostics and explicit extraction_gaps rows naming unextracted regions, missing extractor dependencies and a continuation_id referencing shared extraction_continuations recovery instructions. Preserves the default sandbox; publishes regular files only. Linux, caller-supplied tool, format-specific extractors, util-linux prlimit and procps ps required. No mounts or extracted-code execution. Select a returned native file with open_binary for Ghidra analysis; decompressed offsets are not original-file addresses.`,
|
||||
inputSchema: firmwareInputSchemas.extract_firmware,
|
||||
outputSchema: evidenceResultOf(firmwareResultSchemas.extract_firmware),
|
||||
examples: [
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import type { FirmwareOperation } from "../../domain/firmware/firmwareAnalysis.js";
|
||||
|
||||
/** Derive public support facts from one audited, stable release number. */
|
||||
const defineToolRequirement = (release: {
|
||||
readonly name: string;
|
||||
@@ -39,3 +41,9 @@ export const FIRMWARE_TOOL_REQUIREMENTS = {
|
||||
|
||||
/** Caller-supplied firmware engine selected by the operation. */
|
||||
export type FirmwareEngineName = keyof typeof FIRMWARE_TOOL_REQUIREMENTS;
|
||||
|
||||
/** The engine required by each existing firmware operation, shared by discovery and execution. */
|
||||
export const FIRMWARE_OPERATION_ENGINES = {
|
||||
inspect_firmware_regions: "binwalk",
|
||||
extract_firmware: "unblob",
|
||||
} as const satisfies Readonly<Record<FirmwareOperation, FirmwareEngineName>>;
|
||||
@@ -1,3 +1,4 @@
|
||||
import { FIRMWARE_OPERATION_ENGINES } from "../contracts/firmware/firmwareToolRequirements.js";
|
||||
import { snapshotEnvironment } from "../process/snapshotEnvironment.js";
|
||||
import { assertArtifactCommandPath } from "../process/ArtifactCommandPath.js";
|
||||
import { join } from "node:path";
|
||||
@@ -332,8 +333,7 @@ export class FirmwareProvider implements FirmwareAnalysisPort {
|
||||
const environment = snapshotEnvironment(this.environment);
|
||||
let root: PrivateRuntimeRoot | undefined;
|
||||
let rootLease: AnalysisCleanupLease | undefined;
|
||||
const engineName =
|
||||
request.operation === "inspect_firmware_regions" ? "binwalk" : "unblob";
|
||||
const engineName = FIRMWARE_OPERATION_ENGINES[request.operation];
|
||||
const withCaller = (phase: FirmwareDeadline) =>
|
||||
options?.signal === undefined
|
||||
? phase.signal
|
||||
|
||||
Reference in new issue
Block a user