mirror of
https://github.com/uutils/shadow.git
synced 2026-10-16 02:48:14 -07:00
* shadow-core: take /etc/.pwd.lock before the per-file lock FileLock took the per-file `.lock` first and `/etc/.pwd.lock` second. Two tools working on different files at once could then meet in the classic shape: one holding passwd.lock and waiting for .pwd.lock, the other holding shadow.lock and waiting for passwd.lock's holder to let go of .pwd.lock. Neither can move; both sit out the fifteen-second timeout; both fail -- on a system that was merely busy. Sampling the lock files every 250 ms while the whole suite ran showed it plainly: a chpasswd held shadow.lock for 28 s and a useradd held passwd, group and gshadow for 14 s, each waiting on the other, and seven tests failed with "timed out" on one lock or the other. Three runs after the change: no failures, and the suite back from 35-50 s to 17 s. .pwd.lock now comes first, the order lckpwdf(3) and the GNU tools use and the only one that cannot deadlock: whoever holds it is the only process asking for per-file locks, so it never waits for anyone who is waiting for it. If the per-file lock then cannot be taken, the .pwd.lock reference is given back, so nothing is held on failure. * useradd: lock the shadow file with the others, and write it first useradd wrote /etc/passwd and the group files in one transaction, then opened /etc/shadow on its own afterwards -- under a comment claiming the first locks were still held, which they were not: commit_all releases them. A shadow lock that timed out at that point left the account half made: a passwd line with no shadow line, which a second useradd then reported as already existing. That is exactly what turned up when the login tests put the suite under load. The shadow file is now locked with passwd, group and gshadow before anything is written, and written first, for the reason pwconv writes shadow first: a failure between the writes must never leave a passwd line whose hash is nowhere. The old write_shadow_entry helper stays for its unit test only. A regression test puts a directory where the shadow file should be and checks that useradd fails with /etc/passwd untouched. Before this change the passwd line was written and the failure came after. * login: begin a session on the system login is the twenty-fifth tool and the one uutils/login was archived towards. It is what getty runs on a terminal: it prompts for a name and, through the `login` PAM service, a password; refuses after LOGIN_RETRIES failures or LOGIN_TIMEOUT seconds; opens the PAM session and takes its environment; records the login in utmp and wtmp; hands the terminal to the user; runs the login shell as the user; and when the shell exits, closes the session and records the logout. Which login to match was a question. Debian 13 and Ubuntu 25.10 take login from util-linux, but every release in service -- Ubuntu 20.04, 22.04 and 24.04, Debian 12 -- ships shadow's; the two were run side by side on real pseudo-terminals and agree on everything that matters. This implements the option set they share (-p, -f, -h, name) plus util-linux's -H and --help, which conflict with nothing. There is no -h for help: -h has been the remote host since telnetd. -r, the rlogin protocol, is refused. util-linux's vhangup is not reproduced; shadow's login, which the installed base runs, does not do it either. A wrong password and an unknown name get the same words, after the same delay. -f skips authentication and with it the auth stack, pam_nologin included, as both tools do. A missing home falls back to / with a notice when DEFAULT_HOME allows. The shell runs as a child so the session can be closed afterwards; it gets a clean signal mask, login keeps its own, and groups, gid and uid are dropped in that order in one pre_exec. shadow-core grew what login needed and nothing had: pam setcred, open_session, close_session, user and environment; utmp/wtmp recording; getpwnam through NSS; a getty-style spawn_with_controlling_tty; spawn_as_user; and a watchdog. Without the pam feature the applet says PAM is not compiled in and exits: a login that cannot authenticate is not a login, and the static musl archive is documented accordingly. The tests drive the real binary on a pseudo-terminal the way getty would, answering each prompt once it appears: autologin, the environment with and without -p, a wrong then a right password, an unknown user, the retry limit, the missing home, and the terminal handover. Writing them under the full parallel suite is what exposed the two fixes committed before this one. Verified: make check clean with the unprivileged run; 892 tests on alpine and 882 on fedora; 368 e2e assertions against a real install, including the password path on a pty; 52 GNU comparisons; 32 arm64 assertions under emulation. * docker: give the Fedora image the login PAM service Fedora ships /etc/pam.d/login in util-linux, which the test image did not install. With no service file PAM falls back to `other`, which on Fedora denies everything: `login -f` failed its account check and the password path was refused before any prompt, so every login test failed there and only there. util-linux is now installed, which is what any real Fedora has. The login tests then run through Fedora's own stack -- pam_selinux, pam_loginuid, pam_namespace -- and pass, which makes it the second PAM configuration the session code is exercised against rather than a gap.
58 lines
2.7 KiB
Docker
58 lines
2.7 KiB
Docker
# shadow-rs development image — Fedora 45
|
|
#
|
|
# PAM: pam-devel (Linux-PAM)
|
|
# SELinux: libselinux-devel — SELinux is ENFORCING by default
|
|
# Default shell: /bin/bash
|
|
# Shadow-utils package: shadow-utils
|
|
# Note: Fedora has different PAM stack config and login.defs defaults than Debian.
|
|
# SELinux is enforcing, which affects file context on /etc/passwd, /etc/shadow.
|
|
|
|
# Pinned: see the note in Dockerfile.debian.
|
|
FROM fedora:46
|
|
|
|
RUN dnf install -y \
|
|
gcc \
|
|
pam-devel \
|
|
# util-linux carries /etc/pam.d/login on Fedora; without it PAM falls back
|
|
# to `other`, which denies everything, and login cannot be tested at all.
|
|
util-linux \
|
|
libselinux-devel \
|
|
audit-libs-devel \
|
|
pkgconf-pkg-config \
|
|
shadow-utils \
|
|
&& dnf clean all
|
|
|
|
# Install Rust via rustup
|
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y \
|
|
--default-toolchain stable \
|
|
--component clippy,rustfmt
|
|
ENV PATH="/root/.cargo/bin:${PATH}"
|
|
# Two throwaway PAM services so the PAM wrapper can be tested against a stack
|
|
# whose answer is known. Without them the only coverage is the end-to-end image
|
|
# authenticating a real account, which cannot exercise a *refusal* without
|
|
# depending on a wrong password being wrong.
|
|
RUN printf 'auth required pam_permit.so\naccount required pam_permit.so\npassword required pam_permit.so\nsession required pam_permit.so\n' > /etc/pam.d/shadow-rs-test-permit \
|
|
&& printf 'auth required pam_deny.so\naccount required pam_deny.so\npassword required pam_deny.so\nsession required pam_deny.so\n' > /etc/pam.d/shadow-rs-test-deny
|
|
|
|
|
|
|
|
# cargo-deny: pinned, checksum-verified prebuilt binary. The static musl build
|
|
# runs on glibc too, so every image shares one asset. Installing the binary
|
|
# (instead of `cargo install cargo-deny`) skips a multi-minute source compile.
|
|
ARG CARGO_DENY_VERSION=0.19.8
|
|
ARG CARGO_DENY_SHA256=70e769ae3872e34d45132b17040859175e11401dc12dddb0303e0b8c7d088f3f
|
|
RUN curl -LsSf "https://github.com/EmbarkStudios/cargo-deny/releases/download/${CARGO_DENY_VERSION}/cargo-deny-${CARGO_DENY_VERSION}-x86_64-unknown-linux-musl.tar.gz" -o /tmp/cargo-deny.tar.gz \
|
|
&& echo "${CARGO_DENY_SHA256} /tmp/cargo-deny.tar.gz" | sha256sum -c - \
|
|
&& tar zxf /tmp/cargo-deny.tar.gz -C /tmp \
|
|
&& install -m 0755 "/tmp/cargo-deny-${CARGO_DENY_VERSION}-x86_64-unknown-linux-musl/cargo-deny" /root/.cargo/bin/cargo-deny \
|
|
&& rm -rf /tmp/cargo-deny.tar.gz "/tmp/cargo-deny-${CARGO_DENY_VERSION}-x86_64-unknown-linux-musl" \
|
|
&& cargo-deny --version
|
|
|
|
RUN useradd -m -s /bin/bash testuser \
|
|
&& useradd -m -s /bin/sh testuser2 \
|
|
&& groupadd testgroup \
|
|
&& usermod -aG testgroup testuser
|
|
|
|
WORKDIR /workspace
|
|
CMD ["/bin/bash"]
|