Files
shadow/docker/Dockerfile.debian
T
Pierre Warnier aa987bb390 newuidmap, newgidmap: set the id maps of a user namespace (#298)
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.
2026-09-07 15:46:36 +02:00

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"]