#154: Reject setuid multicall invocations where argv[0] doesn't match
AT_EXECFN from the ELF auxiliary vector. Prevents an attacker from
spoofing argv[0] to route the multicall binary to a different tool
in setuid context. Only enforced when euid != uid.
#155: Add daily cargo-audit workflow matching uutils/coreutils pattern.
Also triggers on Cargo.toml/Cargo.lock changes.
Fixes#154. Fixes#155.
First release under the uutils organization.
Highlights:
- Repo transferred to uutils/shadow-rs
- uucore upgraded to 0.8
- nix crate fully replaced by rustix
- Default install now uses standalone per-tool binaries with
least-privilege setuid layout
- println!/eprintln! replaced with non-panicking writes
- Unwind tables suppressed in release builds
- NSS-backed user lookup via getpwuid_r
- 35+ security findings addressed across 6 review rounds
Fully remove nix as a direct dependency of the shadow-rs workspace.
New module shadow_core::process provides process-wide POSIX wrappers
via libc for setuid/setgid/initgroups/execv/sigprocmask. These MUST
use libc (not rustix) because rustix intentionally provides only
per-thread versions, which are incorrect for setuid-root tools.
Migrations:
- hardening.rs: setrlimit → rustix::process, sigprocmask → process
module, User::from_uid → /etc/passwd parser lookup
- pam.rs: nix::sys::termios → rustix::termios
- passwd.rs: full migration (seteuid, setuid, getuid, User lookup)
- newgrp.rs: full migration (termios, User lookup, setgid, initgroups,
execv)
- chpasswd/chage/chfn/chsh: setuid → shadow_core::process
- lock.rs tests: fcntl_getfd → rustix::io
libc stays for: PAM FFI, crypt(3) FFI, process-wide POSIX wrappers.
nix remains only as a transitive dep of uucore (upstream).
CRITICAL fixes:
- crypt: constant-time hash comparison via `subtle::ConstantTimeEq`
to prevent timing side-channel attacks (#119)
- useradd: hold all locks (passwd, shadow, group) during entire
mutation window to prevent TOCTOU races (#120)
HIGH fixes:
- newgrp: read shell from passwd entry instead of tainted $SHELL (#121)
- sysroot: reject path traversal (.. components) in --root/--prefix (#122)
- userdel, usermod: replace .expect() with proper UResult error
handling — no panics in setuid-root code (#123)
- userdel: canonicalize home path before recursive delete (#124)
- newgrp: zeroize password memory on drop via Zeroizing<String> (#125)
- grpck, pwck, userdel, usermod: add harden_process() call (#127)
MEDIUM fix:
- useradd: find next available subid range instead of fixed formula,
prevents overlapping container ID namespaces (#129)
- Add homepage.workspace to root package (missing vs subcrates)
- Gate Docker test-docker job to push only (seccomp:unconfined
and SYS_ADMIN are unsafe for untrusted PR contexts)
Feature-gated binary (--features completions) that generates bash/zsh/fish/
elvish/powershell completions from each tool's uu_app() definition.
Usage:
shadow-rs-completions passwd --shell bash
shadow-rs-completions --all --shell zsh --dir completions/
Also updates generate-completions.sh to use the new binary and adds
windows-sys skip to deny.toml for transitive clap dep.
Fixes#106
Eliminates duplicate nix crate that caused cargo-deny ban failure.
Adapts to nix 0.30 API: fsync/fcntl/fchownat now take AsFd instead
of raw i32 file descriptors; use AT_FDCWD for fchownat.
All 14 shadow-utils tools are now implemented.
groupadd: create groups with auto GID allocation, system groups, force mode.
groupdel: delete groups, checks for primary group usage.
groupmod: modify GID, rename, password changes.
grpck: verify /etc/group and /etc/gshadow integrity.
chfn: change GECOS sub-fields (name, room, phone).
chsh: change login shell with /etc/shells validation.
newgrp: change effective group with crypt(3) password verification.
449 tests, zero clippy warnings, all 14 tools in multicall binary.
From OpenBSD pw_init() pattern:
Fixes#43 — suppress_core_dumps(): RLIMIT_CORE=0 + PR_SET_DUMPABLE=0.
Prevents core dumps that could expose password hashes, and prevents
ptrace attachment to the setuid process.
Fixes#44 — raise_file_size_limit(): RLIMIT_FSIZE=RLIM_INFINITY.
Prevents malicious caller from truncating /etc/shadow via ulimit -f.
Fixes#45 — Zero-length output guard in atomic_write.
Refuses to replace original file with empty output. A zero-length
/etc/shadow locks out all users.
3 new tests: test_core_dump_suppression, test_raise_file_size_limit,
test_zero_length_write_rejected.
151 tests, zero clippy warnings.