fix(iso): sweep ii-builder leftovers via NSpid before userdel
Tests / tests (push) Successful in 2m19s
Tests / tests (push) Successful in 2m19s
end-4's ./setup sudo keepalive orphans a `sleep 60` child as ii-builder; userdel then exits 8 and aborts the ISO build (CI exit 8). Sweep uid-matching processes inside the build chroot before userdel, mirroring shadow's user_busy_processes() checks (real/effective/saved uid + root device/inode). Because arch-chroot mounts proc before `unshare --fork --pid`, /proc entry numbers are outer-namespace pids: signal the last NSpid field instead, require matching NSpid depth, and surface kill errors instead of swallowing them. userdel keeps its stderr and, on final failure, dumps the leftover process list before aborting.
This commit is contained in:
@@ -195,9 +195,91 @@ if [[ -d "${DOTS_REPO}/sdata/dist-arch" ]]; then
|
|||||||
\( -name src -o -name pkg \) -prune -exec rm -rf {} +
|
\( -name src -o -name pkg \) -prune -exec rm -rf {} +
|
||||||
fi
|
fi
|
||||||
rm -f /etc/sudoers.d/99-ii-builder
|
rm -f /etc/sudoers.d/99-ii-builder
|
||||||
if ! userdel -r "${BUILD_USER}" 2>/dev/null; then
|
|
||||||
|
# Processes still running as BUILD_USER make userdel exit 8 and abort the
|
||||||
|
# build. end-4's ./setup is the usual source: sudo_init_keepalive forks
|
||||||
|
# `(sleep 60; sudo true) &` and its EXIT trap kills only the subshell,
|
||||||
|
# orphaning the sleep child for up to a minute. Mirrors shadow's
|
||||||
|
# user_busy_processes(): match real/effective/saved uid AND require the
|
||||||
|
# process to live inside this build chroot - the runner shares our PID
|
||||||
|
# namespace, and a host process could carry the same uid.
|
||||||
|
#
|
||||||
|
# mkarchiso runs this script via arch-chroot, which mounts proc *before*
|
||||||
|
# `unshare --fork --pid`: /proc entry numbers belong to the outer pid
|
||||||
|
# namespace, while kill(2) resolves pids in our (nested) namespace. Use the
|
||||||
|
# last NSpid field - the pid in our own namespace - for signalling; keep the
|
||||||
|
# /proc entry number for logging, since that is what userdel reports.
|
||||||
|
nspid_fields() {
|
||||||
|
awk -F'\t' '/^NSpid:/{ $1=""; sub(/^ +/, ""); print; exit }' "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
builder_procs() {
|
||||||
|
local uid broot st p key real eff saved kpid cmd
|
||||||
|
local -a nsf self_nsf=()
|
||||||
|
uid=$(id -u "${BUILD_USER}") || return 0
|
||||||
|
broot=$(stat -Lc '%d:%i' /)
|
||||||
|
read -r -a self_nsf < <(nspid_fields /proc/self/status)
|
||||||
|
for st in /proc/[0-9]*/status; do
|
||||||
|
[[ -r "$st" ]] || continue
|
||||||
|
real=""; eff=""; saved=""
|
||||||
|
while read -r key real eff saved _; do
|
||||||
|
[[ $key == "Uid:" ]] && break
|
||||||
|
done < "$st" || continue
|
||||||
|
[[ $real == "$uid" || $eff == "$uid" || $saved == "$uid" ]] || continue
|
||||||
|
p=${st#/proc/}; p=${p%/status}
|
||||||
|
# Stat of /proc/<pid>/root needs CAP_SYS_PTRACE (or same-uid): exactly
|
||||||
|
# what shadow's userdel uses, so a process we cannot see here is one
|
||||||
|
# userdel cannot flag either. Compare by device+inode; skip on failure.
|
||||||
|
[[ $(stat -Lc '%d:%i' "/proc/$p/root" 2>/dev/null) == "$broot" ]] || continue
|
||||||
|
# NSpid chain length must equal ours, i.e. the process must live in our
|
||||||
|
# pid namespace - only then is its last field killable from here.
|
||||||
|
nsf=()
|
||||||
|
read -r -a nsf < <(nspid_fields "/proc/$p/status") || continue
|
||||||
|
if ((${#nsf[@]} > 0 && ${#nsf[@]} == ${#self_nsf[@]})); then
|
||||||
|
kpid=${nsf[${#nsf[@]} - 1]}
|
||||||
|
else
|
||||||
|
kpid="-"
|
||||||
|
fi
|
||||||
|
cmd=$(tr '\0' ' ' < "/proc/$p/cmdline" 2>/dev/null)
|
||||||
|
printf '%s\t%s\t%s\n' "$p" "$kpid" "$cmd"
|
||||||
|
done
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
sweep_builder_procs() {
|
||||||
|
local sig=$1 p kpid cmd kerr
|
||||||
|
local -a pids=()
|
||||||
|
while IFS=$'\t' read -r p kpid cmd; do
|
||||||
|
[[ -n $p ]] || continue
|
||||||
|
if [[ -z $kpid || $kpid == "-" ]]; then
|
||||||
|
echo "[customize] ${BUILD_USER} process outside our pid namespace (cannot signal): pid $p: $cmd"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo "[customize] ${BUILD_USER} process ($sig): pid $p: $cmd"
|
||||||
|
pids+=("$kpid")
|
||||||
|
done < <(builder_procs)
|
||||||
|
if ((${#pids[@]} > 0)); then
|
||||||
|
# Surface signal failures (they used to be swallowed, hiding a
|
||||||
|
# pid-namespace mismatch as a silent no-op sweep).
|
||||||
|
if ! kerr=$(kill -"$sig" "${pids[@]}" 2>&1); then
|
||||||
|
[[ -n $kerr ]] && printf '[customize] kill -%s: %s\n' "$sig" "$kerr"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
sweep_builder_procs TERM
|
||||||
|
sleep 2
|
||||||
|
sweep_builder_procs KILL
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
if ! userdel -r "${BUILD_USER}"; then
|
||||||
rm -rf "/home/${BUILD_USER:?}"
|
rm -rf "/home/${BUILD_USER:?}"
|
||||||
userdel "${BUILD_USER}"
|
if ! userdel "${BUILD_USER}"; then
|
||||||
|
echo "[customize] FATAL: cannot remove user ${BUILD_USER}; leftover processes:" >&2
|
||||||
|
builder_procs >&2 || true
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
chown -R root:root "${DOTS_REPO}"
|
chown -R root:root "${DOTS_REPO}"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user