]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
prevent mount hash conflicts
authorAl Viro <viro@zeniv.linux.org.uk>
Sat, 21 Jun 2025 02:46:55 +0000 (22:46 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Sun, 29 Jun 2025 22:13:41 +0000 (18:13 -0400)
Currently it's still possible to run into a pathological situation when
two hashed mounts share both parent and mountpoint.  That does not work
well, for obvious reasons.

We are not far from getting rid of that; the only remaining gap is
attach_recursive_mnt() not being careful enough when sliding a tree
under existing mount (for propagated copies or in 'beneath' case for
the original one).

To deal with that cleanly we need to be able to find overmounts
(i.e. mounts on top of parent's root); we could do hash lookups or scan
the list of children but either would be costly.  Since one of the results
we get from that will be prevention of multiple parallel overmounts, let's
just bite the bullet and store a (non-counting) reference to overmount
in struct mount.

With that done, closing the hole in attach_recursive_mnt() becomes easy
- we just need to follow the chain of overmounts before we change the
mountpoint of the mount we are sliding things under.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/mount.h
fs/namespace.c

index ad7173037924a86c8302fee2219e02163e5c2c7a..b8beafdd6d240d4bc865df0b788df2e7c66e5427 100644 (file)
@@ -92,6 +92,7 @@ struct mount {
        int mnt_expiry_mark;            /* true if marked for expiry */
        struct hlist_head mnt_pins;
        struct hlist_head mnt_stuck_children;
+       struct mount *overmount;        /* mounted on ->mnt_root */
 } __randomize_layout;
 
 #define MNT_NS_INTERNAL ERR_PTR(-EINVAL) /* distinct from any mnt_namespace */
index 888816289154b03de6bf13069f7ebbb1c19fa2bd..9b732d74c2cc7ba1b5df41ebfffebee6fa9453e8 100644 (file)
@@ -1043,6 +1043,9 @@ static void __touch_mnt_namespace(struct mnt_namespace *ns)
 static struct mountpoint *unhash_mnt(struct mount *mnt)
 {
        struct mountpoint *mp;
+       struct mount *parent = mnt->mnt_parent;
+       if (unlikely(parent->overmount == mnt))
+               parent->overmount = NULL;
        mnt->mnt_parent = mnt;
        mnt->mnt_mountpoint = mnt->mnt.mnt_root;
        list_del_init(&mnt->mnt_child);
@@ -1078,6 +1081,8 @@ void mnt_set_mountpoint(struct mount *mnt,
 
 static void __attach_mnt(struct mount *mnt, struct mount *parent)
 {
+       if (unlikely(mnt->mnt_mountpoint == parent->mnt.mnt_root))
+               parent->overmount = mnt;
        hlist_add_head_rcu(&mnt->mnt_hash,
                           m_hash(&parent->mnt, mnt->mnt_mountpoint));
        list_add_tail(&mnt->mnt_child, &parent->mnt_mounts);
@@ -2660,7 +2665,9 @@ static int attach_recursive_mnt(struct mount *source_mnt,
        HLIST_HEAD(tree_list);
        struct mnt_namespace *ns = top_mnt->mnt_ns;
        struct mountpoint *smp;
+       struct mountpoint *secondary = NULL;
        struct mount *child, *dest_mnt, *p;
+       struct mount *top;
        struct hlist_node *n;
        int err = 0;
        bool moving = flags & MNT_TREE_MOVE, beneath = flags & MNT_TREE_BENEATH;
@@ -2669,9 +2676,15 @@ static int attach_recursive_mnt(struct mount *source_mnt,
         * Preallocate a mountpoint in case the new mounts need to be
         * mounted beneath mounts on the same mountpoint.
         */
-       smp = get_mountpoint(source_mnt->mnt.mnt_root);
+       for (top = source_mnt; unlikely(top->overmount); top = top->overmount) {
+               if (!secondary && is_mnt_ns_file(top->mnt.mnt_root))
+                       secondary = top->mnt_mp;
+       }
+       smp = get_mountpoint(top->mnt.mnt_root);
        if (IS_ERR(smp))
                return PTR_ERR(smp);
+       if (!secondary)
+               secondary = smp;
 
        /* Is there space to add these mounts to the mount namespace? */
        if (!moving) {
@@ -2704,7 +2717,7 @@ static int attach_recursive_mnt(struct mount *source_mnt,
                unhash_mnt(source_mnt);
                mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
                if (beneath)
-                       mnt_change_mountpoint(source_mnt, smp, top_mnt);
+                       mnt_change_mountpoint(top, smp, top_mnt);
                __attach_mnt(source_mnt, source_mnt->mnt_parent);
                mnt_notify_add(source_mnt);
                touch_mnt_namespace(source_mnt->mnt_ns);
@@ -2719,7 +2732,7 @@ static int attach_recursive_mnt(struct mount *source_mnt,
                }
                mnt_set_mountpoint(dest_mnt, dest_mp, source_mnt);
                if (beneath)
-                       mnt_change_mountpoint(source_mnt, smp, top_mnt);
+                       mnt_change_mountpoint(top, smp, top_mnt);
                commit_tree(source_mnt);
        }
 
@@ -2732,8 +2745,12 @@ static int attach_recursive_mnt(struct mount *source_mnt,
                child->mnt.mnt_flags &= ~MNT_LOCKED;
                q = __lookup_mnt(&child->mnt_parent->mnt,
                                 child->mnt_mountpoint);
-               if (q)
-                       mnt_change_mountpoint(child, smp, q);
+               if (q) {
+                       struct mount *r = child;
+                       while (unlikely(r->overmount))
+                               r = r->overmount;
+                       mnt_change_mountpoint(r, secondary, q);
+               }
                commit_tree(child);
        }
        put_mountpoint(smp);