5 Commits
Author SHA1 Message Date
Pierre Warnier 52c2debe8b release: publish a labelled static musl archive (#224) (#237)
* shadow-core: link libcrypt only where it exists; refuse pam on static musl

musl implements crypt(3) inside libc and ships no libcrypt. Asking for
-lcrypt there made a cross-build for x86_64-unknown-linux-musl fall through
to the host's glibc-built libxcrypt and fail on glibc-only symbols; Alpine
only worked because musl-dev ships an empty libcrypt.a.

Linux-PAM loads its modules with dlopen, which static musl does not have,
and no distribution ships libpam.a. Building the pam feature into a
crt-static musl binary is therefore a configuration error; reject it at
compile time instead of producing a binary whose authentication path can
never work.

The crypt tests stated the musl behaviour wrongly (musl does honour
rounds=; it lacks yescrypt). They now assert the real behaviour per target
instead of skipping.

* release: publish a static musl archive next to the glibc one

dist cannot vary cargo features per target and pam must be off for musl,
so the musl archive is built by 'make dist-musl' and attached by dist as an
extra artifact of the same release. The target enforces the lockfile,
checks the binary has no DT_NEEDED entry and packs it with the licence,
changelog, README and PLATFORM-SUPPORT.md so the trade-off travels with
the binary. The archive name carries a -static suffix so it is never
mistaken for the glibc build.

CI runs the same make target on every pull request, smoke-tests the
archive, runs shadow-core's unit tests against static musl and checks that
the pam feature is rejected there, so a tag is never the first time the
archive is built.

* docs: describe both release archives and what the static musl one gives up

The README now lists the two archives side by side with the one question a
reader has to answer (is PAM, NSS or yescrypt needed on this host?), and
PLATFORM-SUPPORT.md is rewritten around the archive that now exists: how
it is built, which CI job guards it, and the exact effect of each gap.
CONTRIBUTING listed two unsafe-permitted modules; there are three.

* chore: use the canonical uutils/shadow repository URL

The repository is uutils/shadow; the old shadow-rs name is a redirect. The
README badges already used the new name while package metadata, the --help
footer and SECURITY.md still pointed at the old one.
2026-09-04 10:04:38 +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
Pierre Warnier 9eff72d5a6 security: split caller_is_root (getuid) from privilege checks
Addresses Copilot review on PR #27:

1. Renamed is_root() → caller_is_root() using getuid() (real UID)
   to correctly identify the actual caller in setuid-root context.
   Admin operations (lock/unlock/delete/expire/aging) gate on caller.
   Default PAM password change remains accessible to non-root users
   changing their own password.

2. Fixed SECURITY.md: removed contradictory "open public issue" text,
   replaced with private contact guidance per Copilot suggestion.

3. Removed unused thiserror dep from uu_passwd Cargo.toml.
2026-03-23 14:36:22 +01:00
Pierre Warnier 92bf912a09 security: fix setuid-root privilege bypass (getuid vs geteuid)
Found by GitHub Copilot review on PRs #9, #10, #17.

CRITICAL: is_root() used geteuid() which is ALWAYS 0 when passwd is
installed setuid-root. Any unprivileged user could lock/unlock/delete
passwords and skip PAM authentication.

Fix: is_root() now uses getuid() (real UID) to check if the *caller*
is actually root, not just running a setuid binary.

Also:
- get_current_username() uses getuid() (real UID) for same reason
- Removed unused thiserror dependency from uu_passwd
- Fixed SECURITY.md placeholder text
2026-03-23 14:29:06 +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