fix(firmware): reject malformed UTF-8 producer reports

This commit is contained in:
morluto 2026-10-09 04:40:47 +08:00
parent 2b86ea9c7c
commit 4a45c14742
3 changed files with 51 additions and 3 deletions

View File

@ -129,8 +129,9 @@ tree semantics, vulnerability scanning, emulation or hardware runtime capture.
Unblob one worker. No automatic parallel extraction engines.
- **120-second** deadline; inherited **1 GiB per-process address-space** limit,
120 CPU seconds and zero core-dump allowance through `prlimit`.
- JSON reports: at most **8 MiB**; oversized reports fail without a truncated
success. Diagnostic output is bounded with exact captured byte counts.
- JSON reports: at most **8 MiB** and valid UTF-8; malformed encoding and
oversized reports fail without repairing or truncating producer data.
Diagnostic output is bounded with exact captured byte counts.
- Default staging budget: **256 MiB** and **10,000 entries**, configurable using
`max_output_bytes` / `max_output_files` (CLI `--max-output-bytes` /
`--max-output-files`). Depth defaults to 3, allowed range 1–10.

View File

@ -153,7 +153,11 @@ export const readFirmwareReport = async (
`Report exceeded ${FIRMWARE_LIMITS.reportBytes} bytes`,
);
try {
const value: unknown = JSON.parse(bytes.toString("utf8"));
const value: unknown = JSON.parse(
new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
bytes,
),
);
return value;
} catch (cause: unknown) {
throw new AnalysisOutputError(

View File

@ -0,0 +1,43 @@
import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it } from "vitest";
import { readFirmwareReport } from "../../../src/firmware/FirmwareFiles.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
it.each([
["invalid leading byte", [0xff]],
["incomplete sequence", [0xe2, 0x82]],
["overlong sequence", [0xc0, 0xaf]],
["encoded surrogate", [0xed, 0xa0, 0x80]],
])(
"rejects complete JSON containing %s without repairing producer bytes",
async (_name, bytes) => {
const root = await createTestTempDirectory("rea-firmware-report-");
const path = join(root, "report.json");
await writeFile(
path,
Buffer.concat([
Buffer.from('{"description":"'),
Buffer.from(bytes),
Buffer.from('"}'),
]),
);
await expect(
readFirmwareReport(path, "inspect_firmware_regions"),
).rejects.toMatchObject({
_tag: "AnalysisOutputError",
reason: "Tool report is not valid complete JSON",
});
},
);
it("preserves valid Unicode and a literal replacement character in producer JSON", async () => {
const root = await createTestTempDirectory("rea-firmware-report-");
const path = join(root, "report.json");
const report = { description: "固件 🧩 \ufffd", path: "/输入/firmware.bin" };
await writeFile(path, JSON.stringify(report));
expect(await readFirmwareReport(path, "inspect_firmware_regions")).toEqual(
report,
);
});