From 9bf3f7ad228f686cee06be8f6092b9627ff8a44a Mon Sep 17 00:00:00 2001 From: Daniel Hilst <392820+dhilst@users.noreply.github.com> Date: Thu, 13 Aug 2026 07:34:09 -0300 Subject: [PATCH] fix(xcat-dep): run mock builds in an rslave mount namespace to protect the host cgroup mock mounts /sys/fs/cgroup into every build chroot. On the systemd build hosts every mount carries `shared` propagation, so the chroot cgroup joins the same peer group as the host's own /sys/fs/cgroup. When mock tears a chroot down -- its post-build --scrub, or an aborted build's cleanup -- the cgroup unmount PROPAGATES back through the shared peer group and unmounts the HOST's /sys/fs/cgroup. Every subsequent mock (and even new login sessions) then fails with 'Failed to determine whether the unified cgroups hierarchy is used: No medium found', wedging the whole build host until an operator remounts cgroup2. ppc64le is hit hardest because it leaks corpse chroot mounts on abort, but x86_64 shares the identical shared-cgroup exposure and is one bad abort away from the same failure. Re-exec mockbuild-all.pl inside a private mount namespace made rslave (unshare --mount --propagation slave): the namespace still sees host mounts one-way, but nothing mock mounts or unmounts can propagate out to the host, so a chroot teardown can no longer unmount the host cgroup. The namespace also auto-reaps every mount mock leaks when the process exits, so an aborted build no longer strands corpse mounts under /var/lib/mock. Best-effort and guarded: only re-execs as root with unshare present, and MOCKBUILD_ALL_MOUNTNS prevents a re-exec loop. Signed-off-by: Daniel Hilst <392820+dhilst@users.noreply.github.com> --- mockbuild-all.pl | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/mockbuild-all.pl b/mockbuild-all.pl index a0cbd41..27688f6 100755 --- a/mockbuild-all.pl +++ b/mockbuild-all.pl @@ -18,6 +18,36 @@ use MockBuildUtils qw(sh_quote print_step version_matches required_pkgs rpm_version rpm_release rpm_sigmd5 restamp_release_line cross_copy_genesis finalize_xcat_dep bump_dep_release_suffix); +# --- Mount-namespace isolation: guard the host cgroup against mock teardown propagation ---------- +# mock mounts /sys/fs/cgroup into every build chroot. On these systemd build hosts every mount is +# `shared`, so the chroot's cgroup joins the HOST's cgroup peer group. When mock tears a chroot down +# -- its post-build --scrub, or an aborted build's cleanup -- the unmount PROPAGATES back through the +# shared peer group and unmounts the HOST's /sys/fs/cgroup, after which every later mock (and even new +# login sessions) dies with "Failed to determine whether the unified cgroups hierarchy is used: No +# medium found". This bit ppc hardest (it leaks corpse chroot mounts on abort) but x86 shares the same +# shared-cgroup exposure. Re-exec inside a private mount namespace made rslave +# (`unshare --mount --propagation slave`): the namespace still sees host mounts (slave = one-way), but +# nothing mock mounts/unmounts can propagate OUT to the host. As a bonus the namespace tears down every +# mount mock leaks when we exit, so an aborted build can no longer leave corpse mounts under +# /var/lib/mock. Best-effort: only as root (needs CAP_SYS_ADMIN) and only if `unshare` exists; +# otherwise warn loudly and continue unisolated. MOCKBUILD_ALL_MOUNTNS guards against a re-exec loop. +unless ($ENV{MOCKBUILD_ALL_MOUNTNS}) { + if ($> != 0) { + warn "WARN: not root -- skipping mount-namespace isolation (host-cgroup propagation guard); " + . "run as root in CI so mock chroot teardown cannot unmount the host /sys/fs/cgroup\n"; + } elsif (system('sh', '-c', 'command -v unshare >/dev/null 2>&1') != 0) { + warn "WARN: 'unshare' not found -- skipping mount-namespace isolation; mock chroot teardown " + . "may unmount the host /sys/fs/cgroup on a shared-propagation host\n"; + } else { + $ENV{MOCKBUILD_ALL_MOUNTNS} = 1; + my @reexec = ('unshare', '--mount', '--propagation', 'slave', '--', $^X, $0, @ARGV); + exec { $reexec[0] } @reexec; + # exec only returns on failure -- fall through and run unisolated rather than abort the build. + warn "WARN: exec unshare failed ($!) -- continuing without mount-namespace isolation\n"; + delete $ENV{MOCKBUILD_ALL_MOUNTNS}; + } +} + my $script_dir = abs_path(dirname(__FILE__)); my $repo_root = abs_path($script_dir); my $xcat_src = "$repo_root/../xcat-core";