fix(sandbox): harden the per-session record and the ACL runner failure paths (review round v6)
Durable record: bound to the owning session id and validated at the fold (orphan-SID shape, temp path inside the host temp root) — a fork's copied parent record no longer provisions the child, and a tampered record fails loud. Private temp dir: random unguessable name persisted in the record, created exclusively (pre-existing entries and reparse points fail EEXIST). Persistence: a fresh provision kicks an immediate flush (no write-behind debounce), narrowing the crash window to the flush latency — documented as the one self-healing gap. Runner-failure rules: exit-gated on 127 so a confined command that prints the signature on a non-127 exit is never misclassified. Spawn: AssignProcessToJobObject failure terminates the suspended child (no hanging orphans). SandboxExecutionPolicy.sessionId is the branded SessionId. Boundary docs: qualifying clause on the absolutist sentences, NULL-DACL Known Limitation, 'full' scoped to the supported NTFS surface, CLM gate comment.
This commit is contained in:
@@ -123,6 +123,9 @@ export interface Win32Bindings {
|
||||
createJobObjectW(attributes: null, name: null): NativePtr
|
||||
setInformationJobObject(job: NativePtr, cls: number, information: Buffer, length: number): number
|
||||
assignProcessToJobObject(job: NativePtr, process: NativePtr): number
|
||||
// Terminate a suspended child that could not be placed in the kill-on-close
|
||||
// job — closing handles alone would leave it hanging forever.
|
||||
terminateProcess(process: NativePtr, exitCode: number): number
|
||||
// ---- console -------------------------------------------------------------
|
||||
// HandlerRoutine=null + add=1 makes this process ignore CTRL+C (wincon.h):
|
||||
// the runner survives console Ctrl+C so the child handles its own and the
|
||||
@@ -417,6 +420,7 @@ function bindings(): Win32Bindings {
|
||||
createJobObjectW: bind(kernel32, 'CreateJobObjectW', PVOID, [PVOID, 'str16']),
|
||||
setInformationJobObject: bind(kernel32, 'SetInformationJobObject', 'int', [PVOID, 'int', PVOID, 'uint32']),
|
||||
assignProcessToJobObject: bind(kernel32, 'AssignProcessToJobObject', 'int', [PVOID, PVOID]),
|
||||
terminateProcess: bind(kernel32, 'TerminateProcess', 'int', [PVOID, 'uint32']),
|
||||
setConsoleCtrlHandler: bind(kernel32, 'SetConsoleCtrlHandler', 'int', [PVOID, 'int']),
|
||||
getStdHandle: bind(kernel32, 'GetStdHandle', PVOID, ['int']),
|
||||
} as unknown as Win32Bindings
|
||||
|
||||
@@ -5,8 +5,14 @@
|
||||
* token whose restricting SIDs include an orphan SID (`S-1-4-x-y`) that only
|
||||
* this sandbox instance adds to the target directories' DACLs — the
|
||||
* intersection check then allows writes exactly where that SID has a Write
|
||||
* ACE, and nowhere else. Unlike the POC, every API failure throws with the
|
||||
* API name and exact Win32 code; a child is NEVER spawned unrestricted.
|
||||
* ACE, and nowhere else the orphan SID is concerned (the token's write check
|
||||
* ALSO inherits the ambient write ACEs of the other restricting SIDs — the
|
||||
* keep-alive group logon SID + Everyone; Authenticated Users,
|
||||
* INTERACTIVE, and LOCAL are absent from both lists — see the seam's
|
||||
* dual-list contract in `packages/sandbox/sandbox-local` and the package
|
||||
* README's Modes section for the complete boundary). Unlike the POC, every
|
||||
* API failure throws with the API name and exact Win32 code; a child is
|
||||
* NEVER spawned unrestricted.
|
||||
*
|
||||
* Known boundaries (inherent to restricted tokens, not this port):
|
||||
* - writes are restricted; reads, network, and process visibility are NOT
|
||||
|
||||
@@ -331,7 +331,11 @@ export function spawnSandboxedInherited(
|
||||
}
|
||||
|
||||
if (api.assignProcessToJobObject(job, processHandle) === 0) {
|
||||
// The child was created suspended and is NOT in the kill-on-close job:
|
||||
// closing handles would leave it suspended forever. Terminate it first,
|
||||
// then drop the handles and throw.
|
||||
const win32Code = api.getLastError()
|
||||
api.terminateProcess(processHandle, 1)
|
||||
api.closeHandle(threadHandle)
|
||||
api.closeHandle(processHandle)
|
||||
api.closeHandle(job)
|
||||
|
||||
Reference in New Issue
Block a user