From: Al Viro Date: Sat, 21 Jun 2025 02:46:55 +0000 (-0400) Subject: prevent mount hash conflicts X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ffdc52fbbd5835a936ad683c943d6d103a2d4514;p=thirdparty%2Fkernel%2Flinux.git prevent mount hash conflicts 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 --- diff --git a/fs/mount.h b/fs/mount.h index ad7173037924a..b8beafdd6d240 100644 --- a/fs/mount.h +++ b/fs/mount.h @@ -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 */ diff --git a/fs/namespace.c b/fs/namespace.c index 888816289154b..9b732d74c2cc7 100644 --- a/fs/namespace.c +++ b/fs/namespace.c @@ -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);