Merge patch series "Revert "put_mnt_ns(): leave mounts connected""

Christian Brauner <brauner@kernel.org> says:

Keeping every mount of a dying namespace attached to its parent changes
who owns it. An attached but unmounted mount is owned by its parent: the
parent's final mntput() is what drops the child's reference and takes
its superblock down. Before that commit only the namespace root was in
that position and every other mount dropped its own reference in
namespace_unlock().

That ownership rule turns any reference from a child's superblock back
to one of its ancestors into a cycle. The obvious case is a loop device:

    unshare -m sh -c 'mount -o loop /var/tmp/img /var/tmp/mp'

mount(8) opens the image from inside the new namespace, so the loop
device's backing file pins that namespace's copy of the root mount.
When the namespace dies the loop mount stays attached to that copy and
is owned by it. The copy can't reach zero because the backing file
holds it. The backing file is only put once the ext4 superblock
releases the block device and autoclear runs, and that needs the loop
mount to go first. Nothing breaks the cycle. The superblock, the loop
device and, since the root copy keeps all of its children.

systemd-sysext sets up its loop devices in a private mount namespace and
relies on autoclear when that namespace goes away, which is how this was
found. Anything that holds a file on an ancestor from a mounted
filesystem has the same problem. That includes ecryptfs lower paths,
erofs file-backed mounts, fuse passthrough backing files.

That's a bigger fix and not an -rc change. Revert.

* patches from https://patch.msgid.link/20260917-work-put_mnt_ns-revert-v1-0-34d9b8679e68@kernel.org:
  Revert "put_mnt_ns(): leave mounts connected"
  Revert "selftests/filesystems: add mntns cleanup test"

Link: https://patch.msgid.link/20260917-work-put_mnt_ns-revert-v1-0-34d9b8679e68@kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
This commit is contained in:
Christian Brauner 2026-09-23 09:05:56 +02:00
commit fabd324212
No known key found for this signature in database
GPG Key ID: 91C61BC06578DCA2
5 changed files with 1 additions and 68 deletions

View File

@ -6301,7 +6301,7 @@ void put_mnt_ns(struct mnt_namespace *ns)
guard(namespace_excl)();
emptied_ns = ns;
guard(mount_writer)();
umount_tree(ns->root, UMOUNT_CONNECTED);
umount_tree(ns->root, 0);
}
struct vfsmount *kern_mount(struct file_system_type *type)

View File

@ -49,7 +49,6 @@ TARGETS += filesystems/move_mount
TARGETS += filesystems/empty_mntns
TARGETS += filesystems/fsmount_ns
TARGETS += filesystems/fscontext_ns
TARGETS += filesystems/mntns_cleanup
TARGETS += filesystems/xattr
TARGETS += firmware
TARGETS += fpu

View File

@ -1,2 +0,0 @@
# SPDX-License-Identifier: GPL-2.0-only
mntns_cleanup_test

View File

@ -1,6 +0,0 @@
# SPDX-License-Identifier: GPL-2.0
TEST_GEN_PROGS := mntns_cleanup_test
CFLAGS += -Wall -O2 -g $(KHDR_INCLUDES)
include ../../lib.mk

View File

@ -1,58 +0,0 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <unistd.h>
#include "../../kselftest_harness.h"
FIXTURE(mntns_cleanup) {
};
FIXTURE_SETUP(mntns_cleanup)
{
if (geteuid() != 0)
SKIP(return, "test requires CAP_SYS_ADMIN");
ASSERT_EQ(unshare(CLONE_NEWNS), 0);
ASSERT_EQ(mount("", "/", NULL, MS_REC | MS_PRIVATE, NULL), 0);
rmdir("/mnt_dir");
ASSERT_EQ(mkdir("/mnt_dir", 0755), 0);
ASSERT_EQ(mount("tmpfs", "/mnt_dir", "tmpfs", 0, NULL), 0);
ASSERT_EQ(mkdir("/mnt_dir/hidden", 0755), 0);
ASSERT_EQ(mkdir("/mnt_dir/hidden/secret", 0755), 0);
ASSERT_EQ(mount("tmpfs", "/mnt_dir/hidden", "tmpfs", 0, NULL), 0);
}
FIXTURE_TEARDOWN(mntns_cleanup)
{
}
/* Mounts must stay connected when a mount namespace is cleaned up. */
TEST_F(mntns_cleanup, keeps_mounts_connected)
{
int fd, sfd, err;
fd = open("/mnt_dir", O_PATH | O_DIRECTORY | O_CLOEXEC);
ASSERT_GE(fd, 0);
/* Destroy the namespace; the fd keeps /mnt_dir alive. */
ASSERT_EQ(unshare(CLONE_NEWNS), 0);
sfd = openat(fd, "hidden/secret", O_RDONLY);
err = errno;
if (sfd >= 0)
close(sfd);
close(fd);
ASSERT_LT(sfd, 0)
TH_LOG("mount namespace teardown revealed what the overmount covered");
ASSERT_EQ(err, ENOENT);
}
TEST_HARNESS_MAIN