diff --git a/ChangeLog b/ChangeLog index 001f011..62e4f6a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,22 @@ +2026-04-28 Egor Ignatov + + libtcb: skip privilege drop in unprivileged user namespaces. + 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. + * libs/libtcb.c (tcb_drop_priv_r): Behave as non-root when + setgroups_allowed() returns 0. + + libtcb: add setgroups_allowed() helper. + 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. + * libs/libtcb.c (setgroups_allowed) [__linux__]: New function. + (setgroups_allowed) [!__linux__]: New stub returning 1. + 2024-12-22 Björn Esser tcb_(un)convert: Check for UID and EUID to be 0 before proceeding. diff --git a/libs/libtcb.c b/libs/libtcb.c index 872e435..25b9c46 100644 --- a/libs/libtcb.c +++ b/libs/libtcb.c @@ -167,6 +167,33 @@ static int sys_setgroups(size_t size, const gid_t *list) return syscall(SYS_setgroups, size, list); } +#ifdef __linux__ +/* + * In an unprivileged user namespace the kernel writes "deny" to + * /proc/self/setgroups, after which setgroups(2) is permanently + * denied for this process. Detect that situation so callers of + * tcb_drop_priv_r() can short-circuit instead of failing on + * setgroups(0, NULL). + */ +static int setgroups_allowed(void) +{ + int fd; + char buf[5]; + ssize_t n; + + fd = open("/proc/self/setgroups", O_RDONLY | O_NOCTTY); + if (fd == -1) + return 1; + n = read(fd, buf, 5); + close(fd); + if (n != 5) + return 1; + return memcmp(buf, "deny\n", 5) != 0; +} +#else +static int setgroups_allowed(void) { return 1; } +#endif + #define PRIV_MAGIC 0x1004000a #define PRIV_MAGIC_NONROOT 0xdead000a @@ -188,6 +215,17 @@ int tcb_drop_priv_r(const char *name, struct tcb_privs *p) return 0; } +/* + * In an unprivileged user namespace setgroups(2) is denied by the + * kernel. Without it we cannot drop supplementary groups, and being + * euid 0 inside such a namespace does not carry real privileges + * anyway, so behave as if we were non-root. + */ + if (!setgroups_allowed()) { + p->is_dropped = PRIV_MAGIC_NONROOT; + return 0; + } + if (stat(TCB_DIR, &st)) return -1;