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.$$"]}]}