Files
reverse-engineering-anything/tests/boundary/filesystem/artifactInventoryDescriptorCleanup.test.ts
T
Kaoru0822andClaude Opus 5.5 0eb3919d69 fix(artifacts): admit raw UDRW disk images (#7106)
* fix(artifacts): admit raw UDRW disk images

Raw (UDRW, UDTO) disk images have no UDIF koly trailer, so target
admission and root inventory both refused them as unsupported binaries.
Recognize disk images in one domain owner (koly trailer, GPT, Apple
partition map, MBR, or an unpartitioned HFS+/HFSX volume header) that
both boundaries consume.

hdiutil reports "has no checksum" for these images; the native reader
now treats that status line as an explicit absent checksum, records the
nonzero verify command, attaches read-only, and reports that integrity
was not verified. Checksum mismatches and unrecognized images are still
refused.

Positional range reads used by these probes now fill until end of file
so short host reads cannot hide a signature.

Fixes #5687

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(artifacts): read disk image signature windows at the recognizer

Target admission passed its first unfilled 4 KiB read to the raw-image
checks, so a short host read could hide a GPT, APM, MBR or HFS+
signature that inventory (which fills its prefix) still observed. The
recognizer now reads its own leading window through the caller's
filling positional read, and target admission fills its format-probe
prefix as well. The controlled short-read regression now covers target
admission alongside inventory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 05:20:24 -07:00

180 lines
6.4 KiB
TypeScript

import { createHash } from "node:crypto";
import type { FileHandle } from "node:fs/promises";
import { lstat, realpath, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect, it, onTestFinished, vi } from "vitest";
import { parseBinaryTarget } from "../../../src/application/BinaryTargetResolver.js";
import { ArtifactProvider } from "../../../src/artifacts/ArtifactProvider.js";
import { classifyAndHashRoot } from "../../fixtures/artifactInventory.js";
import { asarHeaderBytes } from "../../fixtures/asarHeader.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
const injected = vi.hoisted(() => ({
opened: undefined as
| ((path: unknown, handle: FileHandle) => void)
| undefined,
}));
vi.mock("node:fs/promises", async (importOriginal) => {
const actual = await importOriginal<typeof import("node:fs/promises")>();
return {
...actual,
open: async (...args: Parameters<typeof actual.open>) => {
const handle = await actual.open(...args);
injected.opened?.(args[0], handle);
return handle;
},
};
});
it.each([false, true])(
"preserves root admission failure and close authority (invalidated=%s)",
async (invalidated) => {
const root = await createTestTempDirectory("rea-root-admission-cleanup-");
const path = join(root, "payload.bin");
await writeFile(path, "selected bytes");
const client = new ArtifactProvider({}).createClient({
path,
sourcePath: path,
sha256: "0".repeat(64),
kind: "artifact",
format: "javascript",
});
let descriptor: FileHandle | undefined;
let closeAttempts = 0;
let blocked = true;
injected.opened = (openedPath, handle) => {
if (openedPath !== path || descriptor !== undefined) return;
descriptor = handle;
const close = handle.close.bind(handle);
vi.spyOn(handle, "stat").mockRejectedValueOnce(
Object.assign(new Error("primary admission stat failure"), {
code: "EIO",
errno: -5,
syscall: "fstat",
}),
);
vi.spyOn(handle, "close").mockImplementation(async () => {
closeAttempts += 1;
if (invalidated) await close();
if (blocked) throw new Error("descriptor cleanup outcome unavailable");
await close();
});
};
onTestFinished(async () => {
injected.opened = undefined;
blocked = false;
await client.close();
vi.restoreAllMocks();
});
const inventory = await client.execute("inventory_artifact", {});
expect(inventory).toMatchObject({
ok: false,
error: {
reason: "io",
detail: expect.stringContaining("primary admission stat failure"),
cleanup: {
resources: [expect.stringContaining(path)],
reason: "descriptor cleanup outcome unavailable",
},
},
});
expect(descriptor).toBeDefined();
expect((await client.close()).ok).toBe(false);
const attemptsBeforeRecovery = closeAttempts;
blocked = false;
const recovery = await client.close();
expect(recovery.ok).toBe(!invalidated);
expect(closeAttempts).toBe(
invalidated ? attemptsBeforeRecovery : attemptsBeforeRecovery + 1,
);
expect(descriptor?.fd).toBe(-1);
if (!invalidated && !inventory.ok)
expect(inventory.error.cleanupIncomplete).toBe(false);
if (invalidated) expect(closeAttempts).toBe(1);
},
);
it("classifies root ASAR, UDIF and raw DMG signatures across controlled short reads at both boundaries and closes the descriptor", async () => {
const root = await createTestTempDirectory("rea-root-short-read-signature-");
const compactPath = join(root, "compact.asar");
const asarPath = join(root, "whitespace.asar");
const dmgPath = join(root, "trailer.dmg");
const asarBytes = asarHeaderBytes(
`${" ".repeat(9_000)}\t\r\n${JSON.stringify({ files: {} })}`,
);
const dmgBytes = Buffer.alloc(1_024);
const compactBytes = asarHeaderBytes(JSON.stringify({ files: {} }));
dmgBytes.write("koly", dmgBytes.length - 512);
const rawDmgPath = join(root, "raw.dmg");
const rawDmgBytes = Buffer.alloc(4_096);
rawDmgBytes.write("EFI PART", 512, "ascii");
rawDmgBytes.writeUInt32LE(0x0001_0000, 520);
rawDmgBytes.writeUInt32LE(92, 524);
await writeFile(rawDmgPath, rawDmgBytes);
await writeFile(asarPath, asarBytes);
await writeFile(compactPath, compactBytes);
await writeFile(dmgPath, dmgBytes);
let openedPaths: readonly string[] = [];
const descriptors: FileHandle[] = [];
let largestActualRead = 0;
injected.opened = (candidate, handle) => {
if (typeof candidate !== "string" || !openedPaths.includes(candidate))
return;
descriptors.push(handle);
const nativeRead = handle.read.bind(handle);
const readAtMostThreeBytes = async (
buffer: Buffer,
offset: number,
length: number,
position: number | null,
) => {
const boundedLength = Math.min(length, 3);
largestActualRead = Math.max(largestActualRead, boundedLength);
return nativeRead(buffer, offset, boundedLength, position);
};
vi.spyOn(handle, "read").mockImplementation(
readAtMostThreeBytes as FileHandle["read"],
);
};
onTestFinished(() => {
injected.opened = undefined;
vi.restoreAllMocks();
});
for (const [path, bytes, format] of [
[compactPath, compactBytes, "asar"],
[asarPath, asarBytes, "asar"],
[dmgPath, dmgBytes, "dmg"],
[rawDmgPath, rawDmgBytes, "dmg"],
] as const) {
// Target admission opens the canonical path; inventory opens the input.
openedPaths = [path, await realpath(path)];
descriptors.length = 0;
largestActualRead = 0;
const result = await classifyAndHashRoot(path, false, await lstat(path));
expect(result.format).toBe(format);
expect(result.digest?.sha256).toBe(
createHash("sha256").update(bytes).digest("hex"),
);
if (format === "asar")
expect(result.digest?.asarHeader).toMatchObject({ state: "matched" });
expect(largestActualRead).toBeLessThanOrEqual(3);
expect(descriptors).toHaveLength(1);
expect(descriptors[0]?.fd).toBe(-1);
// Target admission must observe the same signature under short reads.
descriptors.length = 0;
largestActualRead = 0;
expect(await parseBinaryTarget(path)).toMatchObject({
ok: true,
value: { format },
});
expect(descriptors.length).toBeGreaterThan(0);
expect(largestActualRead).toBeGreaterThan(0);
expect(largestActualRead).toBeLessThanOrEqual(3);
}
});