Experiment formal browser group ownership amendment proposal 01 - kmosoti/FabricO11y GitHub Wiki
Prospective browser group ownership amendment
Status: private proposal only; no trust tests, finalizer, launch path, native
clock/helper or packaged adapter changed under this proposal. No capability or
runtime control ran. Original browser cleanup failures remain preserved at
the actual C080 registration result,
wiki 724a74a, page SHA-256
75aa8f63089a724963b34a13a59f2915c219ee840e35b51ec41d131cb1350c63.
Pip scope ACK is required before the separate trust registration.
Concrete boundary
At browser head b1e7e83c74ba029c3b377ee047220db93853d578,
tools/ui/live_browser_check.py:385-393 retains only (label, Popen);
its finalizer at 1238-1256 has neither a spawn-bound pidfd nor original process
identity. The literal fixture holds leader/child pidfds privately, but its
namespace at tools/ui/test_role_matrix.py:357-363 supplies only Popen pairs.
An already waited leader cannot be safely rebound from its numeric PGID.
Keep these pairs for the existing sampler; add a private owned_processes
mapping keyed by the exact retained Popen object. A public JSON receipt, an
arbitrary PGID or a freshly opened pidfd after leader wait is never authority.
Verified source and remaining inference
Linux exposes PIDFD_SIGNAL_PROCESS_GROUP since 6.9; the local header
/usr/include/linux/pidfd.h:17 gives value 1 << 2.
Linux pidfd_send_signal documentation
describes group-leader scope. Linux v6.17
kernel/signal.c,
3753-3803 and 3842-3859, obtains the original struct pid from the descriptor
and dispatches flag 4 through PIDTYPE_PGID; it does not first require a live
leader task. Lines 1341-1357 iterate that group's tasks and return ESRCH for an
empty group. Therefore a retained leader descriptor is a source-supported
candidate for after-wait group signaling; actual installed-kernel behavior is
still unverified. Numeric PID reuse cannot redirect this kernel-object lookup.
CPython 3.13.7
_signal source,
1217-1229, forwards the supplied flags directly to the syscall. Missing Python
support, EINVAL, ENOSYS, EBADF or EPERM must refuse; do not infer support from a
header or version string and do not fall back to killpg.
Small production mechanism proposed after actual trust red
- Before releasing a command, create a private session using Popen and a tiny isolated stdlib exec gate. Only its anonymous gate-reader FD is inherited; it waits at most five seconds, bounded by the existing inner deadline, and exits on EOF, timeout or an invalid byte. It does not fork or exec the target before release. Parent pidfds are close-on-exec, remain private and are never passed to the command. The exact argv/environment reaches the target through same-PID exec; no shell or user-supplied executable is used as the gate. This closes the spawn-to-binding race without relying on SIGCHLD disposition or a leader staying alive voluntarily.
- Capture the exact Popen handle, original UID/PID/start_ticks/PGID/session ID, leader pidfd and its fstat device/inode while the gate waits. Require own UID, parent PID, and PID=PGID=session ID. Re-read identity after pidfd open. Register one private ownership object, retain it until confirmed stop, and validate its descriptor identity before every signaling boundary. The mapping and object must agree on the same Popen; caller booleans cannot grant ownership. A real flag-4 signal-zero capability call must succeed before gate release. Failed capture/capability closes the gate and stops only the originally bound individual pidfd, then performs a bounded wait; no target or descendant was released. An unbound gate is released only to EOF/its finite expiry, never signaled by guessed identity.
- Group probes, TERM and KILL use only
signal.pidfd_send_signal(original_pidfd, signal, None, 4)after ownership validation, including when Popen.poll/wait reports leader exit. Signal-zero ESRCH is accepted as empty only through the still-valid original handle; all other errors retain uncertainty. Attempt every registered group even when another stop fails. Keep 15-second TERM, five-second KILL and two-second sampler bounds. Native successful fixture setup/request/stop must still fit the one global 240-second deadline; an exhausted deadline is failure, not a reset. Emergency force-stop under the outer containment preserves that failed verdict rather than claiming timely acceptance. - Save/enable the controller's subreaper state before launching children and
restore it after cleanup. Reap the exact original leader through Popen and
adopted descendants through independently captured, revalidated child
pidfds and nonblocking
waitid(P_PIDFD, WEXITED | WNOHANG). Candidate child capture requires own UID, controller parent, original group membership and unchanged start identity, with original flag-4 group probes around capture; no concurrent new launches or unrelated waits occur during closeout. Linux waitid documentation defines descriptor-selected child waiting; the subreaper documentation defines adoption by the nearest living subreaper. Missing reaping capability refuses admission. Do not use arbitrarywaitpid(-1)or guessed-PGID signals. Failure to bind/reap or confirm original group zero retains scratch. - Stop the sampler and archive bounded public stop/identity/error observations before deciding removal. Remove owned scratch only after every original group and sampler is confirmed stopped. A refusal retains scratch and cannot emit the existing stopped-groups cleanup claim. Ownership FDs remain open until their final stop observation; no descriptor number is treated as a persistent identity after close.
Separate trust controls to register first
Extend the existing fixture/helper and tables; no new parallel browser class. The helper passes duplicated originally retained leader pidfds and immutable ownership identities into the explicit mapping before any exited-leader wait. Independent test cleanup continues using its separate original pidfds and is unaffected by finalizer fault injection. Existing finite 45-second processes, PIDFD identity checks, restored subreaper, retained failure evidence and outer cgroup containment stay mandatory.
| Literal control | Fixed required observation |
|---|---|
| Missing ownership mapping/entry | Refuse before either pidfd or numeric signal; preserve scratch and attempt other valid entries |
| Wrong exact Popen/leader/start/PGID or another owned group's pidfd | Refuse before any signal to the mismatched entry; never touch the other original group |
| Closed original FD, same integer reused for ordinary FD, or replaced by another live pidfd | Original descriptor identity check refuses before signal; independent fixture cleanup retains custody |
| Unsupported flag-4 EINVAL/ENOSYS or absent Python/child-wait capability | Target exec marker absent; finite gate safely stops; failure and scratch retained; no numeric fallback |
| Real retained leader pidfd after leader wait, with TERM-resistant same-group child | Actual flag-4 TERM/KILL reaches the original child, exact child reaped and original flag-4 zero probe yields ESRCH |
| One real group stop deliberately refused at pidfd boundary | Every other owned group attempted/stopped; refused group remains observable; scratch retained and no false cleanup claim |
| Gate capture/release timeout and target immediate exit | Finite stop/reap, no unbound signals or descriptors/processes left, exact successful exec retains original PID |
The five existing actual-finalizer counterexamples and empty-finalizer positive keep their outcomes. Amend the deliberate-stop fault boundary to cover both the historical numeric call and actual pidfd call, without suppressing independent cleanup. Preserve the published original red unchanged; rerun the admitted namespace controls on old production before claiming a repaired finalizer. Distinguish absent ownership mechanism assertions from executed unsafe-signal counterexamples. Actual native flag-4/group-zero and gated-exec evidence is a prerequisite; mocked capability refusal alone cannot admit the browser.
Affected production is confined to tools/ui/live_browser_check.py launch and
closeout, with the smallest local ownership helper; prospective estimate
150-190 net lines, to be measured before implementation and reported if larger.
Trust changes are only tools/ui/test_role_matrix.py, the clock protocol and
CURRENT status. No clock/shim/frame/ledger/row/status changes, generic process
framework, new deadline, or browser run is admitted by this proposal.
Limit: private group ownership covers the expected browser/server descendants, not hostile descendants deliberately moving into other sessions. The unchanged outer resource-group tree remains the final independent stop boundary; it does not substitute for the required per-fixture zero-population evidence.