mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
test(filesystem): probe actual permission denial before host assertions
This commit is contained in:
+7
-2
@@ -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(
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Vendored
+63
@@ -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`,
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user