From a19881afa025818128ca4e60658f7581c24d81bc Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Fri, 9 Oct 2026 10:56:08 -0700 Subject: [PATCH] libct: don't change host mounts propagation When the container does not have its own mount namespace, prepareRoot is run in the host mount namespace. Since commit 91ca3314 ("chroot when no mount namespaces is provided"), this results in host's / being recursively made a slave (which, in the host mount namespace, means private), and the parent mount of the container rootfs being made private (or slave). In the host mount namespace, do not change the propagation of the existing mounts. Instead, make the new rootfs mount a slave, so that container mounts do not propagate to the host and other mount namespaces (otherwise, if the rootfs parent mount is shared, every container mount appears twice in the host mount namespace, and is propagated to all its slaves). Add an integration test. (cherry picked from commit bd825f99bc47244cbc478968b4702ea8b10ded13) Signed-off-by: Kir Kolyshkin --- CHANGELOG.md | 3 +++ libcontainer/rootfs_linux.go | 18 +++++++++++++++++- tests/integration/host-mntns.bats | 19 +++++++++++++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f0c9bce..759879eef 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 this is a correctness rather than a security fix. (#5403, #5583) - Fixed a missing `O_CLOEXEC` when opening the cgroup v2 directory to set up device rules. (#5403, #5583) +- Running a container which does not have its own mount namespace no longer + changes the propagation of host's `/` and of the parent mount of the + container rootfs (they were made private or slave). (#5537, #5582) ## [1.4.3] - 2026-06-13 diff --git a/libcontainer/rootfs_linux.go b/libcontainer/rootfs_linux.go index 14a8674dd..d1e67c841 100644 --- a/libcontainer/rootfs_linux.go +++ b/libcontainer/rootfs_linux.go @@ -169,7 +169,11 @@ func setupAndMountToRootfs(pipe *syncSocket, config *configs.Config, mountConfig // finalizeRootfs after this function to finish setting up the rootfs. func prepareRootfs(pipe *syncSocket, iConfig *initConfig) (err error) { config := iConfig.Config - if err := prepareRoot(config); err != nil { + prepare := prepareRoot + if !config.Namespaces.Contains(configs.NEWNS) { + prepare = prepareRootInHostMountNS + } + if err := prepare(config); err != nil { return fmt.Errorf("error preparing rootfs: %w", err) } @@ -1121,6 +1125,18 @@ func prepareRoot(config *configs.Config) error { return mount(config.Rootfs, config.Rootfs, "bind", unix.MS_BIND|unix.MS_REC, "") } +// prepareRootInHostMountNS is like prepareRoot, but for a container that +// runs in the host mount namespace. The propagation of existing mounts +// must not be changed, as this would affect the host. +func prepareRootInHostMountNS(config *configs.Config) error { + if err := mount(config.Rootfs, config.Rootfs, "bind", unix.MS_BIND|unix.MS_REC, ""); err != nil { + return err + } + // Make the new rootfs mount a slave, so that container mounts + // do not propagate to the host (and other mount namespaces). + return mount("", config.Rootfs, "", unix.MS_SLAVE|unix.MS_REC, "") +} + func setReadonly() error { flags := uintptr(unix.MS_BIND | unix.MS_REMOUNT | unix.MS_RDONLY) diff --git a/tests/integration/host-mntns.bats b/tests/integration/host-mntns.bats index 7907a5b9e..d5a4f402f 100644 --- a/tests/integration/host-mntns.bats +++ b/tests/integration/host-mntns.bats @@ -17,6 +17,25 @@ function teardown() { teardown_bundle } +# This test goes first, since without the fix, any test which runs +# a container in the host mount namespace changes the host mounts +# propagation, so the bug would go unnoticed. +@test "runc run [host mount ns] must not change host mounts propagation" { + update_config ' .process.args = ["true"] + | .linux.namespaces -= [{"type": "mount"}] + | .linux.maskedPaths = [] + | .linux.readonlyPaths = [] + | .root.readonly = false' + + # Check / and the mount the container bundle resides on. + before=$(findmnt -n -o TARGET,PROPAGATION / && findmnt -n -o TARGET,PROPAGATION -T "$ROOT/bundle") + run -0 runc run test_host_mntns + after=$(findmnt -n -o TARGET,PROPAGATION / && findmnt -n -o TARGET,PROPAGATION -T "$ROOT/bundle") + echo "before: $before" + echo "after: $after" + [ "$before" = "$after" ] +} + @test "runc run [host mount ns + hooks]" { update_config ' .process.args = ["/bin/echo", "Hello World"] | .hooks |= . + {"createRuntime": [{"path": "/bin/sh", "args": ["/bin/sh", "-c", "touch createRuntimeHook.$$"]}]}