mirror of
https://github.com/uutils/shadow.git
synced 2026-10-16 02:48:14 -07:00
The setuid helpers that write /proc/<pid>/uid_map and gid_map: what Podman and rootless Docker call for every container they start. The kernel lets an unprivileged process map exactly one id into a namespace it created, its own; everything a container needs beyond that takes the privilege these carry, and they hand it out only within the ranges /etc/subuid and /etc/subgid grant the caller. Fedora ships them in shadow-utils; with these two, every tool the two reference distributions ship from the suite exists here. What is checked before anything is written was established by running the GNU helpers against a real user namespace, never by reading them: the operands must be a target and plain-decimal triples, a zero count is the overflow message; every outside range must sit inside one granted range -- an entry by login name or by numeric uid -- or be the caller's own id mapped once; root is exempt from neither, as newuidmap(1) says; and the target process must belong to the caller by real uid, account uid and the owner of /proc/<pid>, with all three named when they disagree. Overlapping ranges are refused by name; the kernel would refuse them with a bare EINVAL, so the message costs nothing and changes no accepted outcome. The map file is opened through a descriptor held on /proc/<pid>, so a pid recycled between the check and the write cannot redirect it, and the `fd:N` form takes such a descriptor from a caller that did its own checks. It is re-opened through /proc/self/fd rather than adopted, which keeps the tool free of unsafe. The tests do the real thing. A test user creates a namespace with unshare and the helper runs as that user, unprivileged, against it: the own-id mapping lands and the kernel's uid_map reads it back, the second write is refused by the kernel and reported, the fd form works, and every refusal is checked against the same live target. That runs on glibc and musl alike. The Debian image gains the uidmap package so tests/gnu-compat.sh can hold the helpers against GNU's. They take no --help -- any bad command line is the usage text and exit 1 -- so the comparison is on the refusals the two share rather than on help. Verified: make check clean with the unprivileged run; 912 tests on alpine and 934 on fedora with PAM; 393 e2e assertions against a real install, the kernel-visible writes included; 57 GNU comparisons; 34 arm64 assertions under emulation.
50 lines
2.4 KiB
Docker
50 lines
2.4 KiB
Docker
# shadow-rs development image — Debian Trixie (13)
|
|
#
|
|
# PAM: libpam0g-dev (Linux-PAM)
|
|
# SELinux: libselinux1-dev
|
|
# Default shell: /bin/bash
|
|
# Shadow-utils package: login + passwd
|
|
# Note: Trixie is now the default Debian for the Rust Docker image.
|
|
|
|
# Pinned: an unpinned tag lands a new toolchain on main with no PR signal,
|
|
# and clippy gains lints between releases. Renovate proposes the bump.
|
|
FROM rust:1.98-trixie
|
|
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
libpam0g-dev \
|
|
# uidmap: GNU newuidmap/newgidmap, for tests/gnu-compat.sh
|
|
uidmap \
|
|
libselinux1-dev \
|
|
libaudit-dev \
|
|
pkg-config \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
RUN rustup component add clippy rustfmt
|
|
# 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" /usr/local/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"]
|