From 1ac3c5dc9969eb532bc5fb22bfedaa4f2b4293c0 Mon Sep 17 00:00:00 2001 From: r-vdp Date: Fri, 17 Apr 2026 10:57:19 +0200 Subject: [PATCH] nixos/shadow: use file capabilities for newuidmap/newgidmap Writing a multi-line /proc//[ug]id_map only requires CAP_SETUID/CAP_SETGID over the parent user namespace, not full root. shadow's own --with-fcaps install mode (70971457b761) sets exactly cap_setuid+ep / cap_setgid+ep, and Arch, Fedora and Debian have shipped these binaries with file capabilities instead of setuid for years. The setuid variant already drops to the same single capability before the uid_map write (see lib/idmapping.c), so the privilege at the point attacker-controlled data reaches the kernel is unchanged. The reduction is in the startup window: with file capabilities the process never has euid 0 and never holds the full capability set during NSS lookups, /etc/subuid parsing and /proc/ opening. The only functional difference is that mapping host uid 0 into a child namespace additionally needs CAP_SETFCAP, which the setuid path got implicitly. NixOS never puts uid 0 into auto-allocated subuid ranges, and granting it manually is a deliberate root-equivalent configuration; the release notes document the override for that case. nixosTests.{shadow,podman,docker-rootless} pass; the latter two exercise newuidmap/newgidmap via rootless containers. Supersedes #461172. Co-authored-by: Rasheeq Azad --- nixos/doc/manual/release-notes/rl-2611.section.md | 2 +- nixos/modules/programs/shadow.nix | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/nixos/doc/manual/release-notes/rl-2611.section.md b/nixos/doc/manual/release-notes/rl-2611.section.md index 798e204a578d..d94e2419c116 100644 --- a/nixos/doc/manual/release-notes/rl-2611.section.md +++ b/nixos/doc/manual/release-notes/rl-2611.section.md @@ -24,4 +24,4 @@ -- Create the first release note entry in this section! +- The `newuidmap` and `newgidmap` security wrappers are now installed with `cap_setuid`/`cap_setgid` file capabilities instead of the setuid-root bit, matching shadow's `--with-fcaps` install mode and other major distributions. Rootless containers (podman, docker-rootless, unprivileged user namespaces) are unaffected. The only behavioural change is that mapping host uid 0 via `/etc/subuid` (which NixOS never configures by default) additionally requires `cap_setfcap`; users who explicitly grant uid 0 in a subuid range can restore the previous behaviour with `security.wrappers.newuidmap.capabilities = lib.mkForce "cap_setuid,cap_setfcap+ep";`. diff --git a/nixos/modules/programs/shadow.nix b/nixos/modules/programs/shadow.nix index d179283ac559..3c53531d90f7 100644 --- a/nixos/modules/programs/shadow.nix +++ b/nixos/modules/programs/shadow.nix @@ -267,13 +267,22 @@ in group = "root"; inherit source; }; + mkCapRoot = capabilities: source: { + inherit capabilities source; + owner = "root"; + group = "root"; + }; in { su = mkSetuidRoot "${config.security.shadow.su.package}/bin/su"; sg = mkSetuidRoot "${cfg.package.out}/bin/sg"; newgrp = mkSetuidRoot "${cfg.package.out}/bin/newgrp"; - newuidmap = mkSetuidRoot "${cfg.package.out}/bin/newuidmap"; - newgidmap = mkSetuidRoot "${cfg.package.out}/bin/newgidmap"; + # File capabilities instead of setuid root, mirroring shadow's + # own --with-fcaps install mode and what Arch/Fedora/Debian ship. + # The kernel only requires CAP_SETUID/CAP_SETGID over the parent + # userns to write a multi-line /proc//[ug]id_map. + newuidmap = mkCapRoot "cap_setuid+ep" "${cfg.package.out}/bin/newuidmap"; + newgidmap = mkCapRoot "cap_setgid+ep" "${cfg.package.out}/bin/newgidmap"; } // lib.optionalAttrs config.users.mutableUsers { chsh = mkSetuidRoot "${cfg.package.out}/bin/chsh";