]> www.infradead.org Git - users/willy/linux.git/commitdiff
vfs: syscall: Add move_mount(2) to move mounts around
authorDavid Howells <dhowells@redhat.com>
Tue, 11 Sep 2018 21:20:53 +0000 (22:20 +0100)
committerDavid Howells <dhowells@redhat.com>
Tue, 11 Sep 2018 21:20:53 +0000 (22:20 +0100)
Add a move_mount() system call that will move a mount from one place to
another and, in the next commit, allow to attach an unattached mount tree.

The new system call looks like the following:

int move_mount(int from_dfd, const char *from_path,
       int to_dfd, const char *to_path,
       unsigned int flags);

Signed-off-by: David Howells <dhowells@redhat.com>
cc: linux-api@vger.kernel.org

arch/x86/entry/syscalls/syscall_32.tbl
arch/x86/entry/syscalls/syscall_64.tbl
fs/namespace.c
include/linux/lsm_hooks.h
include/linux/security.h
include/linux/syscalls.h
include/uapi/linux/mount.h
security/security.c

index ea1b413afd473cf9ad7cc8b3033f5a0c3abc0169..76d092b7d1b09bb359a783ec9f86ed32dd801c07 100644 (file)
 385    i386    io_pgetevents           sys_io_pgetevents               __ia32_compat_sys_io_pgetevents
 386    i386    rseq                    sys_rseq                        __ia32_sys_rseq
 387    i386    open_tree               sys_open_tree                   __ia32_sys_open_tree
+388    i386    move_mount              sys_move_mount                  __ia32_sys_move_mount
index 0545bed581dc82a382c9fe4d6124843c6ba303d8..37ba4e65eee6e4f3be5f37df7d73cf5c73458e9e 100644 (file)
 333    common  io_pgetevents           __x64_sys_io_pgetevents
 334    common  rseq                    __x64_sys_rseq
 335    common  open_tree               __x64_sys_open_tree
+336    common  move_mount              __x64_sys_move_mount
 
 #
 # x32-specific system call numbers start at 512 to avoid cache impact
