From 9a4e6da5a96ed2708a0f628b436dcd7415999327 Mon Sep 17 00:00:00 2001 From: Kaoru0822 Date: Tue, 6 Oct 2026 10:03:55 -0700 Subject: [PATCH] fix(firmware): validate configured executables (#751) --- src/firmware/FirmwareCommand.test.ts | 79 ++++++++++++++++++++++++++++ src/firmware/FirmwareCommand.ts | 28 +++++++--- 2 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 src/firmware/FirmwareCommand.test.ts diff --git a/src/firmware/FirmwareCommand.test.ts b/src/firmware/FirmwareCommand.test.ts new file mode 100644 index 000000000..1ca439b5f --- /dev/null +++ b/src/firmware/FirmwareCommand.test.ts @@ -0,0 +1,79 @@ +import { chmod, mkdir, writeFile } from "node:fs/promises"; +import { join } from "node:path"; + +import { expect, it as test } from "vitest"; + +import { createTestTempDirectory } from "../../tests/fixtures/temporaryDirectory.js"; +import { resolveFirmwareCommand } from "./FirmwareCommand.js"; + +const it = test.skipIf(process.platform !== "linux"); + +it("rejects a directory configured as the firmware engine executable", async () => { + const root = await createTestTempDirectory("rea-firmware-command-"); + const command = join(root, "binwalk"); + const limiter = await executableFixture(root, "prlimit"); + await mkdir(command); + + await expect( + resolveFirmwareCommand( + { + REA_BINWALK_COMMAND: command, + REA_FIRMWARE_PRLIMIT_COMMAND: limiter, + }, + "binwalk", + "inspect_firmware_regions", + ), + ).rejects.toMatchObject({ + _tag: "AnalysisCapabilityUnavailableError", + reason: expect.stringContaining(command), + }); +}); + +it("classifies an unusable firmware engine as an unavailable capability", async () => { + const root = await createTestTempDirectory("rea-firmware-command-"); + const command = join(root, "binwalk"); + const limiter = await executableFixture(root, "prlimit"); + await writeFile(command, "not executable", { mode: 0o600 }); + + await expect( + resolveFirmwareCommand( + { + REA_BINWALK_COMMAND: command, + REA_FIRMWARE_PRLIMIT_COMMAND: limiter, + }, + "binwalk", + "inspect_firmware_regions", + ), + ).rejects.toMatchObject({ + _tag: "AnalysisCapabilityUnavailableError", + reason: expect.stringContaining(command), + }); +}); + +it("rejects a directory configured as the resource limiter executable", async () => { + const root = await createTestTempDirectory("rea-firmware-limiter-"); + const command = await executableFixture(root, "binwalk"); + const limiter = join(root, "prlimit"); + await mkdir(limiter); + + await expect( + resolveFirmwareCommand( + { + REA_BINWALK_COMMAND: command, + REA_FIRMWARE_PRLIMIT_COMMAND: limiter, + }, + "binwalk", + "inspect_firmware_regions", + ), + ).rejects.toMatchObject({ + _tag: "AnalysisCapabilityUnavailableError", + reason: expect.stringContaining(limiter), + }); +}); + +const executableFixture = async (root: string, name: string) => { + const path = join(root, name); + await writeFile(path, "#!/bin/sh\n"); + await chmod(path, 0o700); + return path; +}; diff --git a/src/firmware/FirmwareCommand.ts b/src/firmware/FirmwareCommand.ts index fe8f19f86..85abf068f 100644 --- a/src/firmware/FirmwareCommand.ts +++ b/src/firmware/FirmwareCommand.ts @@ -1,5 +1,5 @@ import { randomUUID } from "node:crypto"; -import { access, realpath } from "node:fs/promises"; +import { access, realpath, stat } from "node:fs/promises"; import { constants } from "node:fs"; import { isAbsolute } from "node:path"; import { @@ -55,7 +55,19 @@ export const resolveFirmwareCommand = async ( `${variable} is unavailable: ${cause instanceof Error ? cause.message : String(cause)}`, ); }); - await access(command, constants.X_OK); + let sha256: string; + try { + await access(command, constants.R_OK | constants.X_OK); + if (!(await stat(command)).isFile()) + throw new TypeError("Configured path is not a regular file"); + sha256 = await hashFirmwareFile(command); + } catch (cause: unknown) { + throw new AnalysisCapabilityUnavailableError( + engine, + operation, + `${variable} is not a readable executable regular file: ${command}: ${cause instanceof Error ? cause.message : String(cause)}`, + ); + } const limiter = environment.REA_FIRMWARE_PRLIMIT_COMMAND ?? "/usr/bin/prlimit"; if (!isAbsolute(limiter)) @@ -64,14 +76,18 @@ export const resolveFirmwareCommand = async ( operation, "REA_FIRMWARE_PRLIMIT_COMMAND must be absolute", ); - await access(limiter, constants.X_OK).catch(() => { + try { + await access(limiter, constants.X_OK); + if (!(await stat(limiter)).isFile()) + throw new TypeError("Configured path is not a regular file"); + } catch (cause: unknown) { throw new AnalysisCapabilityUnavailableError( engine, operation, - `Resource limiter unavailable: ${limiter}; provide util-linux prlimit`, + `Resource limiter is not an executable regular file: ${limiter}: ${cause instanceof Error ? cause.message : String(cause)}; provide util-linux prlimit`, ); - }); - return { command, limiter, sha256: await hashFirmwareFile(command) }; + } + return { command, limiter, sha256 }; }; /** Execute one bounded provider command and verify its owned descendants have stopped. */