fs: support getname_maybe_null() in move_mount()
authorChristian Brauner <brauner@kernel.org>
Fri, 21 Feb 2025 13:13:07 +0000 (14:13 +0100)
committerChristian Brauner <brauner@kernel.org>
Tue, 4 Mar 2025 08:29:53 +0000 (09:29 +0100)
Allow move_mount() to work with NULL path arguments.

Link: https://lore.kernel.org/r/20250221-brauner-open_tree-v1-8-dbcfcb98c676@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
fs/namespace.c
include/linux/fs.h

index 36f5102..2658768 100644 (file)
@@ -2465,6 +2465,7 @@ int count_mounts(struct mnt_namespace *ns, struct mount *mnt)
 enum mnt_tree_flags_t {
        MNT_TREE_MOVE = BIT(0),
        MNT_TREE_BENEATH = BIT(1),
+       MNT_TREE_PROPAGATION = BIT(2),
 };
 
 /**
@@ -3436,8 +3437,8 @@ static int can_move_mount_beneath(const struct path *from,
        return 0;
 }
 
-static int do_move_mount(struct path *old_path, struct path *new_path,
-                        bool beneath)
+static int do_move_mount(struct path *old_path,
+                        struct path *new_path, enum mnt_tree_flags_t flags)
 {
        struct mnt_namespace *ns;
        struct mount *p;
@@ -3445,8 +3446,7 @@ static int do_move_mount(struct path *old_path, struct path *new_path,
        struct mount *parent;
        struct mountpoint *mp, *old_mp;
        int err;
-       bool attached;
-       enum mnt_tree_flags_t flags = 0;
+       bool attached, beneath = flags & MNT_TREE_BENEATH;
 
        mp = do_lock_mount(new_path, beneath);
        if (IS_ERR(mp))
@@ -3547,7 +3547,7 @@ static int do_move_mount_old(struct path *path, const char *old_name)
        if (err)
                return err;
 
-       err = do_move_mount(&old_path, path, false);
+       err = do_move_mount(&old_path, path, 0);
        path_put(&old_path);
        return err;
 }
@@ -4388,6 +4388,21 @@ err_unlock:
        return ret;
 }
 
+static inline int vfs_move_mount(struct path *from_path, struct path *to_path,
+                                enum mnt_tree_flags_t mflags)
+{
+       int ret;
+
+       ret = security_move_mount(from_path, to_path);
+       if (ret)
+               return ret;
+
+       if (mflags & MNT_TREE_PROPAGATION)
+               return do_set_group(from_path, to_path);
+
+       return do_move_mount(from_path, to_path, mflags);
+}
+
 /*
  * Move a mount from one place to another.  In combination with
  * fsopen()/fsmount() this is used to install a new mount and in combination
@@ -4401,8 +4416,12 @@ SYSCALL_DEFINE5(move_mount,
                int, to_dfd, const char __user *, to_pathname,
                unsigned int, flags)
 {
-       struct path from_path, to_path;
-       unsigned int lflags;
+       struct path to_path __free(path_put) = {};
+       struct path from_path __free(path_put) = {};
+       struct filename *to_name __free(putname) = NULL;
+       struct filename *from_name __free(putname) = NULL;
+       unsigned int lflags, uflags;
+       enum mnt_tree_flags_t mflags = 0;
        int ret = 0;
 
        if (!may_mount())
@@ -4415,43 +4434,51 @@ SYSCALL_DEFINE5(move_mount,
            (MOVE_MOUNT_BENEATH | MOVE_MOUNT_SET_GROUP))
                return -EINVAL;
 
-       /* If someone gives a pathname, they aren't permitted to move
-        * from an fd that requires unmount as we can't get at the flag
-        * to clear it afterwards.
-        */
+       if (flags & MOVE_MOUNT_SET_GROUP)       mflags |= MNT_TREE_PROPAGATION;
+       if (flags & MOVE_MOUNT_BENEATH)         mflags |= MNT_TREE_BENEATH;
+
        lflags = 0;
        if (flags & MOVE_MOUNT_F_SYMLINKS)      lflags |= LOOKUP_FOLLOW;
        if (flags & MOVE_MOUNT_F_AUTOMOUNTS)    lflags |= LOOKUP_AUTOMOUNT;
-       if (flags & MOVE_MOUNT_F_EMPTY_PATH)    lflags |= LOOKUP_EMPTY;
-
-       ret = user_path_at(from_dfd, from_pathname, lflags, &from_path);
-       if (ret < 0)
-               return ret;
+       if (flags & MOVE_MOUNT_F_EMPTY_PATH)    uflags = AT_EMPTY_PATH;
+       from_name = getname_maybe_null(from_pathname, uflags);
+       if (IS_ERR(from_name))
+               return PTR_ERR(from_name);
 
        lflags = 0;
        if (flags & MOVE_MOUNT_T_SYMLINKS)      lflags |= LOOKUP_FOLLOW;
        if (flags & MOVE_MOUNT_T_AUTOMOUNTS)    lflags |= LOOKUP_AUTOMOUNT;
-       if (flags & MOVE_MOUNT_T_EMPTY_PATH)    lflags |= LOOKUP_EMPTY;
+       if (flags & MOVE_MOUNT_T_EMPTY_PATH)    uflags = AT_EMPTY_PATH;
+       to_name = getname_maybe_null(to_pathname, uflags);
+       if (IS_ERR(to_name))
+               return PTR_ERR(to_name);
+
+       if (!to_name && to_dfd >= 0) {
+               CLASS(fd_raw, f_to)(to_dfd);
+               if (fd_empty(f_to))
+                       return -EBADF;
+
+               to_path = fd_file(f_to)->f_path;
+               path_get(&to_path);
+       } else {
+               ret = filename_lookup(to_dfd, to_name, lflags, &to_path, NULL);
+               if (ret)
+                       return ret;
+       }
 
-       ret = user_path_at(to_dfd, to_pathname, lflags, &to_path);
-       if (ret < 0)
-               goto out_from;
+       if (!from_name && from_dfd >= 0) {
+               CLASS(fd_raw, f_from)(from_dfd);
+               if (fd_empty(f_from))
+                       return -EBADF;
 
-       ret = security_move_mount(&from_path, &to_path);
-       if (ret < 0)
-               goto out_to;
+               return vfs_move_mount(&fd_file(f_from)->f_path, &to_path, mflags);
+       }
 
-       if (flags & MOVE_MOUNT_SET_GROUP)
-               ret = do_set_group(&from_path, &to_path);
-       else
-               ret = do_move_mount(&from_path, &to_path,
-                                   (flags & MOVE_MOUNT_BENEATH));
+       ret = filename_lookup(from_dfd, from_name, lflags, &from_path, NULL);
+       if (ret)
+               return ret;
 
-out_to:
-       path_put(&to_path);
-out_from:
-       path_put(&from_path);
-       return ret;
+       return vfs_move_mount(&from_path, &to_path, mflags);
 }
 
 /*
index e71d58c..7e9df86 100644 (file)
@@ -2855,6 +2855,7 @@ static inline struct filename *getname_maybe_null(const char __user *name, int f
        return __getname_maybe_null(name);
 }
 extern void putname(struct filename *name);
+DEFINE_FREE(putname, struct filename *, if (!IS_ERR_OR_NULL(_T)) putname(_T))
 
 extern int finish_open(struct file *file, struct dentry *dentry,
                        int (*open)(struct inode *, struct file *));