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>
This commit is contained in:
@@ -45,7 +45,37 @@ Source layout (all under `src/`):
|
||||
triggers the kernel's unprivileged-userns setgroups() restriction, which showed up
|
||||
as every other supplementary group collapsing to the overflow gid ("nobody") in
|
||||
`id`, and `su` inside the sandbox failing with "can't set groups: Operation not
|
||||
permitted".
|
||||
permitted". Because of that, bwrap's own `--uid`/`--gid` (which require
|
||||
`--unshare-user`) aren't usable when running as root either — `--user`/`--group`
|
||||
work around this: when set, `build_bwrap_args()`/`run_bwrap()` bind-mount the
|
||||
separate `slocker-lite-priv-drop` helper (see below) into the sandbox at a fixed
|
||||
hidden path and route the real command through it as
|
||||
`<uid>:<gid> -- <command...>`. This only actually works without a user namespace
|
||||
(i.e. running as 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
|
||||
silently doing nothing. `run_bwrap()` fails fast (returns -1) if the helper can't
|
||||
be found next to this binary when `--user` was requested, rather than silently
|
||||
running the command as root.
|
||||
- `priv_drop_helper.cpp` → the separate `slocker-lite-priv-drop` binary (its own
|
||||
`executable()` target in `meson.build`, **built with `-static`**). Deliberately
|
||||
has zero dependencies on the rest of this project (no fmt/spdlog/etc.) and is
|
||||
fully statically linked: it gets bind-mounted *into the container image's own
|
||||
filesystem*, which won't have `slocker_lite`'s own shared library dependencies —
|
||||
a dynamically linked binary bind-mounted that way fails outright ("error while
|
||||
loading shared libraries"), which is exactly what happened before this was split
|
||||
out (the original approach bind-mounted `slocker_lite`'s own — dynamically linked
|
||||
— binary via `/proc/self/exe` and reexeced it; kept only as a lesson, not as
|
||||
working code). Usage: `slocker-lite-priv-drop <uid>:<gid> -- <command> [args...]`;
|
||||
does `setgroups(0,…)` → `setgid()` → `setuid()` → `execvp()`, in that order
|
||||
(dropping the group needs `CAP_SETGID`, which is lost once `setuid()` drops root).
|
||||
`find_priv_drop_helper()` (`src/bwrap.cpp`) locates it next to `slocker_lite`'s own
|
||||
binary (via `/proc/self/exe`'s directory), which holds both when run straight from
|
||||
`buildDir/` and after a real `meson install`.
|
||||
- `user_spec.{h,cpp}` — `resolve_user_and_group()` resolves `--user`/`--group` (each
|
||||
a name or numeric id) against the *mounted image's own* `/etc/passwd`/`/etc/group`
|
||||
(not the host's), since names like `git` only mean anything inside that image's own
|
||||
user database. A numeric `--user` with no `--group` and no matching `/etc/passwd`
|
||||
entry defaults gid to the same numeric value as the uid.
|
||||
- `process.{h,cpp}` — argv-based subprocess helpers (fork/execvp, no shell):
|
||||
`run_process()` captures stdout (used for `containers-storage` calls),
|
||||
`run_process_foreground()` inherits all of stdio (used for the interactive `bwrap`
|
||||
@@ -82,11 +112,13 @@ out to already be directly visible.
|
||||
Build directory is `buildDir/` (already configured).
|
||||
|
||||
- Configure (only needed if `buildDir/` is missing or deleted): `meson setup buildDir`
|
||||
- Build: `meson compile -C buildDir` (or `ninja -C buildDir`)
|
||||
- Build: `meson compile -C buildDir` (or `ninja -C buildDir`) — also builds
|
||||
`buildDir/slocker-lite-priv-drop`, the statically-linked helper `-r --user` needs
|
||||
(see `priv_drop_helper.cpp` in "Project state")
|
||||
- Run the executable: `./buildDir/slocker_lite -m <image.tar>` (see `--help` for the
|
||||
full flag list: `-m/--mount`, `-r/--run`, `-u/--umount`, `-c/--cleanup`,
|
||||
`-l/--list-images`, `-n/--no-nsenter`, `-t/--test`, `--log-level`, `-h/--help`,
|
||||
`-V/--version`)
|
||||
`-l/--list-images`, `-n/--no-nsenter`, `--user`, `--group`, `-t/--test`,
|
||||
`--log-level`, `-h/--help`, `-V/--version`)
|
||||
- Run tests: `meson test -C buildDir`
|
||||
|
||||
## Code style
|
||||
|
||||
Reference in New Issue
Block a user