do_move_mount(): split the checks in subtree-of-our-ns and entire-anon cases
authorAl Viro <viro@zeniv.linux.org.uk>
Fri, 6 Jun 2025 22:31:03 +0000 (18:31 -0400)
committerAl Viro <viro@zeniv.linux.org.uk>
Sat, 7 Jun 2025 04:41:20 +0000 (00:41 -0400)
... and fix the breakage in anon-to-anon case.  There are two cases
acceptable for do_move_mount() and mixing checks for those is making
things hard to follow.

One case is move of a subtree in caller's namespace.
        * source and destination must be in caller's namespace
* source must be detachable from parent
Another is moving the entire anon namespace elsewhere
* source must be the root of anon namespace
* target must either in caller's namespace or in a suitable
  anon namespace (see may_use_mount() for details).
* target must not be in the same namespace as source.

It's really easier to follow if tests are *not* mixed together...

Reviewed-by: Christian Brauner <brauner@kernel.org>
Fixes: 3b5260d12b1f ("Don't propagate mounts into detached trees")
Reported-by: Allison Karlitskaya <lis@redhat.com>
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
fs/namespace.c

index 854099aafed517bb61ac3016a8e8f6cd3f5bb562..2e939b783618d92d88a3db4d2529c6653c686c3c 100644 (file)
@@ -3656,37 +3656,41 @@ static int do_move_mount(struct path *old_path,
        ns = old->mnt_ns;
 
        err = -EINVAL;
-       if (!may_use_mount(p))
-               goto out;
-
        /* The thing moved must be mounted... */
        if (!is_mounted(&old->mnt))
                goto out;
 
-       /* ... and either ours or the root of anon namespace */
-       if (!(attached ? check_mnt(old) : is_anon_ns(ns)))
-               goto out;
-
-       if (is_anon_ns(ns) && ns == p->mnt_ns) {
+       if (check_mnt(old)) {
+               /* if the source is in our namespace... */
+               /* ... it should be detachable from parent */
+               if (!mnt_has_parent(old) || IS_MNT_LOCKED(old))
+                       goto out;
+               /* ... and the target should be in our namespace */
+               if (!check_mnt(p))
+                       goto out;
+       } else {
                /*
-                * 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.
+                * otherwise the source must be the root of some anon namespace.
+                * AV: check for mount being root of an anon namespace is worth
+                * an inlined predicate...
                 */
-               goto out;
-       } else if (is_anon_ns(p->mnt_ns)) {
+               if (!is_anon_ns(ns) || mnt_has_parent(old))
+                       goto out;
                /*
-                * Don't allow moving an attached mount tree to an
-                * anonymous mount tree.
+                * Bail out early if the target is within the same namespace -
+                * subsequent checks would've rejected that, but they lose
+                * some corner cases if we check it early.
                 */
-               goto out;
+               if (ns == p->mnt_ns)
+                       goto out;
+               /*
+                * Target should be either in our namespace or in an acceptable
+                * anon namespace, sensu check_anonymous_mnt().
+                */
+               if (!may_use_mount(p))
+                       goto out;
        }
 
-       if (old->mnt.mnt_flags & MNT_LOCKED)
-               goto out;
-
        if (!path_mounted(old_path))
                goto out;