[PATCH] mount: unmount copied tree
From: Christian Brauner
Date: Sat Feb 14 2026 - 10:22:13 EST
Fix an oversight when creating a new mount namespace. If someone had the
bright idea to make the real rootfs a shared or dependent mount and it
is later copied the copy will become a peer of the old real rootfs mount
or a dependent mount of it. The namespace semaphore is dropped and we
use mount lock exact to lock the new real root mount. If that fails or
the subsequent do_loopback() fails we rely on the copy of the real root
mount to be cleaned up by path_put(). The problem is that this doesn't
deal with mount propagation and will leave the mounts linked in the
propagation lists. Fix this by actually unmounting the copied tree.
Reported-by: syzbot+a89f9434fb5a001ccd58@xxxxxxxxxxxxxxxxxxxxxxxxx
Link: https:/lore.kernel.org/699047f6.050a0220.2757fb.0024.GAE@xxxxxxxxxx
Signed-off-by: Christian Brauner <brauner@xxxxxxxxxx>
---
fs/namespace.c | 37 ++++++++++++++++++++++---------------
1 file changed, 22 insertions(+), 15 deletions(-)
diff --git a/fs/namespace.c b/fs/namespace.c
index a67cbe42746d..89cac95e6c9b 100644
--- a/fs/namespace.c
+++ b/fs/namespace.c
@@ -3073,16 +3073,13 @@ static struct file *open_detached_copy(struct path *path, unsigned int flags)
return file;
}
-DEFINE_FREE(put_empty_mnt_ns, struct mnt_namespace *,
- if (!IS_ERR_OR_NULL(_T)) free_mnt_ns(_T))
-
static struct mnt_namespace *create_new_namespace(struct path *path, unsigned int flags)
{
- struct mnt_namespace *new_ns __free(put_empty_mnt_ns) = NULL;
- struct path to_path __free(path_put) = {};
struct mnt_namespace *ns = current->nsproxy->mnt_ns;
struct user_namespace *user_ns = current_user_ns();
+ struct mnt_namespace *new_ns;
struct mount *new_ns_root;
+ struct path to_path;
struct mount *mnt;
unsigned int copy_flags = 0;
bool locked = false;
@@ -3096,8 +3093,10 @@ static struct mnt_namespace *create_new_namespace(struct path *path, unsigned in
scoped_guard(namespace_excl) {
new_ns_root = clone_mnt(ns->root, ns->root->mnt.mnt_root, copy_flags);
- if (IS_ERR(new_ns_root))
+ if (IS_ERR(new_ns_root)) {
+ emptied_ns = new_ns;
return ERR_CAST(new_ns_root);
+ }
/*
* If the real rootfs had a locked mount on top of it somewhere
@@ -3122,12 +3121,17 @@ static struct mnt_namespace *create_new_namespace(struct path *path, unsigned in
/* Borrow the reference from clone_mnt(). */
to_path.mnt = &new_ns_root->mnt;
- to_path.dentry = dget(new_ns_root->mnt.mnt_root);
+ to_path.dentry = new_ns_root->mnt.mnt_root;
/* Now lock for actual mounting. */
LOCK_MOUNT_EXACT(mp, &to_path);
- if (unlikely(IS_ERR(mp.parent)))
+ if (unlikely(IS_ERR(mp.parent))) {
+ guard(namespace_excl)();
+ emptied_ns = new_ns;
+ guard(mount_writer)();
+ umount_tree(new_ns_root, 0);
return ERR_CAST(mp.parent);
+ }
/*
* We don't emulate unshare()ing a mount namespace. We stick to the
@@ -3135,10 +3139,13 @@ static struct mnt_namespace *create_new_namespace(struct path *path, unsigned in
* saner and simpler semantics.
*/
mnt = __do_loopback(path, flags, copy_flags);
- if (IS_ERR(mnt))
- return ERR_CAST(mnt);
-
scoped_guard(mount_writer) {
+ if (IS_ERR(mnt)) {
+ emptied_ns = new_ns;
+ umount_tree(new_ns_root, 0);
+ return ERR_CAST(mnt);
+ }
+
if (locked)
mnt->mnt.mnt_flags |= MNT_LOCKED;
/*
@@ -3151,14 +3158,14 @@ static struct mnt_namespace *create_new_namespace(struct path *path, unsigned in
}
/* Add all mounts to the new namespace. */
- for (struct mount *p = new_ns_root; p; p = next_mnt(p, new_ns_root)) {
- mnt_add_to_ns(new_ns, p);
+ for (mnt = new_ns_root; mnt; mnt = next_mnt(mnt, new_ns_root)) {
+ mnt_add_to_ns(new_ns, mnt);
new_ns->nr_mounts++;
}
- new_ns->root = real_mount(no_free_ptr(to_path.mnt));
+ new_ns->root = real_mount(to_path.mnt);
ns_tree_add_raw(new_ns);
- return no_free_ptr(new_ns);
+ return new_ns;
}
static struct file *open_new_namespace(struct path *path, unsigned int flags)
--
2.47.3
--mmw32roaduaocuzj--