Bound ordinary Restricted Execution engine socket directories (#503)

This commit is contained in:
Quixos Development Agent 01f26965-84fa-4a79-8069-725682c13065
2026-10-29 12:30:56 +00:00
parent 3bb4324aa0
commit 43fcf6b27a
2 changed files with 120 additions and 13 deletions
+73 -11
View File
@@ -1,12 +1,17 @@
import { spawn } from "node:child_process";
import { randomUUID } from "node:crypto";
import { createServer, request as httpRequest } from "node:http";
import { mkdtemp, readFile, writeFile, rm, open } from "node:fs/promises";
import { mkdtemp, readFile, writeFile, rm, open, lstat, readdir } from "node:fs/promises";
import { constants } from "node:fs";
import { tmpdir } from "node:os";
import { join, dirname } from "node:path";
import { checkedSocketPath } from "../socket-address.mjs";
import {
checkedSocketPath,
socketDirectory,
prepareSocketDirectory,
removeEmptySocketDirectory,
} from "../socket-address.mjs";
const abi = "quixos-transaction-ports-v1";
/** Reserved supervisor faults are not authored domain rejections. A graph
@@ -329,15 +334,57 @@ export async function serveRestrictedRegistry(config, io = process) {
}
await engine.exited;
}
const sameDirectory = async (directory, owner) => {
const current = await lstat(directory);
if (
!owner ||
!current.isDirectory() ||
current.dev !== owner.dev ||
current.ino !== owner.ino ||
current.uid !== owner.uid ||
current.mode & 0o077
)
throw fixedError("RESTRICTED_START_FAILED");
};
// A bounded ordinary-engine parent is private to this admitted lifetime.
// Never close/unlink through a replacement directory or release its lease
// while the original namespace's ownership remains uncertain.
if (engine.socketParent) {
await sameDirectory(engine.socketParent, engine.socketParentOwner);
if (engine.directory) await sameDirectory(engine.directory, engine.directoryOwner);
if (engine.broker) {
const current = await lstat(engine.broker).catch((error) => {
if (error.code !== "ENOENT") throw error;
return null;
});
if (
current &&
(!engine.brokerOwner ||
!current.isSocket() ||
current.dev !== engine.brokerOwner.dev ||
current.ino !== engine.brokerOwner.ino)
)
throw fixedError("RESTRICTED_START_FAILED");
}
}
engine.server?.closeAllConnections();
if (engine.server) await new Promise((resolve) => engine.server.close(resolve));
try {
if (engine.directory) await rm(engine.directory, { recursive: true, force: true });
} finally {
engines.delete(engine);
await engine.release?.();
changed();
if (engine.socketParent) {
await sameDirectory(engine.socketParent, engine.socketParentOwner);
if (engine.directory) await sameDirectory(engine.directory, engine.directoryOwner);
}
if (engine.directory) await rm(engine.directory, { recursive: true, force: true });
if (engine.socketParent) {
await removeEmptySocketDirectory(engine.socketIdentity);
const remaining = await lstat(engine.socketParent).catch((error) => {
if (error.code !== "ENOENT") throw error;
return null;
});
if (remaining) throw fixedError("RESTRICTED_START_FAILED");
}
engines.delete(engine);
await engine.release?.();
changed();
})();
return engine.destroying;
};
@@ -361,13 +408,24 @@ export async function serveRestrictedRegistry(config, io = process) {
() => destroyEngine(engine),
);
if (!call.open) return;
let directoryRoot;
if (config.sandboxIdentity) {
// The privileged helper owns a fixed explicit /run root and requires
// this exact basename. Keep its cross-UID ancestry and identity rules.
for (const filename of ["broker.sock", "engine.sock"])
checkedSocketPath(tmpdir() + "/qx-engine-XXXXXX/" + filename);
directoryRoot = tmpdir();
} else {
engine.socketIdentity = { kind: "restricted-engine", process: process.pid, lifetime: randomUUID() };
directoryRoot = socketDirectory(engine.socketIdentity);
}
engine.directory = await mkdtemp(join(tmpdir(), "qx-engine-"));
for (const filename of ["broker.sock", "engine.sock"])
checkedSocketPath(directoryRoot + "/qx-engine-XXXXXX/" + filename);
if (!config.sandboxIdentity) {
engine.socketParent = await prepareSocketDirectory(engine.socketIdentity);
engine.socketParentOwner = await lstat(engine.socketParent);
if ((await readdir(engine.socketParent)).length) throw fixedError("RESTRICTED_START_FAILED");
}
engine.directory = await mkdtemp(join(directoryRoot, "qx-engine-"));
if (engine.socketParent) engine.directoryOwner = await lstat(engine.directory);
if (!call.open) return;
const broker = checkedSocketPath(join(engine.directory, "broker.sock"));
engine.socket = checkedSocketPath(join(engine.directory, "engine.sock"));
@@ -416,10 +474,14 @@ export async function serveRestrictedRegistry(config, io = process) {
}
});
if (engine.socketParent) engine.broker = broker;
await new Promise((resolve, reject) => {
engine.server.once("error", reject);
engine.server.listen(broker, resolve);
});
if (engine.socketParent) {
engine.brokerOwner = await lstat(broker);
}
const modules = { "entry.mjs": { js: workerSource }, "package.mjs": { js: packageSource } };
for (const module of sdk) modules[module.name] = { js: module.source };
const setting = { registryExport: config.registryExport, exports: config.exports, maxBytes: limits.maxBytes };