test(filesystem): probe actual permission denial before host assertions

This commit is contained in:
morluto
2026-10-24 02:55:53 +08:00
parent 63370e9300
commit d0375a1ed0
7 changed files with 102 additions and 20 deletions
+7 -2
View File
@@ -897,8 +897,13 @@ After import, deleting that source must preserve the captured database identity.
Instruction inspection and containing-function lookup also agree across
hexadecimal case, leading zeros, and encoded default address-space spellings.
The same source-acquisition workflow exercises missing and directory-replaced
inputs, plus real read-permission denial on a non-root host. Root runs report
that permission-denial check as unverified. Filesystem integration separately checks selected-platform routing and exclusive creation.
inputs, plus real read-permission denial on a host that enforces it. Permission
fixtures must probe the relevant read, write or search operation before relying
on POSIX mode bits: a non-root Linux process may bypass DAC through capabilities.
`requirePermissionDenial` reports an explicit skip when the host permits the
probe; it restores the probe's permissions before skipping. Such a skip leaves
real permission coverage unverified. Filesystem integration separately checks
selected-platform routing and exclusive creation.
Namespace annotation probes compile a separate host C fixture with C++ ABI
symbols, avoiding a C++ runtime prerequisite. Real Ghidra demangling supplies
duplicate leaf names in two top-level namespaces and a nested namespace. The
@@ -1,3 +1,4 @@
import { requirePermissionDenial } from "../../../tests/fixtures/permissionDenial.js";
import {
appendFile,
chmod,
@@ -167,9 +168,10 @@ it("uses confirmed publication after the output owner's descriptor cleanup compl
}
});
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
it.skipIf(process.platform === "win32")(
"returns a tagged permission refusal for an existing read-only empty directory",
async () => {
async (context) => {
await requirePermissionDenial(context, "directory-write");
const root = await createTestTempDirectory("rea-bundle-readonly-");
const files = await writeEnvironmentServeFiles(root);
const inputs = await readEnvironmentExportInputs(
@@ -200,9 +202,10 @@ it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
},
);
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
it.skipIf(process.platform === "win32")(
"names the nested path when write permission is lost after earlier files",
async () => {
async (context) => {
await requirePermissionDenial(context, "directory-write");
const root = await createTestTempDirectory("rea-bundle-later-denied-");
const files = await writeEnvironmentServeFiles(root);
const inputs = await readEnvironmentExportInputs(
+4 -2
View File
@@ -1,3 +1,4 @@
import { requirePermissionDenial } from "../../tests/fixtures/permissionDenial.js";
import { createHash } from "node:crypto";
import {
access,
@@ -129,9 +130,10 @@ const resolveHopperMachOImage = (
resourceScope: inspectionResources.owner,
});
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
it.skipIf(process.platform === "win32")(
"reports a FAT64 source that became unreadable as a host read denial",
async () => {
async (context) => {
await requirePermissionDenial(context, "file-read");
const directory = await createTestTempDirectory("rea-hopper-fat64-");
const path = join(directory, "universal");
await writeFile(path, Buffer.alloc(64));
+4 -2
View File
@@ -1,3 +1,4 @@
import { requirePermissionDenial } from "../../tests/fixtures/permissionDenial.js";
import { execFile } from "node:child_process";
import { once } from "node:events";
import {
@@ -75,9 +76,10 @@ describe.skipIf(process.platform === "win32")(
expect((await lstat(alias)).isSymbolicLink()).toBe(true);
});
it.skipIf(process.getuid?.() === 0)(
it.skipIf(process.platform === "win32")(
"explains unreadable directory recovery without changing unverified permissions",
async () => {
async (context) => {
await requirePermissionDenial(context, "directory-read");
const directory = await temporaryDirectory();
await chmod(directory, 0o333);
try {
+4 -2
View File
@@ -1,3 +1,4 @@
import { requirePermissionDenial } from "../../tests/fixtures/permissionDenial.js";
import { chmod, mkdir, readdir, realpath, rm, symlink } from "node:fs/promises";
import { dirname, join } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
@@ -93,9 +94,10 @@ describe("IDA workspace ownership before input preparation", () => {
expect(await readdir(root)).toEqual([]);
});
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
it.skipIf(process.platform === "win32")(
"preserves the input identity failure and retries workspace removal after permission recovery",
async () => {
async (context) => {
await requirePermissionDenial(context, "directory-write");
const { root, target } = await createIdaTarget();
roots.push(root);
const { client, producer } = clientFor(root, {
+13 -8
View File
@@ -1,3 +1,4 @@
import { requirePermissionDenial } from "../../../tests/fixtures/permissionDenial.js";
import {
chmod,
mkdir,
@@ -104,9 +105,10 @@ it("distinguishes unavailable content hashes from completed path enumeration", a
).toMatchObject({ status: "deleted", before: { sha256: null }, after: null });
});
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
it.skipIf(process.platform === "win32")(
"retains readable entries and marks an unreadable directory subtree unknown",
async () => {
async (context) => {
await requirePermissionDenial(context, "directory-read");
const root = await createTestTempDirectory("rea-fs-unreadable-directory-");
const visible = join(root, "a-visible.txt");
const blocked = join(root, "z-blocked");
@@ -143,9 +145,10 @@ it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
},
);
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
it.skipIf(process.platform === "win32")(
"retains unreadable-file and sibling observations in a complete snapshot",
async () => {
async (context) => {
await requirePermissionDenial(context, "file-read");
const root = await createTestTempDirectory("rea-fs-unreadable-file-");
const unreadable = join(root, "unreadable.txt");
const readable = join(root, "readable.txt");
@@ -398,9 +401,10 @@ it("preserves creation when the selected root was observed missing before captur
]);
});
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
it.skipIf(process.platform === "win32")(
"rejects only selected roots that cannot be admitted before launch",
async () => {
async (context) => {
await requirePermissionDenial(context, "directory-search");
const root = await createTestTempDirectory("rea-fs-root-admission-");
await writeFile(join(root, "file"), "regular file");
await mkdir(join(root, "denied"));
@@ -466,9 +470,10 @@ it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
},
);
it.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
it.skipIf(process.platform === "win32")(
"records an unsearchable directory's children as omitted rather than failing",
async () => {
async (context) => {
await requirePermissionDenial(context, "directory-search");
const root = await createTestTempDirectory("rea-fs-unsearchable-");
const listed = join(root, "listed");
await mkdir(listed);
+63
View File
@@ -0,0 +1,63 @@
import {
chmod,
mkdir,
readFile,
readdir,
stat,
writeFile,
} from "node:fs/promises";
import { join } from "node:path";
import type { TestContext } from "vitest";
import { createTestTempDirectory } from "./temporaryDirectory.js";
type PermissionOperation =
| "file-read"
| "directory-read"
| "directory-write"
| "directory-search";
/** Probe actual DAC enforcement; UID alone does not account for Linux capabilities. */
export const requirePermissionDenial = async (
context: Pick<TestContext, "skip">,
operation: PermissionOperation,
): Promise<void> => {
if (process.platform === "win32")
context.skip("POSIX mode denial is unavailable on Windows");
const root = await createTestTempDirectory("rea-permission-probe-");
const directory = join(root, "directory");
const file = join(directory, "file");
await mkdir(directory);
await writeFile(file, "probe");
let denied = false;
try {
if (operation === "file-read") {
await chmod(file, 0);
await readFile(file);
} else if (operation === "directory-read") {
await chmod(directory, 0o333);
await readdir(directory);
} else if (operation === "directory-write") {
await chmod(directory, 0o500);
await writeFile(join(directory, "created"), "probe");
} else {
await chmod(directory, 0o444);
await stat(file);
}
} catch (cause) {
if (
!(cause instanceof Error) ||
!("code" in cause) ||
cause.code !== "EACCES"
)
throw cause;
denied = true;
} finally {
await chmod(directory, 0o700);
await chmod(file, 0o600);
}
if (!denied)
context.skip(
`Host bypasses POSIX ${operation} denial; real permission coverage is unverified`,
);
};