mount: handle mount propagation for detached mount trees
authorChristian Brauner <brauner@kernel.org>
Tue, 25 Feb 2025 10:15:46 +0000 (11:15 +0100)
committerChristian Brauner <brauner@kernel.org>
Tue, 4 Mar 2025 08:29:54 +0000 (09:29 +0100)
In commit ee2e3f50629f ("mount: fix mounting of detached mounts onto
targets that reside on shared mounts") I fixed a bug where propagating
the source mount tree of an anonymous mount namespace into a target
mount tree of a non-anonymous mount namespace could be used to trigger
an integer overflow in the non-anonymous mount namespace causing any new
mounts to fail.

The cause of this was that the propagation algorithm was unable to
recognize mounts from the source mount tree that were already propagated
into the target mount tree and then reappeared as propagation targets
when walking the destination propagation mount tree.

When fixing this I disabled mount propagation into anonymous mount
namespaces. Make it possible for anonymous mount namespace to receive
mount propagation events correctly. This is no also a correctness issue
now that we allow mounting detached mount trees onto detached mount
trees.

Mark the source anonymous mount namespace with MNTNS_PROPAGATING
indicating that all mounts belonging to this mount namespace are
currently in the process of being propagated and make the propagation
algorithm discard those if they appear as propagation targets.

Link: https://lore.kernel.org/r/20250225-work-mount-propagation-v1-1-e6e3724500eb@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/mount.h
fs/namespace.c
fs/pnode.c
fs/pnode.h

index e2501a724688363393cbd566b3b9d516ec6d26ce..96862eba22469cf3549693ca6d6bc49ae19cca79 100644 (file)
@@ -5,6 +5,12 @@
 #include <linux/ns_common.h>
 #include <linux/fs_pin.h>
 
+extern struct list_head notify_list;
+
+typedef __u32 __bitwise mntns_flags_t;
+
+#define MNTNS_PROPAGATING      ((__force mntns_flags_t)(1 << 0))
+
 struct mnt_namespace {
        struct ns_common        ns;
        struct mount *  root;
@@ -27,6 +33,7 @@ struct mnt_namespace {
        struct rb_node          mnt_ns_tree_node; /* node in the mnt_ns_tree */
        struct list_head        mnt_ns_list; /* entry in the sequential list of mounts namespace */
        refcount_t              passive; /* number references not pinning @mounts */
+       mntns_flags_t           mntns_flags;
 } __randomize_layout;
 
 struct mnt_pcp {
index a7da41e8167389c3e9e9fac5b2d4f025648faf10..59894e3f3e68ed0a9fcec44ff2633c5883a4df1e 100644 (file)
@@ -3536,13 +3536,6 @@ static int do_move_mount(struct path *old_path,
        if (!may_use_mount(p))
                goto out;
 
-       /*
-        * Don't allow moving an attached mount tree to an anonymous
-        * mount tree.
-        */
-       if (!is_anon_ns(ns) && is_anon_ns(p->mnt_ns))
-               goto out;
-
        /* The thing moved must be mounted... */
        if (!is_mounted(&old->mnt))
                goto out;
@@ -3551,15 +3544,31 @@ static int do_move_mount(struct path *old_path,
        if (!(attached ? check_mnt(old) : is_anon_ns(ns)))
                goto out;
 
-       /*
-        * Ending up with two files referring to the root of the same
-        * anonymous mount namespace would cause an error as this would
-        * mean trying to move the same mount twice into the mount tree
-        * which would be rejected later. But be explicit about it right
-        * here.
-        */
-       if (is_anon_ns(ns) && is_anon_ns(p->mnt_ns) && ns == p->mnt_ns)
+       if (is_anon_ns(ns)) {
+               /*
+                * Ending up with two files referring to the root of the
+                * same anonymous mount namespace would cause an error
+                * as this would mean trying to move the same mount
+                * twice into the mount tree which would be rejected
+                * later. But be explicit about it right here.
+                */
+               if ((is_anon_ns(p->mnt_ns) && ns == p->mnt_ns))
+                       goto out;
+
+               /*
+                * If this is an anonymous mount tree ensure that mount
+                * propagation can detect mounts that were just
+                * propagated to the target mount tree so we don't
+                * propagate onto them.
+                */
+               ns->mntns_flags |= MNTNS_PROPAGATING;
+       } else if (is_anon_ns(p->mnt_ns)) {
+               /*
+                * Don't allow moving an attached mount tree to an
+                * anonymous mount tree.
+                */
                goto out;
+       }
 
        if (old->mnt.mnt_flags & MNT_LOCKED)
                goto out;
@@ -3603,6 +3612,9 @@ static int do_move_mount(struct path *old_path,
        if (err)
                goto out;
 
+       if (is_anon_ns(ns))
+               ns->mntns_flags &= ~MNTNS_PROPAGATING;
+
        /* if the mount is moved, it should no longer be expire
         * automatically */
        list_del_init(&old->mnt_expire);
index ef048f008bdd5a4b6f2c3d5906034579072a257b..6dd26ca96cdd8025609970a2a32d40002109e430 100644 (file)
@@ -150,7 +150,7 @@ static struct mount *propagation_next(struct mount *m,
                                         struct mount *origin)
 {
        /* are there any slaves of this mount? */
-       if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
+       if (!IS_MNT_PROPAGATED(m) && !list_empty(&m->mnt_slave_list))
                return first_slave(m);
 
        while (1) {
@@ -174,7 +174,7 @@ static struct mount *skip_propagation_subtree(struct mount *m,
         * Advance m such that propagation_next will not return
         * the slaves of m.
         */
-       if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
+       if (!IS_MNT_PROPAGATED(m) && !list_empty(&m->mnt_slave_list))
                m = last_slave(m);
 
        return m;
@@ -185,7 +185,7 @@ static struct mount *next_group(struct mount *m, struct mount *origin)
        while (1) {
                while (1) {
                        struct mount *next;
-                       if (!IS_MNT_NEW(m) && !list_empty(&m->mnt_slave_list))
+                       if (!IS_MNT_PROPAGATED(m) && !list_empty(&m->mnt_slave_list))
                                return first_slave(m);
                        next = next_peer(m);
                        if (m->mnt_group_id == origin->mnt_group_id) {
@@ -226,7 +226,7 @@ static int propagate_one(struct mount *m, struct mountpoint *dest_mp)
        struct mount *child;
        int type;
        /* skip ones added by this propagate_mnt() */
-       if (IS_MNT_NEW(m))
+       if (IS_MNT_PROPAGATED(m))
                return 0;
        /* skip if mountpoint isn't covered by it */
        if (!is_subdir(dest_mp->m_dentry, m->mnt.mnt_root))
@@ -380,7 +380,7 @@ bool propagation_would_overmount(const struct mount *from,
        if (!IS_MNT_SHARED(from))
                return false;
 
-       if (IS_MNT_NEW(to))
+       if (IS_MNT_PROPAGATED(to))
                return false;
 
        if (to->mnt.mnt_root != mp->m_dentry)
index 0b02a63938911e89c724fe8e6b9a6e886f70ce9f..ddafe0d087ca0a6dab7bb36cb79cb3b2727c7dad 100644 (file)
@@ -12,7 +12,7 @@
 
 #define IS_MNT_SHARED(m) ((m)->mnt.mnt_flags & MNT_SHARED)
 #define IS_MNT_SLAVE(m) ((m)->mnt_master)
-#define IS_MNT_NEW(m)  (!(m)->mnt_ns || is_anon_ns((m)->mnt_ns))
+#define IS_MNT_PROPAGATED(m) (!(m)->mnt_ns || ((m)->mnt_ns->mntns_flags & MNTNS_PROPAGATING))
 #define CLEAR_MNT_SHARED(m) ((m)->mnt.mnt_flags &= ~MNT_SHARED)
 #define IS_MNT_UNBINDABLE(m) ((m)->mnt.mnt_flags & MNT_UNBINDABLE)
 #define IS_MNT_MARKED(m) ((m)->mnt.mnt_flags & MNT_MARKED)