148 lines
8.5 KiB
Markdown
148 lines
8.5 KiB
Markdown
# Row253 C4 addendum: the supervisor belongs after sudo
|
|
|
|
Draft for Duckworth's static ruling. DESIGN.md remains unchanged. Authority:
|
|
duckworth-0109/0110, RULING-c4.md at 2c49dd80c, and sage-2307/2312.
|
|
No repaired product code or new test has run. Product scripts still have the
|
|
original frozen bytes; this addendum does not claim they satisfy C4.
|
|
|
|
## Finding and evidence
|
|
|
|
The current tester_capture puts GNU timeout --signal=KILL outside sudo, then
|
|
kills that timeout's process group. It works for the exec-style sudo fake.
|
|
sudo(8), **Process model**, says the pty monitor creates a new terminal session
|
|
and forks the command there. **Signal handling** (called Signals in the local
|
|
rendering) says SIGKILL cannot be caught and relayed. Therefore an outer group
|
|
kill does not establish that the query in the new session is killed.
|
|
|
|
Evidence was a static read of the installed sudo(8)/sudoers(5) documentation,
|
|
then Duckworth's independent ruling against sudo 1.9.17p2. No real sudo, host
|
|
experiment, live unit or live-port experiment ran. The shared fake below is
|
|
written but unrun. It models the session boundary, not authentication or a pty.
|
|
|
|
## Proposed trusted supervisor and runtime trade
|
|
|
|
Recommend a reviewed inline Bash supervisor, run **after the uid drop**, using
|
|
root-validated fixed system Bash, setsid, sleep and kill/builtins. These are
|
|
host tools, never binaries or scripts from the selected copy. Resolve system
|
|
symlinks to their real targets; require executable root-owned ELF files and
|
|
safe root-owned ancestors, outside homes. Do not use PATH lookup for these
|
|
supervisor tools. env -i removes BASH_ENV and other injected runtime settings.
|
|
Add setsid to the documented host dependencies. No new configuration knob.
|
|
|
|
This is a proposal under R1's root-validated system-binary option, pending
|
|
Duckworth's approval of Bash rather than the example GNU timeout. The exact
|
|
inline supervisor must be identical in both host scripts, with budgets and
|
|
command argv passed separately. No helper is imported from the copy.
|
|
|
|
Alternatives weighed:
|
|
|
|
- GNU timeout alone inside sudo fixes the session hop but kills one process
|
|
group. It misses a grandchild that uses setpgid without setsid. It needs the
|
|
same owned-session cleanup as the proposed supervisor.
|
|
- Validated --node with inline builtins can implement the same supervisor.
|
|
The installer already validates it. Acceptance deliberately reports unsafe
|
|
--node paths through F9/F10; making that binary a required trusted supervisor
|
|
would change those existing outcomes or need another pinned runtime.
|
|
- Fixed root-validated system Python can fork a session, multiplex bounded
|
|
output and scan /proc cleanly. It is already used for P10, but making it
|
|
mandatory for the config query changes missing/unsafe Python from a failed
|
|
P10 observation into a preflight refusal. Preserve that behavior here.
|
|
|
|
## R1: own a query session, not sudo's group
|
|
|
|
The supervisor starts in sudo's possibly separate session as the stack uid.
|
|
It forks the query child, then setsid occurs in that child through the fixed
|
|
system setsid binary. The supervisor itself may already be a group leader,
|
|
so it must not attempt setsid in its own process. Disable Bash job control;
|
|
the newly forked child is not a group leader, so setsid need not fork again.
|
|
The child's PID from $! is the kernel session id. No PID comes from copy output.
|
|
|
|
Run a separate five-second watchdog inside the uid boundary. On expiry, on a
|
|
supervisor catchable signal, on output refusal, and after normal query exit:
|
|
|
|
1. Scan numeric /proc/*/stat entries for the owned session id. Parse after the
|
|
final closing parenthesis, so spaces, parentheses and newlines in comm do
|
|
not shift the kernel fields. Use the kernel session field, not ancestry.
|
|
2. SIGKILL every live PID in that session, including different process groups.
|
|
Repeat until no running members remain or a fixed 250 ms cleanup budget
|
|
expires. Never select a process from a different session.
|
|
3. Wait for the direct child and the watchdog. Refuse on a cleanup limit or
|
|
read/parse error that prevents establishing cleanup. Zombies are not running;
|
|
only direct children can be reaped here. Tests pin that distinction.
|
|
|
|
A five-second deadline concerns the inner query. The root-side deadline is
|
|
independent. Query bytes are accepted only after success and completed cleanup.
|
|
The query can never authorize a root kill: its stdout supplies no PID/session.
|
|
The supervisor runs without root authority and kills only its owned session.
|
|
|
|
## R2: independent root bound and bounded output
|
|
|
|
Root waits at most seven seconds for the metadata sudo call. Keep GNU timeout
|
|
outside sudo for this separate purpose. Keep the 4096-byte cap plus one-byte
|
|
overflow detection. All partial output is discarded on any refusal.
|
|
|
|
Give outer timeout its own operator reason, distinct from inner timeout,
|
|
child failure, output limit and cleanup refusal. A child-supplied exit value
|
|
alone must not masquerade as proof the outer timer fired. Capture GNU timeout's
|
|
trusted verbose signal notice separately, with sudo/helper stderr redirected
|
|
away from that channel. The root-owned notice file is outside the user's
|
|
home. Validate the combination of notice and status; no eval or source.
|
|
|
|
Apply the same primitive to the new P10 peer probe, with inner two seconds,
|
|
outer four seconds, a 128-byte output cap and the existing one-second socket
|
|
connect timeout. Preserve its fixed system-Python safety check and failed-P10
|
|
behavior. All observed stdout still requires successful cleanup and exact
|
|
pid/uid/gid validation. Neither path sends application bytes to the socket.
|
|
|
|
## R3: narrowed claim and residual
|
|
|
|
C4 now means: killed at the inner bound, and root never waits past the outer
|
|
bound. A hostile same-uid query can kill/stop the supervisor or setsid away.
|
|
A hang in the trusted supervisor has the same outer-bound outcome. In that
|
|
case root refuses and discards stdout; the separate session may remain.
|
|
This is the existing DESIGN.md:97-99 same-uid limit, not a new closure claim.
|
|
No root-side cross-session killer is added. It would require another row.
|
|
|
|
The stalled-supervisor fake must prove outer timing, its own refusal reason
|
|
and no use of partial stdout. It then explicitly kills its recorded leftover
|
|
session as fixture cleanup, naming that step as the R3 residual. It must not
|
|
claim the product cleaned that session. Other normal-supervisor cases assert
|
|
no running members of the recorded query session remain after the call.
|
|
|
|
## R4 and F5: headers and remaining legacy calls
|
|
|
|
Both headers will name the inner supervisor, its runtime/tool validation,
|
|
five/seven-second query bounds and two/four-second peer-probe bounds.
|
|
|
|
The old F3 talk path (currently timeout 330 around AS_USER talk) has the same
|
|
sudo session-hop issue. Propose recording it as a bounded follow-up, rather
|
|
than changing it in this slice: it predates the socket metadata/probe helpers,
|
|
drives a 300-second functional conversation and simultaneous F5 scan, and has
|
|
existing stdout/stderr diagnostics. It is not part of the new five-second
|
|
config query. This is an explicit F5 scope choice for Duckworth/Sage to accept
|
|
or reject, not a claim the old talk timer kills a separate session. Likewise,
|
|
the other legacy AS_USER diagnostics are not newly supervised by this row.
|
|
|
|
## Shared fake and next evidence
|
|
|
|
packages/cli/tests/case-sudo-fixture.mjs provides the one process-model seam.
|
|
Both existing test files import it. Mode exec retains the prior fake. Mode
|
|
hop keeps a parent, forks through setsid, records kernel PID and SID, and
|
|
relays TERM/HUP/INT only. It cannot relay KILL. The installer draft case makes
|
|
the copy ignore TERM/HUP and block; it checks recorded query PIDs and the
|
|
separate fake session, then explicitly cleans fixture-owned PIDs on failure.
|
|
This is written but not run, and is expected to expose the old product gap.
|
|
|
|
Before the next whole gate, complete both-mode cases for timeout, TERM/HUP,
|
|
setpgid grandchild, overflow, and the distinct stalled-supervisor residual.
|
|
Both host-script paths use this seam. Add changes for absent inner supervisor,
|
|
absent outer bound, leader-group-only cleanup and unbounded read. The setpgid
|
|
case must kill the group-only change. Record every restore and raw log.
|
|
|
|
Duckworth reviews this addendum and fake statically first. Product code then
|
|
implements the approved mechanism. All product changes void prior results
|
|
under sage-2307/mos-1200. Only Sage's next CLEAR permits a diagnostic or gate
|
|
run. The next gate is both whole touched files, all checks, CLI/WebUI whole
|
|
and all ten shell files. Existing RED evidence carries only if applicable
|
|
product bytes remain equal. No host deployment is authorized by this packet.
|