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).
Since Rust 1.92, panic=abort no longer prevents unwind table emission
on Linux. For setuid-root binaries, unwind tables leak internal binary
layout information (function boundaries, call graph structure).
Add -C force-unwind-tables=no via .cargo/config.toml. This is the only
stable mechanism in Cargo 1.95 (profile-rustflags is still unstable).
Applied to all profiles — harmless in dev builds where debug info
already exposes far more than unwind tables.
Verified with readelf: passwd release binary has zero .eh_frame or
.eh_frame_hdr sections after this change.
Fixes#143. Raised by @oech3.
- show_error!/show_warning!: combine prefix + message into a single
writeln! call (was two writes: write! then writeln!)
- pam display_message: lock stderr before writing
- passwd PrivDrop::Drop: lock stderr before writing
- pwck check_passwd_entries: hoist stderr lock above the loop so
per-entry diagnostics reuse one locked handle instead of
reconstructing it on each iteration
println!() and eprintln!() panic when stdout/stderr is full or closed
(e.g., `command >/dev/full`). Replace all production-code occurrences
with non-panicking alternatives:
- show_error!/show_warning! macros rewritten to use
`let _ = writeln!(stderr())` instead of eprintln!
- Normal output uses `let _ = writeln!(stdout)` with locked handles
- PAM display_message uses `let _ = writeln!(stderr())`
Also fixes a pre-existing clippy::map_unwrap_or lint in atomic.rs.
Test code is intentionally left unchanged — panicking on write failure
in tests is acceptable and desirable for fast failure.
Fixes#141.