8 Commits

Author SHA1 Message Date
ceamac 6624fd3ab5 Add user: support to compose services
ComposeService::user/group (compose_file.{h,cpp}) parse a "user[:group]"
key, split on the first ':' the same way an image's own declared USER is
split (oci_image.cpp). start_compose_services() passes both straight
through to run_mounted_container()'s existing user/group parameters, which
already fall back to the image's own declared user when unset -- the same
default -r/--run itself has when --user isn't given.

Verified end to end on the real target machine (root, via the scoped doas
rule), checked via `ps -eo pid,ppid,uid,cmd` (not -x/--exec, see below):
the actual sandboxed command runs as the resolved uid/gid, matching plain
-r --user's own already-working behavior.

Also recorded in TODO.md: verifying this surfaced a real but unrelated
bug in resolve_namespace_pid() (sandbox_process.cpp), which -x/--exec's
own default-identity resolution uses -- it stops at bwrap's own pid-1
namespace supervisor instead of walking one level deeper to the real
(correctly priv-dropped) target, so `-x/--exec <pid> -- id` with no
explicit --user misreports root for a session that's actually running as
a non-root user the whole time. Not a regression from this change and not
fixed here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
2026-09-07 12:16:52 +00:00
ceamac e047d243f2 Fix a race in create_session_cgroup(); skip the straggler test when unreachable
create_session_cgroup() used to be called from run_bwrap()'s on_start
callback, in the parent, concurrently with the just-forked child execing
into bwrap and bwrap then doing its own internal clone() of the sandboxed
target. Without --unshare-pid, bwrap has little enough setup work to do
that it could reliably win that race, cloning its target before the
parent's own write into cgroup.procs completed -- leaving that target, and
everything it later spawns, permanently outside the tracked cgroup, so the
post-exit sweep found nothing to reap. Found via the user's own request to
test "pid namespace off, cgroup on" as root: confirmed directly by
inspecting cgroup.procs mid-session, showing only bwrap's own pid.

Fixed by giving run_process_foreground() a new before_exec hook, invoked in
the child synchronously right before execvp() -- the child cannot proceed
to exec (and thus cannot trigger any of bwrap's own internal forking) until
this has already returned, closing the race structurally rather than by
timing luck. run_bwrap() now creates the session cgroup there instead of in
on_start; the parent side just reconstructs the deterministic path
unconditionally, since the downstream sweep/cleanup functions already
tolerate a nonexistent directory gracefully either way.

Also fixes a false positive found while verifying this: the regression
test's own process-matching did a substring search across a whole cmdline
blob, which matched an unrelated manual `pkill -f 'sleep 137'` diagnostic
command run by hand during the investigation. Tightened to an exact
argv[0]/argv[1] match.

Finally, the regression test now SKIP()s (instead of failing) when neither
a pid namespace nor a working session cgroup is available for the current
effective config -- a documented, known residual limitation, not a
regression -- checked directly via two new helpers rather than assumed from
e.g. geteuid().
2026-09-05 10:28:07 +00:00
ceamac 1f52bc5f6f Add regression test for the session-straggler sweep; resolve TODO entry
test_session_cleanup.cpp exercises kill_via_cgroup() directly against two
plain forked processes (one setsid()-ing away from the other before it
exits), confirming a reparented straggler is actually reaped -- reproducing
the real escape shape (no pid namespace support at all) through a full
mount/bwrap session isn't possible from the CLI on a single run, since
--unshare-pid is a config-file-only setting, not a flag.

Also resolves TODO.md's SIGINT/SIGTERM entry and extends the relevant
CLAUDE.md sections (bwrap.{h,cpp}, session_cgroup.{h,cpp}, kill_session.{h,cpp})
with the fix's rationale and its known residual limitation (a kernel with
neither cgroup v2 nor pid namespace support still can't be reached
automatically).
2026-09-05 07:20:51 +00:00
ceamac 0c3a4bae99 TODO: Ctrl-C/SIGTERM on a foreground session may leave orphans
forward_signal_to_foreground_child() (process.cpp) does a plain kill() on
only the one tracked bwrap pid -- unlike --kill's kill_session(), which
picks a strategy (cgroup, pid-namespace, or tracked-pid) to reach every
process the session started. Anything inside the sandbox that
daemonizes/double-forks into a new session escapes the simple forward and
can be left running after Ctrl-C, even though --kill against the same
session would reach it. Reported by the user during real-device testing;
not yet reproduced with a specific repro, just the architectural gap.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
2026-09-04 14:46:39 +00:00
ceamac b3990094b9 Confirm root cause: HOME set but not exported in the invoking shell
The user identified the exact trigger: declare -p HOME showed
`declare -- HOME="/root"` (no -x), meaning HOME was a plain shell
variable, never exported, so slocker-lite's own getenv("HOME") saw
nothing -- identical to HOME being fully unset from a child process's
point of view. An earlier full environment dump had looked like it
already had HOME correctly set and ruled this out; it didn't, since
that dump listed all shell variables (declare -p style), not strictly
the exported environment a child process actually receives.

Reproduced directly on the real device (env -u HOME bash -c 'HOME=/root;
declare -p HOME; slocker-lite -w') and confirmed the existing fix
(resolve_home_dir()'s passwd-database fallback) already handles it
correctly -- resolves to /root/.config/slocker-lite/config.yaml, not a
cwd-relative path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
2026-09-03 18:40:33 +00:00
ceamac cc3beba011 Update TODO: cwd-relative path fix shipped, exact trigger still open
The previous entry's fix (xdg_state_dir()/config_file_path() now
absolute-by-construction, plus a passwd-database $HOME fallback) has
landed, but the user's own environment dump after hitting the dnsmasq
symptom showed $HOME correctly set to /root with nothing that should
have produced a relative path under the old code either -- so the exact
mechanism that triggered it originally is still unconfirmed, even though
the fix should cover it defensively regardless of cause.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
2026-09-03 18:37:15 +00:00
ceamac a70634f1d7 TODO: investigate cwd-relative config/state path fallback
Found on the real device: two slocker-lite invocations from different
working directories ended up with completely separate config.yaml/state
trees (one under /root, one under /root/src/slocker-lite), both
auto-allocating the same 10.168.0.0/24 subnet and independently mutating
host-level ip/iptables state with no awareness of each other. Looks like
$HOME being unset/empty in some invocations, causing the $HOME-relative
fallback to silently resolve relative to cwd instead -- not yet confirmed,
needs a real repro before deciding on a fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
2026-09-03 17:34:40 +00:00
ceamac 4147a387b8 Add TODO: run the DNS resolver as a low-privilege user
Tracks the security follow-up for network_dns.cpp's --user=root
--group=root workaround -- dnsmasq's own default privilege drop broke
reading state under /root (mode 0700), so it's kept at root entirely for
now. Not urgent (networking here is already root-only throughout), but
worth revisiting later.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
2026-09-03 17:34:40 +00:00