Skip to content

Commit b8805df

Browse files
committed
libtcb: tolerate setgroups EPERM in unprivileged user namespaces
In a user namespace where /proc/self/setgroups is "deny", setgroups(2) is permanently rejected by the kernel. Perform the regular privilege drop and only tolerate sys_setgroups(0, NULL) failing with EPERM in such a namespace; in that case the kernel guarantees no supplementary group could have been gained via the namespace, so leaving the list in place is safe. Record this with a new PRIV_MAGIC_NOSETGROUPS state so that tcb_gain_priv_r() skips the matching setgroups() call. Fixes failures of pam_tcb, libnss_tcb, tcb_unconvert and shadow's shadowtcb_drop_priv() when running under rootless container.
1 parent f00de37 commit b8805df

2 files changed

Lines changed: 42 additions & 4 deletions

File tree

‎ChangeLog‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
2026-05-06 Egor Ignatov <egori at altlinux.org>
22

3+
libtcb: tolerate setgroups EPERM in unprivileged user namespaces.
4+
In a user namespace where /proc/self/setgroups is "deny",
5+
setgroups(2) is permanently rejected by the kernel. Perform the
6+
regular privilege drop and only tolerate sys_setgroups(0, NULL)
7+
failing with EPERM in such a namespace; in that case the kernel
8+
guarantees no supplementary group could have been gained via the
9+
namespace, so leaving the list in place is safe. Record this
10+
with a new PRIV_MAGIC_NOSETGROUPS state so that tcb_gain_priv_r()
11+
skips the matching setgroups() call.
12+
Fixes failures of pam_tcb, libnss_tcb, tcb_unconvert and shadow's
13+
shadowtcb_drop_priv() when running under rootless container.
14+
* libs/libtcb.c (PRIV_MAGIC_NOSETGROUPS): New magic value.
15+
(tcb_drop_priv_r): Tolerate EPERM from sys_setgroups(0, NULL)
16+
when setgroups_allowed() returns 0 and record this by setting
17+
p->is_dropped to PRIV_MAGIC_NOSETGROUPS.
18+
(tcb_gain_priv_r): Accept PRIV_MAGIC_NOSETGROUPS and skip
19+
sys_setgroups() in that state.
20+
321
libtcb: add setgroups_allowed() helper.
422
Detect /proc/self/setgroups == "deny" to recognize an unprivileged
523
user namespace where setgroups(2) is permanently denied by the

‎libs/libtcb.c‎

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -196,10 +196,12 @@ static int setgroups_allowed(void) { return 1; }
196196

197197
#define PRIV_MAGIC 0x1004000a
198198
#define PRIV_MAGIC_NONROOT 0xdead000a
199+
#define PRIV_MAGIC_NOSETGROUPS 0xbeef000a
199200

200201
int tcb_drop_priv_r(const char *name, struct tcb_privs *p)
201202
{
202203
int res;
204+
int magic = PRIV_MAGIC;
203205
struct stat st;
204206
gid_t shadow_gid = -1;
205207
char *dir;
@@ -234,14 +236,30 @@ int tcb_drop_priv_r(const char *name, struct tcb_privs *p)
234236

235237
p->number_of_groups = res;
236238

237-
if (sys_setgroups(0, NULL) == -1)
238-
return -1;
239+
/*
240+
* Try to clear the supplementary group list. In a user namespace
241+
* where /proc/self/setgroups is "deny", setgroups(2) is permanently
242+
* denied and the call returns EPERM. In that case the kernel
243+
* guarantees the caller did not gain any group via the namespace
244+
* mechanism, so it is safe to leave the list in place; we record
245+
* this via PRIV_MAGIC_NOSETGROUPS so tcb_gain_priv_r() skips the
246+
* matching setgroups() call.
247+
*/
248+
if (sys_setgroups(0, NULL) == -1) {
249+
int saved_errno = errno;
250+
if (errno != EPERM || setgroups_allowed()) {
251+
errno = saved_errno;
252+
return -1;
253+
}
254+
magic = PRIV_MAGIC_NOSETGROUPS;
255+
}
256+
239257
if (!ch_gid(shadow_gid, &p->old_gid))
240258
return -1;
241259
if (!ch_uid(st.st_uid, &p->old_uid))
242260
return -1;
243261

244-
p->is_dropped = PRIV_MAGIC;
262+
p->is_dropped = magic;
245263
return 0;
246264
}
247265

@@ -253,6 +271,7 @@ int tcb_gain_priv_r(struct tcb_privs *p)
253271
return 0;
254272

255273
case PRIV_MAGIC:
274+
case PRIV_MAGIC_NOSETGROUPS:
256275
break;
257276

258277
default:
@@ -264,7 +283,8 @@ int tcb_gain_priv_r(struct tcb_privs *p)
264283
return -1;
265284
if (!ch_gid(p->old_gid, NULL))
266285
return -1;
267-
if (sys_setgroups(p->number_of_groups, p->grplist) == -1)
286+
if (p->is_dropped == PRIV_MAGIC &&
287+
sys_setgroups(p->number_of_groups, p->grplist) == -1)
268288
return -1;
269289

270290
p->is_dropped = 0;

0 commit comments

Comments
 (0)