[FIX] rm -rf followed a bind mount into the host's /dev
make_rootfs wiped the rootfs with `rm -rf "$ROOT"` and no check on what was mounted under it. A diagnostic session had left `mount --rbind /dev "$ROOT/dev"` in place; the next run deleted straight through it into the real /dev. Every device node went -- zero, full, random, urandom, tty, console -- and /dev/null came back later as a regular file created by a redirection, which broke every `> /dev/null` on the machine and stopped the pass at its first pacman call. Two guards now, because unmounting alone is not enough: umount_chroot, then findmnt to CHECK, then refuse. A stale mount is a reason to stop, never a thing to delete through. /dev is also no longer written to at all. The bind became --rbind with --make-rslave, which brings the host's /dev/shm along as the tmpfs it already is -- so the separate tmpfs and the chmod that reached the host are both gone. That chmod was the reason this driver could touch /dev in the first place. The plain --bind was itself the original bug: it does not carry submounts, so the chroot saw /dev/shm as an empty 0755 stub, CPython's configure got EACCES from sem_open, and python was built without POSIX semaphores. --- FR --- make_rootfs effaçait le rootfs par `rm -rf "$ROOT"` sans vérifier ce qui était monté dessous. Une session de diagnostic avait laissé `mount --rbind /dev "$ROOT/dev"` en place ; l'exécution suivante a supprimé au travers, dans le /dev réel. Tous les nœuds de périphériques ont disparu — zero, full, random, urandom, tty, console — et /dev/null est revenu en fichier ordinaire créé par une redirection, ce qui a cassé tout `> /dev/null` sur la machine et arrêté la passe à son premier appel à pacman. Deux gardes désormais, car démonter ne suffit pas : umount_chroot, puis findmnt pour VÉRIFIER, puis refuser. Un montage résiduel est une raison de s'arrêter, jamais une chose à traverser. Et /dev n'est plus écrit du tout. Le bind devient --rbind avec --make-rslave, ce qui apporte le /dev/shm de l'hôte tel qu'il est déjà — un tmpfs — donc le tmpfs séparé et le chmod qui atteignait l'hôte disparaissent tous deux. Ce chmod était la raison pour laquelle ce pilote pouvait toucher /dev. Le --bind simple était lui-même le défaut d'origine : il ne porte pas les sous-montages, le chroot voyait donc /dev/shm comme une souche vide en 0755, le configure de CPython recevait EACCES de sem_open, et python était bâti sans sémaphores POSIX. Assisted-by: Claude Opus 5
This commit is contained in:
parent
c43289c5ca
commit
0da64c9933
1 changed files with 47 additions and 57 deletions
|
|
@ -145,6 +145,24 @@ SigLevel = Never
|
|||
[core]
|
||||
Server = file://$REPO1
|
||||
EOF
|
||||
# NOTHING MAY BE MOUNTED UNDER $ROOT WHEN THIS RUNS.
|
||||
#
|
||||
# `rm -rf` follows a bind mount and deletes what is on the other side. This
|
||||
# function had no such guard, and the cost was the host's /dev: a diagnostic
|
||||
# session left `mount --rbind /dev "$ROOT/dev"` in place, the next run wiped
|
||||
# the rootfs, and the delete went through the mount into the real /dev. Every
|
||||
# device node went -- zero, full, random, urandom, tty, console -- and
|
||||
# /dev/null came back later as a regular file created by a redirection, which
|
||||
# broke every `> /dev/null` on the machine.
|
||||
#
|
||||
# Two guards, because the first one is not enough: unmount, then CHECK, then
|
||||
# refuse. A stale mount is a reason to stop, never a thing to delete through.
|
||||
umount_chroot
|
||||
if findmnt -rno TARGET | grep -qF "$ROOT/"; then
|
||||
printf 'still mounted under %s:\n' "$ROOT" >&2
|
||||
findmnt -rno TARGET | grep -F "$ROOT/" | sed 's/^/ /' >&2
|
||||
die "refusing to rm -rf through a mount"
|
||||
fi
|
||||
sudo rm -rf "$ROOT" "$CACHE"
|
||||
sudo mkdir -p "$ROOT/var/lib/pacman" "$CACHE"
|
||||
# EVERYTHING in stage 1, not a hand-picked core.
|
||||
|
|
@ -516,71 +534,41 @@ mount_chroot() {
|
|||
# /dev/pts is not optional: without it any build step that opens a pty --
|
||||
# and gcc's testsuite driver does -- fails in a way that names the pty and
|
||||
# not the missing mount.
|
||||
for m in proc sys dev dev/pts dev/shm; do
|
||||
for m in proc sys dev; do
|
||||
sudo mkdir -p "$ROOT/$m"
|
||||
done
|
||||
mountpoint -q "$ROOT/proc" || sudo mount -t proc proc "$ROOT/proc"
|
||||
mountpoint -q "$ROOT/sys" || sudo mount -t sysfs sys "$ROOT/sys"
|
||||
mountpoint -q "$ROOT/dev" || sudo mount --bind /dev "$ROOT/dev"
|
||||
mountpoint -q "$ROOT/dev/pts" || sudo mount -t devpts devpts "$ROOT/dev/pts"
|
||||
# /dev/shm, for the same reason /dev/pts needs its own line: `mount --bind
|
||||
# /dev` does NOT carry submounts, and both of these are separate mounts on
|
||||
# the host.
|
||||
# /dev by --rbind, then --make-rslave. Not a plain --bind, and nothing under
|
||||
# it is ever modified.
|
||||
#
|
||||
# nss failed with
|
||||
# A plain --bind does NOT carry submounts, so the chroot saw /dev/shm as the
|
||||
# empty 0755 stub inside devtmpfs rather than the host's tmpfs. That cost
|
||||
# three passes: CPython runs sem_open at configure time, got EACCES, decided
|
||||
# the platform has no POSIX semaphores, and compiled _multiprocessing without
|
||||
# SemLock -- after which every consumer failed with a message blaming the
|
||||
# platform, days after the cause.
|
||||
#
|
||||
# ImportError: This platform lacks a functioning sem_open implementation.
|
||||
# https://github.com/python/cpython/issues/48020
|
||||
# The first fix mounted a separate tmpfs at $ROOT/dev/shm and chmod'ed it.
|
||||
# That was wrong in a way worth recording: $ROOT/dev was a bind of the HOST's
|
||||
# /dev, so writes under it reach the real /dev -- and the host's /dev/null
|
||||
# ended up replaced by a regular file, which broke every redirection on the
|
||||
# machine until it was recreated with mknod.
|
||||
#
|
||||
# multiprocessing.Semaphore is built on POSIX named semaphores, which glibc
|
||||
# implements as files under /dev/shm. Without the mount, sem_open returns
|
||||
# ENOSYS and CPython reports it as the PLATFORM lacking the feature -- which
|
||||
# reads like an s390x limitation and is a missing tmpfs.
|
||||
#
|
||||
# A tmpfs of its own rather than a bind: nothing here shares semaphores with
|
||||
# anything outside the chroot. Mode 1777 because unprivileged builds write
|
||||
# into it.
|
||||
#
|
||||
# AND IT MUST BE HERE BEFORE PYTHON IS BUILT. Mounting it later does not
|
||||
# help: CPython's configure runs sem_open at BUILD time, and with no /dev/shm
|
||||
# it concluded the platform has no working semaphores --
|
||||
#
|
||||
# POSIX_SEMAPHORES_NOT_ENABLED: 1 (in _sysconfigdata__*.py)
|
||||
# HAVE_SEM_OPEN: 1 (the function exists)
|
||||
#
|
||||
# -- and compiled _multiprocessing without SemLock. Any package that imports
|
||||
# multiprocessing then fails permanently, with a message blaming the
|
||||
# platform:
|
||||
#
|
||||
# ImportError: This platform lacks a functioning sem_open implementation.
|
||||
#
|
||||
# nss is the one that found it. A python built in a chroot without this mount
|
||||
# stays broken however many times the consumer is retried; the fix is to
|
||||
# rebuild python, which is why this comment says so.
|
||||
mountpoint -q "$ROOT/dev/shm" ||
|
||||
sudo mount -t tmpfs -o mode=1777,nosuid,nodev tmpfs "$ROOT/dev/shm"
|
||||
# The MODE, set explicitly and not left to the mount option.
|
||||
#
|
||||
# `-o mode=1777` only applies when this mount is created. The guard above
|
||||
# skips an existing one -- and an existing one may have been mounted without
|
||||
# the option, which is exactly what happened: findmnt showed the tmpfs there
|
||||
# while the directory was drwxr-xr-x, so uid 1000 could create nothing in it.
|
||||
#
|
||||
# That is what CPython's configure actually hit:
|
||||
#
|
||||
# sem_open: Permission denied
|
||||
# checking whether POSIX semaphores are enabled... no
|
||||
#
|
||||
# EACCES, not ENOSYS. The port spent a pass reading it as a missing mount and
|
||||
# another as a missing tmpfs, when the mount was present and the mode wrong.
|
||||
sudo chmod 1777 "$ROOT/dev/shm"
|
||||
# And PROVED from inside, as the build user, because every previous check of
|
||||
# this was made as root and passed while the build kept failing.
|
||||
# --rbind brings the host's /dev/shm along as it is: already a tmpfs, already
|
||||
# drwxrwxrwt. Nothing to create, nothing to chmod, nothing of the host's to
|
||||
# touch. --make-rslave then stops any mount this chroot makes from
|
||||
# propagating back out.
|
||||
mountpoint -q "$ROOT/dev" || {
|
||||
sudo mount --rbind /dev "$ROOT/dev"
|
||||
sudo mount --make-rslave "$ROOT/dev"
|
||||
}
|
||||
# PROVED from inside, as the build user. Every earlier check of this was made
|
||||
# as root and passed while the build kept failing.
|
||||
if ! sudo chroot --userspec="$BUILD_UID:$BUILD_GID" "$ROOT" \
|
||||
/usr/bin/env -i PATH=/usr/bin sh -c \
|
||||
'f=/dev/shm/.el-probe.$$; : > "$f" && rm -f "$f"' 2>/dev/null; then
|
||||
printf ' WARNING: /dev/shm is not writable by the build user;\n'
|
||||
printf ' python will build without POSIX semaphores\n'
|
||||
'f=/dev/shm/.el-probe.$$; : > "$f" && rm -f "$f"'; then
|
||||
die "/dev/shm is not writable by the build user; python would build without POSIX semaphores"
|
||||
fi
|
||||
# Sources and PKGBUILDs, already fetched by stage 1. Bind-mounting them
|
||||
# means the chroot needs no network at all, which is worth having: this
|
||||
|
|
@ -591,7 +579,9 @@ mount_chroot() {
|
|||
}
|
||||
|
||||
umount_chroot() {
|
||||
for m in repo2 build dev/shm dev/pts dev sys proc; do
|
||||
# -R for dev, because --rbind brought its submounts with it.
|
||||
mountpoint -q "$ROOT/dev" && sudo umount -R -l "$ROOT/dev"
|
||||
for m in repo2 build sys proc; do
|
||||
mountpoint -q "$ROOT/$m" && sudo umount -l "$ROOT/$m"
|
||||
done
|
||||
return 0
|
||||
|
|
|
|||
Loading…
Reference in a new issue