Write /etc/shadow as 0640 root:shadow when the group exists#46
Open
r-vdp wants to merge 1 commit intonikstur:mainfrom
Open
Write /etc/shadow as 0640 root:shadow when the group exists#46r-vdp wants to merge 1 commit intonikstur:mainfrom
r-vdp wants to merge 1 commit intonikstur:mainfrom
Conversation
userborn currently writes /etc/shadow as mode 0000, readable only via
CAP_DAC_OVERRIDE. That forces unix_chkpwd, the helper pam_unix
spawns when a non-root process (screen lockers, polkit auth dialogs)
needs to verify a password, to be installed setuid root.
Distros are split on this:
Debian/Ubuntu 0640 root:shadow unix_chkpwd setgid shadow
NixOS (perl path) 0640 root:shadow unix_chkpwd setuid root
Arch 0600 root:root unix_chkpwd setuid root
Fedora 0000 root:root unix_chkpwd setuid root
systemd-sysusers 0000 on first create, otherwise preserves existing
userborn is meant to be a drop-in for NixOS's update-users-groups.pl,
which has written root:shadow 0640 since 2016 (NixOS/nixpkgs fedd7cd).
The current 0000 is therefore a behavioural change.
The trade-off between the two models is:
0000 root:root + setuid-root unix_chkpwd
A code-exec bug in unix_chkpwd gives full root.
Wrong configuration of the shadow group does not grant access to the
shadow file.
0640 root:shadow + setgid-shadow unix_chkpwd
A code-exec bug in unix_chkpwd yields gid shadow only: the
attacker can read /etc/shadow and brute-force hashes offline, but
cannot setuid, cannot write anything.
The trade-off is that gid shadow becomes a meaningful boundary that
must be kept empty.
We can do so on NixOS using an assertion.
The Debian model trades a misconfiguration risk (something
gaining gid shadow) that can leak password hashes for removing a
root-equivalent setuid binary from the default install that could lead
to code-exec as root.
This change enables but does not force the setgid model: if the config
defines a `shadow` group (NixOS always does), /etc/shadow is written as
0640 owned by the shadow group. If not, the previous 0000 behaviour is kept.
The gid is resolved from the in-memory group database userborn just built,
so it works on first boot before /etc/group exists and is independent of
host NSS. The chown happens on the temp file before the atomic rename,
so the final path never appears with a stale group.
References:
Debian shadowconfig:
https://salsa.debian.org/debian/shadow/-/blob/master/debian/shadowconfig
Debian pam (sgid shadow on unix_chkpwd):
https://salsa.debian.org/vorlon/pam/-/blob/master/debian/rules
NixOS update-users-groups.pl:
nixos/modules/config/update-users-groups.pl:317-322
r-vdp
added a commit
to r-vdp/nixpkgs
that referenced
this pull request
Apr 17, 2026
unix_chkpwd only needs to read /etc/shadow, which both update-users-groups.pl and (patched) userborn write 0640 root:shadow. Running it setgid shadow bounds a code-exec bug to gid shadow (offline hash brute-force) instead of full root. Debian/Ubuntu have shipped this for years. Depends on /etc/shadow being group-readable by `shadow`; the perl activation already does this, userborn needs nikstur/userborn#46.
r-vdp
added a commit
to r-vdp/nixpkgs
that referenced
this pull request
Apr 17, 2026
unix_chkpwd only needs to read /etc/shadow, which both update-users-groups.pl and (patched) userborn write 0640 root:shadow. Running it setgid shadow bounds a code-exec bug to gid shadow (offline hash brute-force) instead of full root. Debian/Ubuntu have shipped this for years. Gid `shadow` thereby becomes equivalent to read access on all password hashes, so add an assertion that the group has no members. The group exists purely as a setgid target; nothing in nixpkgs adds to it. The assertion also covers users.users.*.extraGroups (folded into members by users-groups.nix) but cannot cover SupplementaryGroups= in unit files. Depends on /etc/shadow being group-readable by `shadow`; the perl activation already does this, userborn needs nikstur/userborn#46.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Alternative to #41
userborn currently writes
/etc/shadowas mode0000, readable only viaCAP_DAC_OVERRIDE.That forces
unix_chkpwd, the helperpam_unixspawns when a non-root process (screen lockers, polkit auth dialogs) needs to verify a password, to be installed setuid root.Distros are split on this:
userborn is meant to be a drop-in for NixOS's
update-users-groups.pl, which has writtenroot:shadow 0640since 2016 (NixOS/nixpkgs@fedd7cd).The current
0000is therefore a behavioural change.The trade-off between the two models is:
0000 root:root+ setuid-rootunix_chkpwdA code-exec bug in
unix_chkpwdgives full root.Wrong configuration of the
shadowgroup does not grant access to the shadow file.0640 root:shadow+ setgid-shadowunix_chkpwdA code-exec bug in
unix_chkpwdyields gidshadowonly: the attacker can read/etc/shadowand brute-force hashes offline, but cannotsetuid, cannot write anything.The trade-off is that gid
shadowbecomes a meaningful boundary that must be kept empty.We can do so on NixOS using an assertion.
The Debian model trades a misconfiguration risk (something gaining gid
shadow) that can leak password hashes for removing a root-equivalent setuid binary from the default install that could lead to code-exec as root.This change enables but does not force the setgid model: if the config defines a
shadowgroup (NixOS always does),/etc/shadowis written as0640owned by theshadowgroup.If not, the previous
0000behaviour is kept.The gid is resolved from the in-memory group database userborn just built, so it works on first boot before
/etc/groupexists and is independent of host NSS.The
chownhappens on the temp file before the atomic rename, so the final path never appears with a stale group.References:
unix_chkpwd): https://salsa.debian.org/vorlon/pam/-/blob/master/debian/rulesupdate-users-groups.pl:nixos/modules/config/update-users-groups.pl:317-322