apt-Installation laeuft ausserhalb des Dienst-Sandkastens

Der Sandkasten wird an dpkg und dessen Maintainer-Skripte weitervererbt; mit
ProtectKernelLogs=yes liefert "dmesg" ein EPERM und ein Postinst-Skript bricht
daran ab -- die Installation scheitert mit "dpkg returned an error code (1)",
obwohl mit dem Paket alles in Ordnung ist. apt laeuft jetzt ueber systemd-run,
also von PID 1 gestartet und damit ohne unsere Haertung.

Der zuvor eingebaute nsenter-Umweg beim Einbinden von Freigaben ist
zurueckgenommen. Diese Verben ruft der Lizenzserver nicht auf -- der Helfer
bleibt aber in beiden Repositorien dieselbe Datei, weil eine zweite Fassung
zwei Stellen waere, die auseinanderlaufen koennen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-02 21:49:56 +02:00
co-authored by Claude Opus 5
parent 4bc8637a74
commit 8e66d9e7bc
+35 -20
View File
@@ -258,19 +258,17 @@ case "$verb" in
# Kommandozeile (Prozessliste) und nie ueber die Umgebung
# (/proc/<pid>/environ).
read_stdin_limited > "$creds"
# Der aufrufende Dienst laeuft in einem eigenen Mount-Namensraum
# (ProtectSystem, PrivateTmp, ReadWritePaths). Ein Einbinden *darin*
# scheitert mit "mount error(1): Operation not permitted" -- gemessen: mit
# denselben Optionen und denselben Zugangsdaten gelingt es aus einer
# normalen Root-Shell sofort. Deshalb wird im Namensraum des Wirts
# eingebunden; von dort wird der Mount in den Namensraum des Dienstes
# weitergegeben (er haengt als "slave" darunter), die Anwendung sieht ihn
# also. Die Zugangsdatei unter /run ist ueber die Grenze lesbar, /run ist
# nicht privatisiert.
im_wirt=(nsenter --mount=/proc/1/ns/mnt --)
command -v nsenter >/dev/null 2>&1 || im_wirt=()
"${im_wirt[@]}" mkdir -p "$target"
if "${im_wirt[@]}" mount -t cifs "$share" "$target" \
# Eingebunden wird im Mount-Namensraum des aufrufenden Dienstes -- absichtlich:
# nur dieser Dienst liest die Dateien, und mit ihm verschwinden die
# Einbindungen wieder. Damit der Syscall durchkommt, braucht die Unit
# "@mount" im SystemCallFilter. Ohne das scheitert es mit
# "mount error(1): Operation not permitted"; gemessen war der Syscall-Filter
# die Ursache, nicht der Namensraum. Ein Umweg ueber nsenter in den
# Namensraum des Wirts ist keine Option -- "RestrictNamespaces=yes"
# verbietet setns, und das Recht dafuer wieder aufzugeben waere teurer als
# der Gewinn.
mkdir -p "$target"
if mount -t cifs "$share" "$target" \
-o "credentials=$creds,uid=$(id -u tesm 2>/dev/null || echo 0),gid=$(id -g tesm 2>/dev/null || echo 0),file_mode=0640,dir_mode=0750,vers=3.1.1,seal"
then
shred -u "$creds" 2>/dev/null || rm -f "$creds"
@@ -282,13 +280,12 @@ case "$verb" in
umount)
check_mount_target "${1:?Ziel fehlt}"
# Gegenstueck zu mount-cifs: der Mount liegt im Namensraum des Wirts, also
# muss er auch dort geloest werden. Ein umount im Dienst-Namensraum wuerde
# ihn nur dort ausblenden und die Freigabe bliebe eingebunden.
im_wirt=(nsenter --mount=/proc/1/ns/mnt --)
command -v nsenter >/dev/null 2>&1 || im_wirt=()
"${im_wirt[@]}" umount -l "$1" || true
"${im_wirt[@]}" rmdir "$1" 2>/dev/null || true
# Gegenstueck zu mount-cifs, und deshalb im selben Namensraum: loesen kann
# nur, wer die Einbindung auch sieht. Ein Aufruf aus einem anderen Dienst
# greift ins Leere -- genau deshalb laeuft die Aufraeumaufgabe fuer
# abgelaufene Einbindungen im Webprozess und nicht im Ueberwachungsdienst.
umount -l "$1" || true
rmdir "$1" 2>/dev/null || true
;;
apt-install)
@@ -296,9 +293,27 @@ case "$verb" in
for package in "$@"; do
check_in_list "$package" "${PACKAGES_ALLOWED[@]}" || fail "Paket nicht erlaubt: $package"
done
# apt laeuft ausserhalb des Sandkastens des aufrufenden Dienstes. Der wird
# sonst an dpkg und dessen Maintainer-Skripte weitervererbt, und dort
# scheitert etwas voellig Beilaeufiges: mit "ProtectKernelLogs=yes" gibt
# "dmesg" ein EPERM zurueck, ein Postinst-Skript bricht daran ab, und dpkg
# endet mit Code 1 -- die Installation schlaegt fehl, obwohl mit dem Paket
# alles in Ordnung ist. Gemessen und mit systemd-run reproduzierbar
# behoben: PID 1 startet den Prozess, also ohne unsere Haertung.
export DEBIAN_FRONTEND=noninteractive
if command -v systemd-run >/dev/null 2>&1 && [[ -S /run/dbus/system_bus_socket ]]; then
# Zwei getrennte Aufrufe, damit keine Shell dazwischen steht: die
# Paketnamen gehen als eigene Argumente an apt-get, nicht durch eine
# Kommandozeile, die sie erst wieder zerlegen muesste.
ausserhalb=(systemd-run --wait --pipe --collect --quiet
--setenv=DEBIAN_FRONTEND=noninteractive)
"${ausserhalb[@]}" apt-get update -qq
"${ausserhalb[@]}" apt-get install -y -qq "$@"
else
# Kein systemd (Container, Entwicklungsrechner): direkt, wie bisher.
apt-get update -qq
apt-get install -y -qq "$@"
fi
;;
certbot-issue)