From 35a174eda93f68a32a60d2f0dc68398f06acc231 Mon Sep 17 00:00:00 2001 From: sean Date: Sun, 3 May 2026 14:50:48 +0800 Subject: [PATCH 1/2] fix(libcontainer): avoid fresh cgroup2 mounts without private cgroupns When cgroup namespaces is host, mounting a new cgroup2 fs instance for /sys/fs/cgroup can affect the host-visible cgroupfs mount state, including options such as nsdelegate. Avoid that by preferring a bind mount of the existing cgroup v2 hierarchy when cgroupns is host. Keep the existing cgroup2 mount-first logic for private cgroup namespaces, including the EPERM/EBUSY fallback to a bind mount and the rootless ENOENT masking behavior. Signed-off-by: sean --- libcontainer/rootfs_linux.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go index e4a4df20c78..b23b895733c 100644 --- a/libcontainer/rootfs_linux.go +++ b/libcontainer/rootfs_linux.go @@ -397,11 +397,13 @@ func mountCgroupV1(m mountEntry, c *mountConfig) error { } func mountCgroupV2(m mountEntry, c *mountConfig) error { - err := utils.WithProcfdFile(m.dstFile, func(dstFd string) error { - return mountViaFds(m.Source, nil, m.Destination, dstFd, "cgroup2", uintptr(m.Flags), m.Data) - }) - if err == nil || (!errors.Is(err, unix.EPERM) && !errors.Is(err, unix.EBUSY)) { - return err + if c.cgroupns { + err := utils.WithProcfdFile(m.dstFile, func(dstFd string) error { + return mountViaFds(m.Source, nil, m.Destination, dstFd, "cgroup2", uintptr(m.Flags), m.Data) + }) + if err == nil || (!errors.Is(err, unix.EPERM) && !errors.Is(err, unix.EBUSY)) { + return err + } } // When we are in UserNS but CgroupNS is not unshared, we cannot mount @@ -417,9 +419,14 @@ func mountCgroupV2(m mountEntry, c *mountConfig) error { // Emulate cgroupns by bind-mounting the container cgroup path // rather than the whole /sys/fs/cgroup. bindM.Source = c.cgroup2Path + } else { + // Match the old fresh-mount behaviour more closely by detaching the + // bind mount from any host-side propagation relationship. This avoids + // exposing an external master that CRIU cannot restore. + bindM.PropagationFlags = append(bindM.PropagationFlags, unix.MS_PRIVATE) } // mountToRootfs() handles remounting for MS_RDONLY. - err = mountToRootfs(c, mountEntry{Mount: bindM}) + err := mountToRootfs(c, mountEntry{Mount: bindM}) if c.rootlessCgroups && errors.Is(err, unix.ENOENT) { // ENOENT (for `src = c.cgroup2Path`) happens when rootless runc is being executed // outside the userns+mountns. From e8edba3c7837009524425bfa931d091e25883230 Mon Sep 17 00:00:00 2001 From: sean Date: Sun, 3 May 2026 14:55:19 +0800 Subject: [PATCH 2/2] fix(libcontainer): bats test for cgroupfs mount in host cgroupns in host cgroupns, mount cgroupfs should not mutate the global superblock mount options, the test is to check when create container with host cgroupns, it should not mutate the global superblock options of host cgroupfs Signed-off-by: sean --- libcontainer/rootfs_linux.go | 9 +++++---- tests/integration/mounts.bats | 10 ++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go index b23b895733c..96a5da2a472 100644 --- a/libcontainer/rootfs_linux.go +++ b/libcontainer/rootfs_linux.go @@ -419,10 +419,11 @@ func mountCgroupV2(m mountEntry, c *mountConfig) error { // Emulate cgroupns by bind-mounting the container cgroup path // rather than the whole /sys/fs/cgroup. bindM.Source = c.cgroup2Path - } else { - // Match the old fresh-mount behaviour more closely by detaching the - // bind mount from any host-side propagation relationship. This avoids - // exposing an external master that CRIU cannot restore. + } + if !c.cgroupns { + // With host cgroup namespace, bind-mount the existing cgroup2 + // hierarchy instead of mounting a fresh cgroup2 instance, and detach + // it from host-side propagation. bindM.PropagationFlags = append(bindM.PropagationFlags, unix.MS_PRIVATE) } // mountToRootfs() handles remounting for MS_RDONLY. diff --git a/tests/integration/mounts.bats b/tests/integration/mounts.bats index 6bab15727a3..ba514d40f16 100644 --- a/tests/integration/mounts.bats +++ b/tests/integration/mounts.bats @@ -163,6 +163,10 @@ test_mount_target() { mv "$old_config" "./config.json" } +function get_cgroup_mount_flags() { + awk '$5 == "/sys/fs/cgroup" {print $10; exit}' /proc/self/mountinfo +} + # https://github.com/opencontainers/runc/issues/3991 @test "runc run [tmpcopyup]" { mkdir -p rootfs/dir1/dir2 @@ -354,7 +358,13 @@ test_mount_target() { @test "runc run [ro /sys/fs/cgroup mounts]" { # Without cgroup namespace. update_config '.linux.namespaces -= [{"type": "cgroup"}]' + # Save the host cgroup mount flags. + CG_MNT=$(get_cgroup_mount_flags) test_ro_cgroup_mount + # Verify that the cgroup mount flags after container creation are the same as before container creation. + if [ "$CG_MNT" != "$(get_cgroup_mount_flags)" ]; then + fail "Host cgroup mount flags changed" + fi } @test "runc run [ro /sys/fs/cgroup mounts + cgroupns]" {