btrfs: fix send ioctl on 32bit with 64bit kernel
authorJosef Bacik <josef@toxicpanda.com>
Wed, 27 Sep 2017 14:43:13 +0000 (10:43 -0400)
committerDavid Sterba <dsterba@suse.com>
Mon, 30 Oct 2017 11:27:59 +0000 (12:27 +0100)
We pass in a pointer in our send arg struct, this means the struct size
doesn't match with 32bit user space and 64bit kernel space.  Fix this by
adding a compat mode and doing the appropriate conversion.

Signed-off-by: Josef Bacik <jbacik@fb.com>
Reviewed-by: David Sterba <dsterba@suse.com>
[ move structure to the beginning, next to receive 32bit compat ]
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/ioctl.c
fs/btrfs/send.c
fs/btrfs/send.h

index 83ccadaf67050faef680054ceaa06414aabb15ee..f969c034d7b383feab46e987e49c22eb30a3b022 100644 (file)
@@ -86,6 +86,19 @@ struct btrfs_ioctl_received_subvol_args_32 {
                                struct btrfs_ioctl_received_subvol_args_32)
 #endif
 
+#if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT)
+struct btrfs_ioctl_send_args_32 {
+       __s64 send_fd;                  /* in */
+       __u64 clone_sources_count;      /* in */
+       compat_uptr_t clone_sources;    /* in */
+       __u64 parent_root;              /* in */
+       __u64 flags;                    /* in */
+       __u64 reserved[4];              /* in */
+} __attribute__ ((__packed__));
+
+#define BTRFS_IOC_SEND_32 _IOW(BTRFS_IOCTL_MAGIC, 38, \
+                              struct btrfs_ioctl_send_args_32)
+#endif
 
 static int btrfs_clone(struct inode *src, struct inode *inode,
                       u64 off, u64 olen, u64 olen_aligned, u64 destoff,
@@ -5463,6 +5476,41 @@ out_drop_write:
        return ret;
 }
 
+static int _btrfs_ioctl_send(struct file *file, void __user *argp, bool compat)
+{
+       struct btrfs_ioctl_send_args *arg;
+       int ret;
+
+       if (compat) {
+#if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT)
+               struct btrfs_ioctl_send_args_32 args32;
+
+               ret = copy_from_user(&args32, argp, sizeof(args32));
+               if (ret)
+                       return -EFAULT;
+               arg = kzalloc(sizeof(*arg), GFP_KERNEL);
+               if (!arg)
+                       return -ENOMEM;
+               arg->send_fd = args32.send_fd;
+               arg->clone_sources_count = args32.clone_sources_count;
+               arg->clone_sources = compat_ptr(args32.clone_sources);
+               arg->parent_root = args32.parent_root;
+               arg->flags = args32.flags;
+               memcpy(arg->reserved, args32.reserved,
+                      sizeof(args32.reserved));
+#else
+               return -ENOTTY;
+#endif
+       } else {
+               arg = memdup_user(argp, sizeof(*arg));
+               if (IS_ERR(arg))
+                       return PTR_ERR(arg);
+       }
+       ret = btrfs_ioctl_send(file, arg);
+       kfree(arg);
+       return ret;
+}
+
 long btrfs_ioctl(struct file *file, unsigned int
                cmd, unsigned long arg)
 {
@@ -5568,7 +5616,11 @@ long btrfs_ioctl(struct file *file, unsigned int
                return btrfs_ioctl_set_received_subvol_32(file, argp);
 #endif
        case BTRFS_IOC_SEND:
-               return btrfs_ioctl_send(file, argp);
+               return _btrfs_ioctl_send(file, argp, false);
+#if defined(CONFIG_64BIT) && defined(CONFIG_COMPAT)
+       case BTRFS_IOC_SEND_32:
+               return _btrfs_ioctl_send(file, argp, true);
+#endif
        case BTRFS_IOC_GET_DEV_STATS:
                return btrfs_ioctl_get_dev_stats(fs_info, argp);
        case BTRFS_IOC_QUOTA_CTL:
index 0746eda7231d2dde7dba4ff88d2503f06816e3c2..d9ddcdbdd2e7d37898ae208661605e78891c15d9 100644 (file)
@@ -26,6 +26,7 @@
 #include <linux/radix-tree.h>
 #include <linux/vmalloc.h>
 #include <linux/string.h>
+#include <linux/compat.h>
 
 #include "send.h"
 #include "backref.h"
@@ -6371,13 +6372,12 @@ static void btrfs_root_dec_send_in_progress(struct btrfs_root* root)
        spin_unlock(&root->root_item_lock);
 }
 
-long btrfs_ioctl_send(struct file *mnt_file, void __user *arg_)
+long btrfs_ioctl_send(struct file *mnt_file, struct btrfs_ioctl_send_args *arg)
 {
        int ret = 0;
        struct btrfs_root *send_root = BTRFS_I(file_inode(mnt_file))->root;
        struct btrfs_fs_info *fs_info = send_root->fs_info;
        struct btrfs_root *clone_root;
-       struct btrfs_ioctl_send_args *arg = NULL;
        struct btrfs_key key;
        struct send_ctx *sctx = NULL;
        u32 i;
@@ -6413,13 +6413,6 @@ long btrfs_ioctl_send(struct file *mnt_file, void __user *arg_)
                goto out;
        }
 
-       arg = memdup_user(arg_, sizeof(*arg));
-       if (IS_ERR(arg)) {
-               ret = PTR_ERR(arg);
-               arg = NULL;
-               goto out;
-       }
-
        /*
         * Check that we don't overflow at later allocations, we request
         * clone_sources_count + 1 items, and compare to unsigned long inside
@@ -6660,7 +6653,6 @@ out:
        if (sctx && !IS_ERR_OR_NULL(sctx->parent_root))
                btrfs_root_dec_send_in_progress(sctx->parent_root);
 
-       kfree(arg);
        kvfree(clone_sources_tmp);
 
        if (sctx) {
index 02e00166c4dab1894c92fe89c4faa87529b73641..3aa4bc55754fdb4b16567e5904666f346f679d2d 100644 (file)
@@ -130,5 +130,5 @@ enum {
 #define BTRFS_SEND_A_MAX (__BTRFS_SEND_A_MAX - 1)
 
 #ifdef __KERNEL__
-long btrfs_ioctl_send(struct file *mnt_file, void __user *arg);
+long btrfs_ioctl_send(struct file *mnt_file, struct btrfs_ioctl_send_args *arg);
 #endif