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>
This commit is contained in:
2026-08-21 05:34:41 +00:00
parent f88b4c08e7
commit b1a2d924e8
2 changed files with 20 additions and 4 deletions
+14 -4
View File
@@ -116,7 +116,18 @@ std::vector<std::string> detect_bwrap_unshare_args() {
bool user_ns_supported = kernel_supports_namespace(CLONE_NEWUSER);
spdlog::debug("namespace user: {}", user_ns_supported ? "supported" : "not supported");
if (user_ns_supported) {
// Root already has full privilege without a new user namespace, and bwrap's own
// uid/gid mapping into one (a single trivial mapping, since we don't pass
// --uid/--gid/subuid ranges) forces the kernel's unprivileged-userns
// setgroups() restriction: every other supplementary group collapses to the
// overflow gid (65534/"nobody"), and setgroups() calls inside the sandbox (e.g.
// `su`) fail with "Operation not permitted". Observed directly: `id` inside a
// root-launched sandbox showed "nobody" repeated once per real supplementary
// group, and `su git` failed exactly this way. So only request --unshare-user
// (and only combine the other probes with it) when not root.
bool combine_with_user_ns = user_ns_supported && geteuid() != 0;
if (combine_with_user_ns) {
args.push_back("--unshare-user");
}
@@ -126,9 +137,8 @@ std::vector<std::string> detect_bwrap_unshare_args() {
}
// As a regular (non-root) user, most namespace types can only be unshared
// together with a fresh user namespace (which supplies the capabilities
// needed), not in isolation -- so combine them here whenever the user
// namespace probe above succeeded.
int flags = probe.clone_flag | (user_ns_supported ? CLONE_NEWUSER : 0);
// needed), not in isolation -- so combine them here whenever it applies.
int flags = probe.clone_flag | (combine_with_user_ns ? CLONE_NEWUSER : 0);
bool supported = kernel_supports_namespace(flags);
spdlog::debug("namespace {}: {}", probe.name, supported ? "supported" : "not supported");
if (supported) {