fix(firmware): validate configured executables (#751)
This commit is contained in:
parent
fb679b63c2
commit
9a4e6da5a9
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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. */
|
||||
|
|
|
|||
Loading…
Reference in New Issue