]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
fix IS_MNT_PROPAGATING uses
authorAl Viro <viro@zeniv.linux.org.uk>
Thu, 8 May 2025 19:35:51 +0000 (15:35 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Fri, 9 May 2025 22:06:27 +0000 (18:06 -0400)
propagate_mnt() does not attach anything to mounts created during
propagate_mnt() itself.  What's more, anything on ->mnt_slave_list
of such new mount must also be new, so we don't need to even look
there.

When move_mount() had been introduced, we've got an additional
class of mounts to skip - if we are moving from anon namespace,
we do not want to propagate to mounts we are moving (i.e. all
mounts in that anon namespace).

Unfortunately, the part about "everything on their ->mnt_slave_list
will also be ignorable" is not true - if we have propagation graph
A -> B -> C
and do OPEN_TREE_CLONE open_tree() of B, we get
A -> [B <-> B'] -> C
as propagation graph, where B' is a clone of B in our detached tree.
Making B private will result in
A -> B' -> C
C still gets propagation from A, as it would after making B private
if we hadn't done that open_tree(), but now the propagation goes
through B'.  Trying to move_mount() our detached tree on subdirectory
in A should have
* moved B' on that subdirectory in A
* skipped the corresponding subdirectory in B' itself
* copied B' on the corresponding subdirectory in C.
As it is, the logics in propagation_next() and friends ends up
skipping propagation into C, since it doesn't consider anything
downstream of B'.

IOW, walking the propagation graph should only skip the ->mnt_slave_list
of new mounts; the only places where the check for "in that one
anon namespace" are applicable are propagate_one() (where we should
treat that as the same kind of thing as "mountpoint we are looking
at is not visible in the mount we are looking at") and
propagation_would_overmount().  The latter is better dealt with
in the caller (can_move_mount_beneath()); on the first call of
propagation_would_overmount() the test is always false, on the
second it is always true in "move from anon namespace" case and
always false in "move within our namespace" one, so it's easier
to just use check_mnt() before bothering with the second call and
be done with that.

Fixes: 064fe6e233e8 ("mount: handle mount propagation for detached mount trees")
Reviewed-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/namespace.c
fs/pnode.c
fs/pnode.h

index 04a9bb9f31fad1e6b2c84a6cd5a42649bb2c379a..1b466c54a357d1216bc44bc8705ff368342b55b5 100644 (file)
@@ -3557,7 +3557,8 @@ static int can_move_mount_beneath(const struct path *from,
         * @mnt_from itself. This defeats the whole purpose of mounting
         * @mnt_from beneath @mnt_to.
         */
-       if (propagation_would_overmount(parent_mnt_to, mnt_from, mp))
+       if (check_mnt(mnt_from) &&
+           propagation_would_overmount(parent_mnt_to, mnt_from, mp))
                return -EINVAL;
 
        return 0;
index 7a062a5de10e36d0b1d5b3d52855a4fdbae4b0f9..fb77427df39e2eebdc44f0bf4137f1821bdf51a6 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_PROPAGATED(m) && !list_empty(&m->mnt_slave_list))
+       if (!IS_MNT_NEW(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_PROPAGATED(m) && !list_empty(&m->mnt_slave_list))
+       if (!IS_MNT_NEW(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_PROPAGATED(m) && !list_empty(&m->mnt_slave_list))
+                       if (!IS_MNT_NEW(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,11 +226,15 @@ 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_PROPAGATED(m))
+       if (IS_MNT_NEW(m))
                return 0;
-       /* skip if mountpoint isn't covered by it */
+       /* skip if mountpoint isn't visible in m */
        if (!is_subdir(dest_mp->m_dentry, m->mnt.mnt_root))
                return 0;
+       /* skip if m is in the anon_ns we are emptying */
+       if (m->mnt_ns->mntns_flags & MNTNS_PROPAGATING)
+               return 0;
+
        if (peers(m, last_dest)) {
                type = CL_MAKE_SHARED;
        } else {
@@ -380,9 +384,6 @@ bool propagation_would_overmount(const struct mount *from,
        if (!IS_MNT_SHARED(from))
                return false;
 
-       if (IS_MNT_PROPAGATED(to))
-               return false;
-
        if (to->mnt.mnt_root != mp->m_dentry)
                return false;
 
index ddafe0d087ca0a6dab7bb36cb79cb3b2727c7dad..34b6247af01d925b9d5523a9fa58e70ddc290856 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_PROPAGATED(m) (!(m)->mnt_ns || ((m)->mnt_ns->mntns_flags & MNTNS_PROPAGATING))
+#define IS_MNT_NEW(m) (!(m)->mnt_ns)
 #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)