index ded1a970ec4008bdc8f7896715531bd8aeb0a60c..9fb089d37d05f429456d6eeaa42e6f42b69b1a41 100644 (file)
@@ -1972,7 +1972,7 @@ static int attach_recursive_mnt(struct mount *source_mnt,
                return PTR_ERR(smp);
 
        /* Is there space to add these mounts to the mount namespace? */
-       if (!parent_path) {
+       if (!source_mnt->mnt_ns) {
                err = count_mounts(ns, source_mnt);
                if (err)
                        goto out;
@@ -2386,43 +2386,37 @@ static inline int tree_contains_unbindable(struct mount *mnt)
        return 0;
 }
 
-static int do_move_mount(struct path *path, const char *old_name)
+static int do_move_mount(struct path *old_path, struct path *new_path)
 {
-       struct path old_path, parent_path;
+       struct path parent_path = {.mnt = NULL, .dentry = NULL};
        struct mount *p;
        struct mount *old;
        struct mountpoint *mp;
        int err;
-       if (!old_name || !*old_name)
-               return -EINVAL;
-       err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
-       if (err)
-               return err;
 
-       mp = lock_mount(path);
+       mp = lock_mount(new_path);
        err = PTR_ERR(mp);
        if (IS_ERR(mp))
                goto out;
 
-       old = real_mount(old_path.mnt);
-       p = real_mount(path->mnt);
+       old = real_mount(old_path->mnt);
+       p = real_mount(new_path->mnt);
 
        err = -EINVAL;
        if (!check_mnt(p) || !check_mnt(old))
                goto out1;
 
-       if (old->mnt.mnt_flags & MNT_LOCKED)
+       if (!mnt_has_parent(old))
                goto out1;
 
-       err = -EINVAL;
-       if (old_path.dentry != old_path.mnt->mnt_root)
+       if (old->mnt.mnt_flags & (MNT_LOCKED | MNT_UMOUNT))
                goto out1;
 
-       if (!mnt_has_parent(old))
+       if (old_path->dentry != old_path->mnt->mnt_root)
                goto out1;
 
-       if (d_is_dir(path->dentry) !=
-             d_is_dir(old_path.dentry))
+       if (d_is_dir(new_path->dentry) !=
+           d_is_dir(old_path->dentry))
                goto out1;
        /*
         * Don't move a mount residing in a shared parent.
@@ -2440,7 +2434,8 @@ static int do_move_mount(struct path *path, const char *old_name)
                if (p == old)
                        goto out1;
 
-       err = attach_recursive_mnt(old, real_mount(path->mnt), mp, &parent_path);
+       err = attach_recursive_mnt(old, real_mount(new_path->mnt), mp,
+                                  &parent_path);
        if (err)
                goto out1;
 
@@ -2452,6 +2447,22 @@ out1:
 out:
        if (!err)
                path_put(&parent_path);
+       return err;
+}
+
+static int do_move_mount_old(struct path *path, const char *old_name)
+{
+       struct path old_path;
+       int err;
+
+       if (!old_name || !*old_name)
+               return -EINVAL;
+
+       err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
+       if (err)
+               return err;
+
+       err = do_move_mount(&old_path, path);
        path_put(&old_path);
        return err;
 }
@@ -2873,7 +2884,7 @@ long do_mount(const char *dev_name, const char __user *dir_name,
        else if (flags & (MS_SHARED | MS_PRIVATE | MS_SLAVE | MS_UNBINDABLE))
                retval = do_change_type(&path, flags);
        else if (flags & MS_MOVE)
-               retval = do_move_mount(&path, dev_name);
+               retval = do_move_mount_old(&path, dev_name);
        else
                retval = do_new_mount(&path, type_page, sb_flags, mnt_flags,
                                      dev_name, data_page, data_size);
@@ -3108,6 +3119,61 @@ SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
        return ksys_mount(dev_name, dir_name, type, flags, data);
 }
 
+/*
+ * Move a mount from one place to another.
+ *
+ * Note the flags value is a combination of MOVE_MOUNT_* flags.
+ */
+SYSCALL_DEFINE5(move_mount,
+               int, from_dfd, const char *, from_pathname,
+               int, to_dfd, const char *, to_pathname,
+               unsigned int, flags)
+{
+       struct path from_path, to_path;
+       unsigned int lflags;
+       int ret = 0;
+
+       if (!may_mount())
+               return -EPERM;
+
+       if (flags & ~MOVE_MOUNT__MASK)
+               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.
+        */
+       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;
+
+       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;
+
+       ret = user_path_at(to_dfd, to_pathname, lflags, &to_path);
+       if (ret < 0)
+               goto out_from;
+
+       ret = security_move_mount(&from_path, &to_path);
+       if (ret < 0)
+               goto out_to;
+
+       ret = do_move_mount(&from_path, &to_path);
+
+out_to:
+       path_put(&to_path);
+out_from:
+       path_put(&from_path);
+       return ret;
+}
+
 /*
  * Return true if path is reachable from root
  *
index 8a44075347acf45a2b2fcc3b466714b2170f0615..d052db1a15658c5f4303b9b8e305a6ecab5720f3 100644 (file)
  *     Parse a string of security data filling in the opts structure
  *     @options string containing all mount options known by the LSM
  *     @opts binary data structure usable by the LSM
+ * @move_mount:
+ *     Check permission before a mount is moved.
+ *     @from_path indicates the mount that is going to be moved.
+ *     @to_path indicates the mountpoint that will be mounted upon.
  * @dentry_init_security:
  *     Compute a context for a dentry as the inode is not yet available
  *     since NFSv4 has no label backed by an EA anyway.
@@ -1484,6 +1488,7 @@ union security_list_options {
                                        unsigned long kern_flags,
                                        unsigned long *set_kern_flags);
        int (*sb_parse_opts_str)(char *options, struct security_mnt_opts *opts);
+       int (*move_mount)(const struct path *from_path, const struct path *to_path);
        int (*dentry_init_security)(struct dentry *dentry, int mode,
                                        const struct qstr *name, void **ctx,
                                        u32 *ctxlen);
@@ -1816,6 +1821,7 @@ struct security_hook_heads {
        struct hlist_head sb_set_mnt_opts;
        struct hlist_head sb_clone_mnt_opts;
        struct hlist_head sb_parse_opts_str;
+       struct hlist_head move_mount;
        struct hlist_head dentry_init_security;
        struct hlist_head dentry_create_files_as;
 #ifdef CONFIG_SECURITY_PATH
index 30a3db9f284b317af48ff028429fb33bdfc828c9..a306061d219734e74f6d7ea8c2601439702b885a 100644 (file)
@@ -266,6 +266,7 @@ int security_sb_clone_mnt_opts(const struct super_block *oldsb,
                                unsigned long kern_flags,
                                unsigned long *set_kern_flags);
 int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts);
+int security_move_mount(const struct path *from_path, const struct path *to_path);
 int security_dentry_init_security(struct dentry *dentry, int mode,
                                        const struct qstr *name, void **ctx,
                                        u32 *ctxlen);
@@ -621,6 +622,12 @@ static inline int security_sb_parse_opts_str(char *options, struct security_mnt_
        return 0;
 }
 
+static inline int security_move_mount(const struct path *from_path,
+                                     const struct path *to_path)
+{
+       return 0;
+}
+
 static inline int security_inode_alloc(struct inode *inode)
 {
        return 0;
index 6978f3c76d419e5a88f453385b884011d470cb46..79042396f7e59755a46a0947b67f2953fd45528e 100644 (file)
@@ -907,6 +907,9 @@ asmlinkage long sys_statx(int dfd, const char __user *path, unsigned flags,
 asmlinkage long sys_rseq(struct rseq __user *rseq, uint32_t rseq_len,
                         int flags, uint32_t sig);
 asmlinkage long sys_open_tree(int dfd, const char __user *path, unsigned flags);
+asmlinkage long sys_move_mount(int from_dfd, const char __user *from_path,
+                              int to_dfd, const char __user *to_path,
+                              unsigned int ms_flags);
 
 /*
  * Architecture-specific system calls
index e8db2911adca2ed22cf8a2b3a881c432d5dbb8eb..89adf0d731ab8d66e9f65709e3d5d2e9a47d9cdf 100644 (file)
@@ -7,4 +7,15 @@
 #define OPEN_TREE_CLONE                1               /* Clone the target tree and attach the clone */
 #define OPEN_TREE_CLOEXEC      O_CLOEXEC       /* Close the file on execve() */
 
+/*
+ * move_mount() flags.
+ */
+#define MOVE_MOUNT_F_SYMLINKS          0x00000001 /* Follow symlinks on from path */
+#define MOVE_MOUNT_F_AUTOMOUNTS                0x00000002 /* Follow automounts on from path */
+#define MOVE_MOUNT_F_EMPTY_PATH                0x00000004 /* Empty from path permitted */
+#define MOVE_MOUNT_T_SYMLINKS          0x00000010 /* Follow symlinks on to path */
+#define MOVE_MOUNT_T_AUTOMOUNTS                0x00000020 /* Follow automounts on to path */
+#define MOVE_MOUNT_T_EMPTY_PATH                0x00000040 /* Empty to path permitted */
+#define MOVE_MOUNT__MASK               0x00000077
+
 #endif /* _UAPI_LINUX_MOUNT_H */
index 3d99ed8d9ddd70d6753dd2e90202934b584d7d00..96a061cecb393667cc798a29d85f49ba009e2739 100644 (file)
@@ -444,6 +444,11 @@ int security_sb_parse_opts_str(char *options, struct security_mnt_opts *opts)
 }
 EXPORT_SYMBOL(security_sb_parse_opts_str);
 
+int security_move_mount(const struct path *from_path, const struct path *to_path)
+{
+       return call_int_hook(move_mount, 0, from_path, to_path);
+}
+
 int security_inode_alloc(struct inode *inode)
 {
        inode->i_security = NULL;