|

bugs: 004 non-PID-1 invocation (zyginit -v) clobbers live control socket

bugs: 004 non-PID-1 invocation (zyginit -v) clobbers live control socket

Filed by the Hammerhead team. Running the zyginit binary directly on a host
where zyginit is already PID 1 -- e.g. 'zyginit -v' to check the version --
falls through to the full supervisor path (only --version/-V short-circuit,
not lowercase -v) and the un-guarded create_socket() unlinks+rebinds
/var/run/zyginit.sock, clobbering the live PID 1's socket. zygctl then can't
reach init; recovery requires reboot. Suggested: make -v print version+exit;
refuse to unlink a socket a live server is listening on; gate non-PID-1
supervisor mode behind an explicit flag / distinct socket path.
Author: Chris Tusa <chris.tusa@leafscale.com>
Date: Jul 21, 2026 13:33
Changeset: 593a8091c02bd3240c1c4c4a7977f01b670f0131
Branch: default

Diff

diff -r 0ffb8dfc1b98 -r 593a8091c02b bugs/004-nonpid1-invocation-clobbers-live-control-socket.md
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bugs/004-nonpid1-invocation-clobbers-live-control-socket.md	Tue Jul 21 13:33:36 2026 -0500
@@ -0,0 +1,74 @@
+# Bug 004: running the zyginit binary as a non-PID-1 process (e.g. `zyginit -v`) re-runs init and clobbers the live control socket
+
+## Summary
+
+On a system where zyginit is already running as PID 1, invoking the zyginit
+binary directly from a shell — e.g. an admin running **`zyginit -v`** to check
+the version — does **not** short-circuit and exit. Instead it falls through
+into the full supervisor sequence ("non-init mode"), which creates the control
+socket. `ctlsocket.create_socket()` **unlinks the existing
+`/var/run/zyginit.sock` and binds a new one**, clobbering the socket owned by
+the live PID 1. From then on `zygctl` cannot reach the real init:
+
+```
+zygctl: cannot connect to zyginit at /var/run/zyginit.sock
+zygctl: is zyginit running?
+```
+
+The system otherwise keeps running (PID 1 is untouched in memory), but it is no
+longer controllable via `zygctl`, and per `main.reef:534` recovery "requires
+reboot."
+
+Two distinct defects combine to make this an easy foot-gun:
+
+1. **`-v` is not recognized as a version flag.** Only `--version` and `-V`
+   (capital) short-circuit (`main.reef:943`). Lowercase `-v` — the conventional
+   "show version" flag for most CLI tools — is unmatched, so `zyginit -v` runs
+   the init/supervisor path. An admin's muscle-memory `zyginit -v` is exactly
+   the wrong thing.
+
+2. **Non-PID-1 invocation runs the full supervisor with no live-instance
+   guard.** The `getpid() != 1` branch is labeled "supervisor mode for testing"
+   (`main.reef` ~979/1015), but on a production host it still loads services,
+   enters the event loop, and calls the un-guarded socket setup (~`main.reef:1124`,
+   not wrapped in `is_pid_1()`), so `create_socket()` clobbers the production
+   socket. There is no check for "a live PID-1 zyginit already owns this socket."
+
+## Severity
+
+**Medium.** Not a boot-path defect, but a sharp operational foot-gun: a
+completely reasonable admin action (`zyginit -v` to read the version) silently
+breaks `zygctl` control of the running system until the next reboot. It is easy
+to trigger and non-obvious to diagnose (the socket just "disappears").
+
+## Affected Version
+
+zyginit 0.2.6 (confirmed by source inspection) and 0.2.5 (observed on
+Hammerhead hh-alpha9 — an admin ran `zyginit -v` and `zygctl` then reported the
+socket unreachable; system was fine but uncontrollable until reboot).
+
+## Suggested Fix (for the zyginit team)
+
+1. **Make `zyginit -v` safe.** Treat `-v` as a version alias (or, more
+   conservatively, reject unknown flags with a usage message) so `zyginit -v`
+   prints the version and exits **before** any PID-1/supervisor/socket setup —
+   the same short-circuit that already exists for `--version`/`-V`.
+2. **Guard the socket against a live instance.** Before `create_socket()`
+   unlinks/binds `/var/run/zyginit.sock`, detect whether a live PID-1 zyginit
+   already owns it — e.g. try to `connect()` first (a successful connect means
+   a live server is present → refuse and exit), or check `/proc/1` is zyginit.
+   Never unlink a socket that a live server is actively listening on.
+3. **Gate "supervisor test mode" behind an explicit opt-in.** Non-PID-1
+   supervisor runs (for integration testing) should require an explicit flag
+   (e.g. `--supervisor-test`) and/or use a distinct socket path (e.g.
+   `$ZYGINIT_SOCK` / a temp path) so a bare `zyginit ...` on a production host
+   can never touch the production socket.
+
+## Reported by
+
+Hammerhead team (Chris Tusa), 2026-07-21. Triggered on hh-alpha9 (zyginit 0.2.5,
+PID 1) by running `zyginit -v` from a root shell; `zygctl` subsequently could
+not connect to `/var/run/zyginit.sock`. Cross-ref Hammerhead memory
+`zyginit-no-exec-init-binary` (the known "don't exec the init binary on a
+zyginit host — clobbers the socket, recover via `virsh destroy && virsh start`"
+note).