29 Commits

Author SHA1 Message Date
oech3 21ac3d4437 shadow-core: use getrandom from rustix (#165)
to replace unsafe libc::getrandom

Co-authored-by: oech3 <>
2026-05-24 14:09:12 +02:00
Pierre Warnier 7c6cf221bc process: use rustix::param::linux_execfn instead of unsafe getauxval 2026-04-22 19:09:33 +02:00
Pierre Warnier 4ecf63ec1f security: AT_EXECFN validation + audit.yml (#154, #155)
#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.
2026-04-22 18:13:36 +02:00
Pierre Warnier 86af8fc45b chore: align all versions to 0.2.0 2026-04-22 17:41:16 +02:00
Pierre Warnier 93eb852924 release: v0.2.0 — uutils adoption
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
2026-04-22 16:53:01 +02:00
Pierre Warnier e43a47a34e Merge branch 'main' into renovate/uucore-0.x 2026-04-22 16:23:50 +02:00
Pierre Warnier 3e6dab9b18 docs: update all URLs from shadow-utils-rs to uutils org
Repo transferred to uutils/shadow-rs. Update all references:
- README.md: CI badge, license badge, clone URL
- Cargo.toml: repository, homepage
- CONTRIBUTING.md: clone URL
- SECURITY.md: advisory URL
- shadow-rs.spec: source URL
- CLAUDE.md: PR review API URLs, kanban board
2026-04-22 15:31:42 +02:00
renovate[bot] 4639659b93 chore(deps): update rust crate uucore to 0.8 2026-04-22 12:35:58 +00:00
Pierre Warnier 32c94fbc76 shadow-core, tools: complete nix removal — phase 2 (#140)
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).
2026-04-22 11:26:49 +02:00
Pierre Warnier bc8ea13328 shadow-core, tools: migrate nix to rustix — phase 1 (#140)
Replace straightforward nix API calls with rustix equivalents across
15 files. rustix is already a transitive dependency (via clap) and
provides raw syscall access without libc overhead.

Phase 1 covers simple 1:1 replacements:
- UID/GID: getuid/geteuid/getgid → rustix::process
- File ops: fsync, umask, chownat → rustix::fs
- Process: getpid, kill (stale lock detection) → rustix::process
- Filesystem: chroot, chdir → rustix::process

6 crates fully off nix: groupadd, groupdel, groupmod, useradd,
userdel, usermod.
4 crates partially migrated (nix kept for setuid()): chpasswd, chage,
chfn, chsh.
2 crates cleaned of unused nix/libc deps: pwck, grpck.

Phase 2 (hardening.rs, passwd.rs, newgrp.rs, pam.rs) deferred — these
have complex signal/termios/User::from_uid usage requiring more care.

Tested on debian/alpine/fedora — all pass.
2026-04-22 10:47:03 +02:00
Pierre Warnier 6af298561c security: fix 10 review findings across 11 files
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)
2026-04-03 17:06:56 +02:00
Pierre Warnier fdb6d46a05 review: address 2 Copilot review comments
- 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)
2026-04-03 15:01:26 +02:00
Pierre Warnier 86eb48de44 meta: align Cargo.toml metadata with uutils conventions
- Fix root package edition (2021 → workspace 2024)
- Use workspace inheritance for license, authors, repository, rust-version
- Fix keywords: "coreutils" → "shadow-utils", add "uutils"
- Add homepage, keywords, categories to workspace.package
- Add workspace metadata inheritance to all 15 crate Cargo.toml files
- Normalize tool descriptions to "tool ~ (shadow-rs) verb phrase" format
- Add feat_common_core feature alias (groups all 14 tools)
- Fix pre-existing clippy and fmt issues from edition 2024 alignment
2026-04-03 14:44:59 +02:00
Pierre Warnier e29b06c461 e2e: add end-to-end deployment tests (#102)
Add automated deployment testing in Docker that simulates a real
system-wide installation: build from source, install replacing GNU
shadow-utils, then run 97 assertions covering:

- Symlink dispatch and multicall dispatch (all 14 tools)
- Setuid bits and non-root privilege enforcement
- Full user lifecycle (useradd → chpasswd → usermod → pwck → userdel)
- Full group lifecycle (groupadd → groupmod → groupdel → grpck)
- Individual tool tests (chage, chfn, chsh, passwd -l/-u)
- PAM authentication (su with known password via expect)
- nscd cache invalidation
- Landlock sandboxing (when kernel supports it)
- Ansible integration (user/group modules as real-world consumer)

New files:
- docker/Dockerfile.e2e — multi-stage build + debian:trixie runtime
- tests/e2e/deploy-test.sh — bash test harness with 97 assertions
- tests/e2e/ansible-test.yml — Ansible playbook integration test
- .dockerignore — prevent sending target/ and .git/ to Docker

Modified:
- docker-compose.yml — add e2e service (no changes to existing services)
- Cargo.toml — add pam feature forwarding to root package
- shadow-core/pam.rs — add #[link(name = "pam")] for linker

Closes #109, closes #110, closes #111, closes #112, closes #113
Refs #102
2026-04-03 13:22:55 +02:00
Pierre Warnier 829b37f7a8 completions: add clap_complete shell completion generator (#106)
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
2026-04-03 12:18:18 +02:00
Pierre Warnier 7bd47ff3d0 tests: add integration tests for all 14 tools, add CHANGELOG
Add integration tests for the 8 remaining tools:
- useradd, userdel, usermod (user management)
- groupadd, groupdel, groupmod (group management)
- pwck, grpck (file consistency checkers)

101 new tests covering clap parsing, flag conflicts, error paths,
and root-only file mutation operations with synthetic /etc/ files.

Total test count: 461 → 562.

Also adds CHANGELOG.md for v0.1.0 release.
2026-03-24 15:59:04 +01:00
Pierre Warnier dddd5713ac deps: upgrade nix 0.29 → 0.30 to unify with uucore
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.
2026-03-24 15:24:54 +01:00
Pierre Warnier 9e3faa966c address Copilot review: fix docs, feature-gate crypt, MSRV 1.94, CI msrv job 2026-03-24 14:43:38 +01:00
Pierre Warnier 896a4214b6 infra: uutils compliance — edition 2024, unsafe deny, dead_code deny
Fixes #79 — CODE_OF_CONDUCT.md (Contributor Covenant 2.1)
Fixes #80 — locales/en-US.ftl for all 14 tools
Fixes #81 — rust-version = "1.85.0" in workspace
Fixes #82 — edition 2024 (resolver 3, unsafe extern blocks)
Fixes #83 — GitHub Actions CI (fmt, clippy, test, cargo-deny)
Fixes #84 — .clippy.toml with MSRV and quality thresholds
Fixes #85 — deny.toml tightened (multiple-versions=deny)
Fixes #87 — panic=abort, codegen-units=1 in release profile
Fixes #89 — Makefile with install/uninstall/clean targets
Fixes #90 — workspace lints: cargo group, unused_qualifications

Key policy changes enforced by cargo:
- unsafe_code = "deny" workspace-wide. Only pam.rs and crypt.rs
  have #[allow(unsafe_code)] (C FFI boundaries).
- dead_code = "deny" workspace-wide. No unused code allowed.
- Edition 2024: set_var/remove_var are unsafe, so sanitize_env
  returns a Vec instead of mutating. Signal handler removed
  (was unsafe). localtime_r replaced with pure Rust calendar math.

456 tests, zero clippy warnings.
2026-03-24 14:30:22 +01:00
Pierre Warnier 8ee65a7552 phase3: implement groupadd, groupdel, groupmod, grpck, chfn, chsh, newgrp
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.
2026-03-24 10:47:07 +01:00
Pierre Warnier ed5bb0b71a phase2: implement useradd, userdel, usermod, chpasswd, chage
Fixes #57 — useradd: create user accounts with all core flags
  (-c, -d, -e, -f, -g, -G, -m, -M, -k, -N, -o, -p, -r, -s, -u, -U).
  UID/GID allocation, home dir creation, skel copy, shadow entry.

Fixes #62 — userdel: remove user from passwd/shadow/group/gshadow.
  Flags: -f, -r, -P. Removes from membership lists.

Fixes #63 — usermod: modify user properties.
  Flags: -c, -d, -e, -f, -g, -G, -a, -L, -U, -l, -s, -u, -P.
  Lock/unlock via shadow, group membership management.

Fixes #64 — chpasswd: batch password change from stdin.
  Reads username:password pairs, updates /etc/shadow.
  Supports -e (pre-encrypted), -R (chroot).

Fixes #65 — chage: password aging management.
  All flags: -d, -E, -I, -l, -m, -M, -R, -W.
  List mode (-l) shows aging info. Matches GNU output format.

364 tests, zero clippy warnings.
2026-03-24 10:19:52 +01:00
Pierre Warnier b0cbe5c8de phase2: shadow-core modules + tool scaffolds for user management
Fixes #58 — uid_alloc: UID/GID allocation from login.defs ranges.
Fixes #59 — skel: recursive /etc/skel copy with chown.
Fixes #60 — gshadow: /etc/gshadow parser with proptest.
Fixes #61 — subid: /etc/subuid + /etc/subgid parser with proptest.

Scaffolded 5 Phase 2 tool crates (useradd, userdel, usermod,
chpasswd, chage) as stubs. Updated workspace, multicall binary,
and feature flags.

240 tests on Debian, zero clippy warnings.
2026-03-24 10:07:13 +01:00
Pierre Warnier 7212b96c57 pwck: implement all checks, group parser, GNU-identical output
Fixes #52 — pwck tool with all 11 integrity checks:
  field count, unique usernames, valid UID/GID, primary group exists,
  home directory exists, valid login shell, passwd↔shadow matching,
  passwords in shadow, unique shadow entries, future last_change.
  Flags: -r, -s, -q, -R, positional [passwd [shadow]].
  Exit codes match GNU exactly (0/2/3/4/5/6).
  Output is bit-for-bit identical with GNU pwck.

Fixes #53 — /etc/group parser (GroupEntry) with read/write/proptest.

Fixes #54 — 40 tests: 30 pwck unit/integration + 10 group parser.

192 tests on Debian/Alpine/Fedora, zero clippy warnings.
2026-03-24 09:45:46 +01:00
Pierre Warnier c4607599ff security: core dump suppression, RLIMIT_FSIZE, zero-length guard
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.
2026-03-23 16:45:20 +01:00
Pierre Warnier 3ed6188aea tests: add proptest, edge cases, fuzz targets, integration tests, docs
Fixes #11 — tests/by-util/test_passwd.rs: 15 integration tests
  following uutils convention (status format, exit codes, lock/unlock
  cycle, aging, lifecycle, quiet mode, error cases).

Fixes #12 — fuzz targets for all parsers:
  fuzz_passwd_parse, fuzz_shadow_parse, fuzz_login_defs_parse,
  fuzz_validate_username. All must not panic on any input.

Fixes #15 — proptest round-trip tests for PasswdEntry, ShadowEntry:
  generate random valid entries, serialize, parse back, compare.

Fixes #16 — parser edge case tests:
  wrong field counts, empty files, roundtrip via file I/O, negative
  values, boundary values, unicode rejection, null bytes, mixed
  whitespace, duplicate keys, key-only lines.

Also: CONTRIBUTING.md, SECURITY.md created. README.md and CLAUDE.md
updated to reflect uucore integration and current project state.

142 tests passing on Debian/Alpine/Fedora, zero clippy warnings.
2026-03-23 14:11:19 +01:00