I am building Ouroboros (ouro-jail), a contained developer workload runner. Before advertising native macOS support, I need a supported lifetime boundary that covers ordinary fork/spawn/exec descendants, including detached sessions and double-fork daemons. Execution must stay contained when every userspace custodian dies, rather than relying on a surviving process to enumerate and kill descendants.
Does macOS provide a supported facility that can:
Establish an immutable workload scope before its first instruction and atomically inherit membership through fork/spawn/exec, without modifying the workload or preventing ordinary Node, shell and Git subprocess behavior?
Stop or terminate every member after loss of the final control owner, with enforcement retained in the kernel and no userspace callback required?
Handle concurrent creation during teardown and expose a verifiable final drain, using stable process identities rather than PID-only scans?
Development evidence on macOS 27.0 build 26A428, XNU 13432.1.9 (isolated VM, SIP already disabled in the guest; this is development evidence, not normal-SIP qualification):
Descendant-scoped Endpoint Security clients observe the tested subtree. Reciprocal custodians handle either client's loss, but killing both leaves all known members running. Uncached fail-closed AUTH_EXEC lets a survivor execute a new command after both clients disappear (200 reciprocal-custody trials).
PT_ATTACHEXC gives kernel-enforced debugger-death cleanup for attached targets, but later fork/spawn children survive (80 debugger-lifetime trials).
A locally installed, identity-protected Mach exception handler is inherited through the tested fork/spawn/exec paths. Owner death changes it to a dead handler without stopping normal execution; a child can clear it. Breakpoint controls confirm real kernel exception delivery (24 Mach-lifetime trials).
Published XNU contains per-task fatal-port no-senders cleanup, but machdep.task_get_fatal_port is absent on this tested release kernel. I have not treated it as a supported application API.
LaunchServices creates a real application coalition, and XPC service lifetime follows app death. However, all seven workload descendants survive, retain coalition membership and can execute a new command. Shared and private audit sessions both fail; eight-second follow-ups also retain all seven survivors. A temporary root launchd service restarts and reaps seven recorded processes after a crash, but does not restart a stopped custodian (native-service trials).
I understand Endpoint Security entitlement is separate from a lifetime guarantee. Please clarify whether an approved descendant-scoped ES deployment can provide that owner-loss guarantee through a documented API, whether another supported facility is appropriate, or whether meeting this contract requires a VM boundary.
Below are the relevant parts of a focused C command-line reproduction (192 lines total, Xcode 27.0, SDK 27.0, -Wall -Wextra -Werror, no signing or third-party libraries). The code ran in the isolated VM; the packaged Xcode project has only been build-checked.
1. The owner installs a protected exception handler on itself, then forks the workload. No cleanup callback follows.
static const exception_mask_t masks = EXC_MASK_SYSCALL | EXC_MASK_MACH_SYSCALL | EXC_MASK_BREAKPOINT;
static const exception_behavior_t behavior = EXCEPTION_IDENTITY_PROTECTED | MACH_EXCEPTION_CODES;
mach_port_t channel = MACH_PORT_NULL;
mach_port_options_t options = {.flags = MPO_EXCEPTION_PORT | MPO_INSERT_SEND_RIGHT};
kern_return_t rc = mach_port_construct(mach_task_self(), &options, 0, &channel);
if (rc != KERN_SUCCESS) return 4;
rc = task_set_exception_ports(mach_task_self(), masks, channel, behavior, THREAD_STATE_NONE);
if (rc != KERN_SUCCESS) return 4;
pid_t child = fork();
if (!child) {
/* Ordinary receive rights are not inherited. Only the kernel's task
* exception-handler state provides supervision of this child. */
_exit(payload(directory, mode, exe));
}
2. The workload detaches (double-fork shown; fork, setsid and posix_spawn SETPGROUP/SETSID also tested; a child can also clear the handler with task_set_exception_ports(..., MACH_PORT_NULL, ...)).
child = fork();
if (!child) {
if (strcmp(mode, "fork") && setsid() < 0) _exit(4);
if (!strcmp(mode, "double-fork")) {
pid_t second = fork(); if (second < 0) _exit(4); if (second) _exit(0);
}
child_work(directory, mode);
}
3. Each member reads its inherited handler state and keeps running ordinary BSD syscalls and a Mach trap.
kern_return_t rc = task_get_exception_ports(mach_task_self(), masks, observed, &count,
ports, behaviors, flavors);
/* port == MACH_PORT_DEAD -> dead_mask, valid send right -> live_mask */
for (;;) {
if (write(fd, ".", 1) != 1) _exit(3); /* heartbeat file */
(void)getpid();
mach_port_t thread = mach_thread_self();
mach_port_deallocate(mach_task_self(), thread);
usleep(10000);
}
Observed output (double-fork case, original VM run). Workload, before and after SIGKILL of the owner (pid 1472):
{"kind":"exception_state","role":"root","phase":"before_owner_loss","rc":0,"live_mask":448,"dead_mask":0}
{"kind":"exception_state","role":"late-child","phase":"before_owner_loss","rc":0,"live_mask":448,"dead_mask":0}
{"kind":"exception_state","role":"root","phase":"after_owner_loss","rc":0,"live_mask":0,"dead_mask":448}
{"kind":"exception_state","role":"late-child","phase":"after_owner_loss","rc":0,"live_mask":0,"dead_mask":448}
After owner loss, both root (pid 1473) and the detached late-child (pid 1475, own session) were still alive under the same uniqueid/pidversion, reparented to launchd, with heartbeat files still growing. Across 21 owner-death trials in 7 modes, 39 known fixture processes survived and no case left the fixture empty. The 3 breakpoint controls (owner alive) confirmed real exception delivery.
Expected contract: a documented kernel-owned boundary prevents continued execution by every inherited member after all control owners disappear, without enumerating descendants after the fact or requiring workload cooperation. I am asking which supported macOS API meets that contract, not asserting that exception ports promise it.
Apple DTS asked me to start this thread (Case-ID: 22799543) and will review once I reply with the link.
The full reproduction steps and environment details are in the attached text.
forum-attachment-reproduction.txt
1
0
54