[Pkg-shadow-devel] Bug#1007758: shadow: please consider avoiding chroot in shadowconfig

Johannes Schauer Marin Rodrigues josch at debian.org
Wed Mar 16 11:18:57 GMT 2022

Source: shadow
Version: 1:4.11.1+dfsg1-2
Severity: normal
Tags: patch
User: debian-dpkg at lists.debian.org
Usertags: dpkg-root-support
X-Debbugs-Cc: josch at debian.org


when creating chroots for new architectures that are in the process of
being bootstrapped without yet having emulation support from qemu, it is
not possible to run maintainer scripts inside the foreign architecture
chroot because foreign architecture ELF binaries cannot be executed. The
solution to that problem is to run maintainer scripts from outside the
chroot and use the DPKG_ROOT environment variable to instruct the
maintainer script on which chroot to operate. By default, for normal
installations, that environment variable is set, but empty.

Apart from init-system-helpers and pam, all packages in the
Essential:yes set have support for DPKG_ROOT already. To start building
packages we also need to install build-essential. In debootstrap, the
buildd variant includes the Essential:yes packages, Priority:required
packages and build-essential. Strictly speaking passwd is not necessary
to start building packages as it only gets installed in the buildd
variant of debootstrap because it is Priority:required. The postinst of
apt also indirectly depends on passwd via adduser to create the _apt
user, but since apt is able to operate without the _apt user, this also
doesn't make passwd required for the early native bootstrap phase.

The patch at the end of this mail proposes two ways to add support for
DPKG_ROOT to the shadowconfig script as it is called by the passwd
postinst. The first method, which is disabled by a "if false" uses the
--root parameter to pwck, grpck, pwconv and grpconv to let these tools
chroot into the directory stored in DPKG_ROOT and then operate on that
directory instead of /. Since the DPKG_ROOT variable is empty for normal
installations, that codepath would also transparently work for those
without any conditionals.

The second method that would solve this situation is shown in the second
branch of the if-statement. The disadvantage of the first method is,
that we still need to call chroot(). Currently, all other packages in
the Essential:yes, Priority:required and build-essential set can be
installed without any call to chroot(). The passwd postinst via
shadowconfig would be the only part that requires the chroot() call when
using the --root parameter. It would be nice if no component would
require doing the chroot() call because that would allow creating a
DPKG_ROOT chroot simply inside fakeroot. Thus, the second branch of the
conditional implements a method that works without chroot() and creates
a bit-by-bit identical result compared to a normal installation. While
those 10 lines are definitely complex and prone to breaking, please
consider using that method anyway because

  1) the code-path is never executed during a normal installation
     because the DPKG_ROOT variable is empty
  2) we regularly test this method in our CI system and would send
     patches if it should break in the future
  3) if it breaks it would only break DPKG_ROOT support and not normal

What do you think?


cheers, josch

diff -Nru shadow-4.11.1+dfsg1/debian/shadowconfig shadow-4.11.1+dfsg1/debian/shadowconfig
--- shadow-4.11.1+dfsg1/debian/shadowconfig	2022-03-03 20:41:41.000000000 +0100
+++ shadow-4.11.1+dfsg1/debian/shadowconfig	2022-03-14 14:18:52.000000000 +0100
@@ -5,14 +5,40 @@

 shadowon () {
     set -e
-    pwck -q -r
-    grpck -r
-    pwconv
-    grpconv
-    chown root:root /etc/passwd /etc/group
-    chmod 644 /etc/passwd /etc/group
-    chown root:shadow /etc/shadow /etc/gshadow
-    chmod 640 /etc/shadow /etc/gshadow
+    if false; then
+        pwck -q -r --root "${DPKG_ROOT}/"
+        grpck -r   --root "${DPKG_ROOT}/"
+        pwconv     --root "${DPKG_ROOT}/"
+        grpconv    --root "${DPKG_ROOT}/"
+    elif [ -n "$DPKG_ROOT" ] \
+        && cmp "${DPKG_ROOT}/etc/passwd" "${DPKG_ROOT}/usr/share/base-passwd/passwd.master" 2>/dev/null \
+        && cmp "${DPKG_ROOT}/etc/group" "${DPKG_ROOT}/usr/share/base-passwd/group.master" 2>/dev/null; then
+        # If dpkg is run with --force-script-chrootless and if /etc/passwd
+        # and /etc/group are unchanged, we avoid the chroot() call by manually
+        # processing the files. This produces bit-by-bit identical results
+        # compared to the normal case as shown by the CI setup at
+        # https://salsa.debian.org/helmutg/dpkg-root-demo/-/jobs
+        for f in passwd group; do
+            cp -a "${DPKG_ROOT}/etc/$f" "${DPKG_ROOT}/etc/$f-"
+        done
+        chmod 600 "${DPKG_ROOT}/etc/passwd-"
+        sed -i 's/^\([^:]\+\):\*:/\1:x:/' "${DPKG_ROOT}/etc/group" "${DPKG_ROOT}/etc/passwd"
+        [ -n "$SOURCE_DATE_EPOCH" ] && epoch=$SOURCE_DATE_EPOCH || epoch=$(date +%s)
+        sed "s/^\([^:]\+\):.*/\1:*:$((epoch/60/60/24)):0:99999:7:::/" "${DPKG_ROOT}/etc/passwd" > "${DPKG_ROOT}/etc/shadow"
+        sed "s/^\([^:]\+\):.*/\1:*::/" "${DPKG_ROOT}/etc/group" > "${DPKG_ROOT}/etc/gshadow"
+        touch "${DPKG_ROOT}/etc/.pwd.lock"
+        chmod 600 "${DPKG_ROOT}/etc/.pwd.lock"
+    else
+        pwck -q -r
+        grpck -r
+        pwconv
+        grpconv
+    fi
+    chown root:root "${DPKG_ROOT}/etc/passwd" "${DPKG_ROOT}/etc/group"
+    chmod 644 "${DPKG_ROOT}/etc/passwd" "${DPKG_ROOT}/etc/group"
+    chown root:shadow "${DPKG_ROOT}/etc/shadow" "${DPKG_ROOT}/etc/gshadow"
+    chmod 640 "${DPKG_ROOT}/etc/shadow" "${DPKG_ROOT}/etc/gshadow"

 shadowoff () {

More information about the Pkg-shadow-devel mailing list