Skip to content

fix(spawn): keep setgroups=allow in nested user namespaces so apt/su work (#434)#435

Merged
skeptomai merged 1 commit into
mainfrom
fix/nested-build-setgroups-allow
Jul 4, 2026
Merged

fix(spawn): keep setgroups=allow in nested user namespaces so apt/su work (#434)#435
skeptomai merged 1 commit into
mainfrom
fix/nested-build-setgroups-allow

Conversation

@skeptomai

Copy link
Copy Markdown
Collaborator

Summary

Fixes #434. After #432 removed the privileged-pod workaround, a rootless nested pelagos build (uid 0 without CAP_SYS_ADMIN) failed on any RUN step that calls setgroups(2) — notably apt-get:

E: setgroups 0 failed - setgroups (1: Operation not permitted)
E: Method http has died unexpectedly!

Cause

The parent id-map writer set /proc/<pid>/setgroups=deny before writing the child's gid_map. That deny is only needed for the unprivileged own-id mapping. In the nested build the root parent writes the map and holds CAP_SETGID, so it can write the gid_map while setgroups stays allow (what Docker/Podman/runc do for range-mapped build user namespaces). Latent until now — the old privileged pod skipped the userns entirely.

Fix

Both parent id-map writer sites (spawn + spawn_interactive): only write setgroups=deny when we do not hold CAP_SETGID; otherwise leave it allow.

Validation

  • Minimal repro (FROM debian / RUN apt-get install ca-certificates) under capsh --drop=cap_sys_admin on an ext4 node: fails before, succeeds after (Fetched … / Successfully built).
  • New test test_nested_build_setgroups_allowed (backend-independent); cargo test --lib 392 passed; cargo clippy -- -D warnings clean.
  • Completes the privileged-free in-cluster gruesome build (cargo Nested rootless pelagos build: cargo (flock) fails with EOVERFLOW on the userxattr overlay #432 + apt).

🤖 Generated with Claude Code

…work (#434)

After #432 removed the privileged-pod workaround, a rootless nested `pelagos
build` (uid 0 without CAP_SYS_ADMIN — a k8s pod) failed on any RUN step that
calls setgroups(2). apt was the visible casualty:

    E: setgroups 0 failed - setgroups (1: Operation not permitted)
    E: Method http has died unexpectedly!
    pelagos: error: RUN command failed with exit code 100

Cause: the parent id-map writer set /proc/<pid>/setgroups=deny before writing the
child's gid_map. That deny is only required for the unprivileged own-id mapping.
In the nested build the ROOT parent writes the map and holds CAP_SETGID, so it can
write the gid_map while setgroups stays "allow" — which is what Docker/Podman/runc
do for range-mapped build user namespaces. Denying it needlessly makes
setgroups(2) inside the container return EPERM. Latent until now because the old
privileged pod skipped the user namespace entirely.

Fix: in both parent id-map writer sites (spawn + spawn_interactive), only write
setgroups=deny when we do NOT hold CAP_SETGID; otherwise leave it allow.

Validated on an ext4 node: `FROM debian RUN apt-get install ...` under
`capsh --drop=cap_sys_admin` fails before, succeeds after. Unblocks the full
in-cluster gruesome build (cargo #432 + apt) with no privileged pod.

Adds `test_nested_build_setgroups_allowed` (+ docs) — backend-independent, asserts
the nested userns leaves /proc/self/setgroups=allow.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@skeptomai skeptomai merged commit 83fbc1f into main Jul 4, 2026
6 checks passed
@skeptomai skeptomai deleted the fix/nested-build-setgroups-allow branch July 4, 2026 18:43
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.

Nested/in-pod build: setgroups=deny in the user namespace breaks apt (and any privilege-dropping tool)

1 participant