Commit Graph

17 Commits

Author SHA1 Message Date
ceamac 0b1698dc97 Rename the executable from slocker_lite to slocker-lite
Matches the project name and the slocker-lite-priv-drop helper's own
naming convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
2026-08-21 12:06:03 +00:00
ceamac e64f885c2e Default -r's user/group to the image's own declared user
When --user isn't given, run the sandboxed process as whatever user the
image's own config.User declares (already parsed into OciImageConfig::user/
group), instead of always defaulting to root. An explicit --user/--group on
the command line still takes precedence.

read_oci_image_config() is now called unconditionally in run_container()
(it was previously gated behind "no command given") and shared for both the
default command and the default user/group, rather than growing another
special-case guard.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
2026-08-21 07:28:14 +00:00
ceamac 4e1e1b206d Split OciImageConfig's user into separate user/group fields
--user/--group are now handled as separate strings (ResolvedUser), so
parse config.User's "<user>[:<group>]" the same way instead of leaving
callers to split the combined spec themselves.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
2026-08-21 07:18:23 +00:00
ceamac 69df728e09 Add --user/--group to run as a different uid/gid as root
bwrap --uid/--gid require --unshare-user, which is never requested
when running as root (since last session's fix), and user namespaces
aren't a near-term option anyway -- the actual Android target doesn't
support them.

--user <name-or-uid> / --group <name-or-gid> work around this:
user_spec.cpp resolves them against the *mounted image's own*
/etc/passwd and /etc/group (names like "git" only mean anything inside
that image's user database), and bwrap.cpp bind-mounts a small helper
into the sandbox to do the actual privilege drop before exec'ing the
real command, since bwrap itself can't switch uid/gid without a user
namespace.

The helper has to be a separate, statically-linked binary
(priv_drop_helper.cpp -> slocker-lite-priv-drop, built with -static)
rather than slocker_lite's own binary: bind-mounting a dynamically
linked executable into an arbitrary container image fails ("error
while loading shared libraries") since that image's own /lib won't
have slocker_lite's dependencies. find_priv_drop_helper() locates it
next to slocker_lite's own binary; run_bwrap() fails fast if it's
missing rather than silently running as root.

Only works without a user namespace (root): under --unshare-user the
sandbox's uid map has only one valid entry, so the helper's own
setuid() fails cleanly there instead of doing nothing silently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 07:06:13 +00:00
ceamac 095ae8397a Drop --unshare-net from bwrap args, no network setup yet
Without any network setup (slirp4netns or similar), unsharing the
network namespace just leaves the sandbox with no network at all,
which isn't useful yet. build_bwrap_args() now skips --unshare-net
when assembling the real bwrap invocation; re-add once network
isolation is implemented.

detect_bwrap_unshare_args() itself is unchanged and still probes/
reports net namespace kernel support (e.g. via -t/--test), since
that's capability detection, not policy -- same pattern as the
--new-session removal.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 06:17:20 +00:00
ceamac b1a2d924e8 Don't request --unshare-user for bwrap when running as root
Root already has full privilege without a new user namespace. Creating
one anyway (bwrap's default single-mapping uid/gid setup, no
--uid/--gid/subuid ranges) forces the kernel's unprivileged-userns
setgroups() restriction: every supplementary group outside that one
mapping collapses to the overflow gid (65534/"nobody"), and
setgroups() calls inside the sandbox then fail.

Reported by the user running -r images/gitea.tar as root: `id` showed
groups=0(root),65534(nobody) repeated once per real supplementary
group, and `su git` failed with "can't set groups: Operation not
permitted". detect_bwrap_unshare_args() now skips --unshare-user (and
stops combining the other probes with CLONE_NEWUSER) whenever
geteuid() == 0 -- confirmed fixed by the user on a root-capable
machine. The non-root path is unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 05:34:41 +00:00
ceamac f88b4c08e7 Handle SIGINT/SIGTERM in run_process_foreground for -r
Previously, Ctrl-C or `kill` during -r's bwrap run left slocker_lite
killed outright (default signal disposition), skipping
run_container()'s unmount/cleanup entirely and leaving the layer
imported and/or mounted as garbage.

run_process_foreground() now installs a handler around its waitpid()
that forwards the signal to the running child (kill(), async-signal-
safe) and keeps waiting (EINTR-retry loop) instead of dying. Once the
child actually exits, control returns normally so the existing
unmount/cleanup runs as usual.

Verified with a backgrounded `-r ... -- /bin/sleep 30`, sending both
SIGINT and SIGTERM to the outer slocker_lite PID: process tree exits
cleanly, unmount+delete-layer both run, containers-storage layers
returns to baseline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-21 05:10:46 +00:00
ceamac b8f4680745 Extract OCI image config; use it for -r's default command
read_oci_image_config() (src/oci_image.cpp) reads the image config
blob referenced by the manifest and extracts User, ExposedPorts (as
OciExposedPort{port, OciPortProtocol}, parsed from keys like
"3000/tcp"), Env, Volumes, and the effective default command
(Entrypoint ++ Cmd). Refactored the oci-layout/index.json/manifest
loading read_oci_layers() already did into a shared read_oci_manifest()
helper, since this is the first time a second "loud" (spdlog::error
on failure) caller needs the identical validation.

-r/--run now uses the image's own default command when none is given
on the command line, falling back to /bin/sh only if the image sets
neither Entrypoint nor Cmd. User/ExposedPorts/Env/Volumes are captured
but not applied anywhere yet -- that lines up with volumes/networking
still being deferred.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 16:35:38 +00:00
ceamac d009c3777b Ignore the local images/ test directory
Contains large OCI image tars used for manual testing, not part of
the project.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 15:49:57 +00:00
ceamac 4c58552ba6 Align --list-images output on a tab stop
Pad each name:tag with enough tabs (not spaces) to reach one 8-column
tab stop past the longest entry in the list, so filenames line up
regardless of how much shorter other names/tags are.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 15:49:26 +00:00
ceamac 94ae5b36a2 Add -l/--list-images to discover OCI archives in a directory
list_oci_images() (src/oci_image.cpp) scans a directory non-recursively
for *.tar/*.tar.* files and, for each one that's a valid OCI Image
Layout archive, derives a name:tag from its index.json manifest
annotations -- io.containerd.image.name if present (a full reference),
else org.opencontainers.image.ref.name (conventionally just a bare
tag for skopeo/podman-produced archives). Falls back to the archive's
filename (.tar and any compression suffix stripped) for the name and
"latest" for the tag. Files that aren't OCI archives are skipped
quietly, since a directory scan is expected to hit unrelated tars.

-l/--list-images <directory> wires this into the CLI, printing
"name:tag<TAB>filename" per image. This is also why --log-level lost
its short form last session: -l needed to be free for this.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 15:06:32 +00:00
ceamac 529a96c89c Combine namespace probes with CLONE_NEWUSER
As a regular (non-root) user, most namespace types can only be
unshared together with a fresh user namespace, not in isolation --
the user namespace is what supplies the needed capabilities. Probing
each type on its own under-reported support: on this dev machine only
--unshare-user came back as supported, when in fact ipc/pid/net/uts/
cgroup were all usable once combined with it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 09:07:29 +00:00
ceamac 4478e94b65 Drop --new-session from bwrap args, breaks foreground tty
It detaches the sandboxed process from the controlling terminal, so
an interactive /bin/sh fails with "can't access tty; job control
turned off." Re-add it once background/daemonized runs land, where
detaching from the terminal is actually the point.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 07:52:49 +00:00
ceamac c0bef0d989 Add -r/--run and -c/--cleanup, fix nsenter under root
-r/--run mounts an image, runs a command under bwrap in the
foreground (default /bin/sh, overridable via -- <command> [args...]),
then unmounts and cleans up when it exits. -c/--cleanup deletes a
layer and its ancestor chain from local storage (containers-storage
delete-layer, walking parents via `layer --json`), since -u only ever
unmounted.

bwrap needs to see the merged mount from inside the private namespace
containers-storage mount creates when running rootless; run_bwrap()
locates the live fuse-overlayfs process and runs bwrap via nsenter
into its namespaces. When running as root no such namespace exists
(containers-storage doesn't need to reexec for privilege), so nsenter
fails with EINVAL; detect geteuid() == 0 and skip it automatically
there. -n/--no-nsenter forces it off manually for any other case.

process.cpp gains run_process_foreground() (inherited stdio, for the
interactive bwrap run) and the relocated find_in_path(), now shared
with bwrap.cpp's nsenter lookup.

Also: meson test only ran -m, leaking a layer on every run; it now
runs tests/run_test.py, which drives mount -> umount -> cleanup and
fails if any step does.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 07:44:24 +00:00
ceamac 839551a648 Add kernel namespace detection for bwrap integration
Add detect_bwrap_unshare_args(), which probes the running kernel for
which namespace types (user, ipc, pid, net, uts, cgroup) can actually
be unshared and returns the matching bwrap --unshare-xxx flags. Each
probe forks a throwaway child to call unshare() so the calling
process's own namespaces are never touched. Needed because the target
device (Android, stock kernel) only supports a subset of namespace
types, so bwrap must be invoked with just the flags it can honor.

Temporarily wired into -t/--test so it can be exercised on-device
ahead of the real bwrap invocation; will be removed once that lands.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 05:56:31 +00:00
ceamac 7e1ee150f6 Add CLI option parsing and implement layer unmounting
Replace the implicit single-argument invocation with getopt_long-based
flags: -m/--mount (existing mount flow, now explicit), -u/--umount
(unmounts a layer via containers-storage), -t/--test (stub),
-l/--log-level (runtime spdlog level), -h/--help, -V/--version.
--mount now also prints the top layer's ID so it can be passed to
--umount.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 09:03:58 +00:00
ceamac a301dfb44f Initial commit: OCI image mounting via containers-storage
slocker-lite validates an OCI Image Layout tar, imports its layers into
containers-storage's layer store in order, and mounts the assembled image
using fuse-overlayfs, printing the resulting merged path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 08:12:50 +00:00