Skip to content

libtcb: skip privilege drop in unprivileged user namespaces#38

Open
Blarse wants to merge 2 commits intoopenwall:mainfrom
Blarse:libtcb-unpriv-userns-support
Open

libtcb: skip privilege drop in unprivileged user namespaces#38
Blarse wants to merge 2 commits intoopenwall:mainfrom
Blarse:libtcb-unpriv-userns-support

Conversation

@Blarse
Copy link
Copy Markdown

@Blarse Blarse commented Apr 28, 2026

When an unprivileged user sets up a user namespace without newuidmap, it must first write "deny" to /proc/self/setgroups before writing gid_map, after which setgroups() becomes permanently forbidden[1]. tcb_drop_priv_r() then trips over sys_setgroups(0, NULL) and bails out with EPERM, even though euid 0 inside the namespace is not real root and there is nothing to drop in the first place.

In my case this breaks mkosi tool that builds images inside an unprivileged userns, where any tcb-aware utility aborts on the setgroups step. The patch detects the "deny" state via a small setgroups_allowed() helper and short-circuits tcb_drop_priv_r() with PRIV_MAGIC_NONROOT, the same path already taken for non-root callers. No behavioural change outside user namespaces; non-Linux builds get a stub that always returns 1.

[1] https://man7.org/linux/man-pages/man7/user_namespaces.7.html

Blarse added 2 commits April 28, 2026 17:26
Detect /proc/self/setgroups == "deny" to recognize an unprivileged
user namespace where setgroups(2) is permanently denied by the kernel.
No-op on non-Linux.
When /proc/self/setgroups is "deny" we cannot call setgroups(2),
and euid 0 inside such a namespace does not carry real privileges
anyway.  Treat this case the same as running non-root: mark the
privs structure with PRIV_MAGIC_NONROOT and return success.

Fixes failures of pam_tcb, libnss_tcb, tcb_unconvert and shadow's
shadowtcb_drop_priv() when running under rootless container.
@solardiz solardiz requested a review from ldv-alt May 2, 2026 20:17
@solardiz
Copy link
Copy Markdown
Member

solardiz commented May 2, 2026

Thank you @Blarse! Are these changes already in use in ALT's package perhaps?

I was concerned at first that the deny mode could possibly be enabled by a user in some other scenario, such as planning to attack libtcb as used by a privileged program, but reading up on it and experimenting with it a little bit I see it's very specialized to unprivileged containers.

Another approach I'd consider is only checking for deny after setgroups fails, and letting the uid/gid changes proceed anyway (and similar when restoring, so would need another magic to identify this state, or maybe mark it with number_of_groups = -1). However, this isn't obviously better - it skips the extra logic in the common case (faster?) and never skips uid/gid switching (safer against the unknown?), but adds complexity (and effort since it's different from what you already have in this PR). So just thinking out loud.

Comment thread libs/libtcb.c
if (n <= 0)
return 1;
buf[n] = '\0';
return strncmp(buf, "deny", 4) != 0;
Copy link
Copy Markdown
Member

@solardiz solardiz May 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd make it char buf[5]; and read exactly 5 bytes. If read returns other than 5, assume it's not deny. If it returns 5, check with memcmp(buf, "deny\n", 5).

@solardiz
Copy link
Copy Markdown
Member

solardiz commented May 2, 2026

Another approach I'd consider is only checking for deny after setgroups fails, and letting the uid/gid changes proceed anyway (and similar when restoring, so would need another magic to identify this state, or maybe mark it with number_of_groups = -1). However, this isn't obviously better - it skips the extra logic in the common case (faster?) and never skips uid/gid switching (safer against the unknown?)

"The unknown" includes possible future changes to the kernel, where the deny mode could possibly become usable in some other scenario. It isn't currently intended to disable any security measures that userspace tools take, so us doing essentially that is a risk and may not be future-proof.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants