|
@@ -1957,7 +1957,8 @@ static noinline int copy_to_sk(struct btrfs_root *root,
|
|
|
struct btrfs_path *path,
|
|
|
struct btrfs_key *key,
|
|
|
struct btrfs_ioctl_search_key *sk,
|
|
|
- char *buf,
|
|
|
+ size_t *buf_size,
|
|
|
+ char __user *ubuf,
|
|
|
unsigned long *sk_offset,
|
|
|
int *num_found)
|
|
|
{
|
|
@@ -1989,13 +1990,25 @@ static noinline int copy_to_sk(struct btrfs_root *root,
|
|
|
if (!key_in_sk(key, sk))
|
|
|
continue;
|
|
|
|
|
|
- if (sizeof(sh) + item_len > BTRFS_SEARCH_ARGS_BUFSIZE)
|
|
|
+ if (sizeof(sh) + item_len > *buf_size) {
|
|
|
+ if (*num_found) {
|
|
|
+ ret = 1;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /*
|
|
|
+ * return one empty item back for v1, which does not
|
|
|
+ * handle -EOVERFLOW
|
|
|
+ */
|
|
|
+
|
|
|
+ *buf_size = sizeof(sh) + item_len;
|
|
|
item_len = 0;
|
|
|
+ ret = -EOVERFLOW;
|
|
|
+ }
|
|
|
|
|
|
- if (sizeof(sh) + item_len + *sk_offset >
|
|
|
- BTRFS_SEARCH_ARGS_BUFSIZE) {
|
|
|
+ if (sizeof(sh) + item_len + *sk_offset > *buf_size) {
|
|
|
ret = 1;
|
|
|
- goto overflow;
|
|
|
+ goto out;
|
|
|
}
|
|
|
|
|
|
sh.objectid = key->objectid;
|
|
@@ -2005,20 +2018,33 @@ static noinline int copy_to_sk(struct btrfs_root *root,
|
|
|
sh.transid = found_transid;
|
|
|
|
|
|
/* copy search result header */
|
|
|
- memcpy(buf + *sk_offset, &sh, sizeof(sh));
|
|
|
+ if (copy_to_user(ubuf + *sk_offset, &sh, sizeof(sh))) {
|
|
|
+ ret = -EFAULT;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
*sk_offset += sizeof(sh);
|
|
|
|
|
|
if (item_len) {
|
|
|
- char *p = buf + *sk_offset;
|
|
|
+ char __user *up = ubuf + *sk_offset;
|
|
|
/* copy the item */
|
|
|
- read_extent_buffer(leaf, p,
|
|
|
- item_off, item_len);
|
|
|
+ if (read_extent_buffer_to_user(leaf, up,
|
|
|
+ item_off, item_len)) {
|
|
|
+ ret = -EFAULT;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
*sk_offset += item_len;
|
|
|
}
|
|
|
(*num_found)++;
|
|
|
|
|
|
- if (*num_found >= sk->nr_items)
|
|
|
- break;
|
|
|
+ if (ret) /* -EOVERFLOW from above */
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ if (*num_found >= sk->nr_items) {
|
|
|
+ ret = 1;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
}
|
|
|
advance_key:
|
|
|
ret = 0;
|
|
@@ -2033,22 +2059,37 @@ advance_key:
|
|
|
key->objectid++;
|
|
|
} else
|
|
|
ret = 1;
|
|
|
-overflow:
|
|
|
+out:
|
|
|
+ /*
|
|
|
+ * 0: all items from this leaf copied, continue with next
|
|
|
+ * 1: * more items can be copied, but unused buffer is too small
|
|
|
+ * * all items were found
|
|
|
+ * Either way, it will stops the loop which iterates to the next
|
|
|
+ * leaf
|
|
|
+ * -EOVERFLOW: item was to large for buffer
|
|
|
+ * -EFAULT: could not copy extent buffer back to userspace
|
|
|
+ */
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
|
static noinline int search_ioctl(struct inode *inode,
|
|
|
- struct btrfs_ioctl_search_args *args)
|
|
|
+ struct btrfs_ioctl_search_key *sk,
|
|
|
+ size_t *buf_size,
|
|
|
+ char __user *ubuf)
|
|
|
{
|
|
|
struct btrfs_root *root;
|
|
|
struct btrfs_key key;
|
|
|
struct btrfs_path *path;
|
|
|
- struct btrfs_ioctl_search_key *sk = &args->key;
|
|
|
struct btrfs_fs_info *info = BTRFS_I(inode)->root->fs_info;
|
|
|
int ret;
|
|
|
int num_found = 0;
|
|
|
unsigned long sk_offset = 0;
|
|
|
|
|
|
+ if (*buf_size < sizeof(struct btrfs_ioctl_search_header)) {
|
|
|
+ *buf_size = sizeof(struct btrfs_ioctl_search_header);
|
|
|
+ return -EOVERFLOW;
|
|
|
+ }
|
|
|
+
|
|
|
path = btrfs_alloc_path();
|
|
|
if (!path)
|
|
|
return -ENOMEM;
|
|
@@ -2082,14 +2123,15 @@ static noinline int search_ioctl(struct inode *inode,
|
|
|
ret = 0;
|
|
|
goto err;
|
|
|
}
|
|
|
- ret = copy_to_sk(root, path, &key, sk, args->buf,
|
|
|
+ ret = copy_to_sk(root, path, &key, sk, buf_size, ubuf,
|
|
|
&sk_offset, &num_found);
|
|
|
btrfs_release_path(path);
|
|
|
- if (ret || num_found >= sk->nr_items)
|
|
|
+ if (ret)
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
- ret = 0;
|
|
|
+ if (ret > 0)
|
|
|
+ ret = 0;
|
|
|
err:
|
|
|
sk->nr_items = num_found;
|
|
|
btrfs_free_path(path);
|
|
@@ -2099,22 +2141,73 @@ err:
|
|
|
static noinline int btrfs_ioctl_tree_search(struct file *file,
|
|
|
void __user *argp)
|
|
|
{
|
|
|
- struct btrfs_ioctl_search_args *args;
|
|
|
- struct inode *inode;
|
|
|
- int ret;
|
|
|
+ struct btrfs_ioctl_search_args __user *uargs;
|
|
|
+ struct btrfs_ioctl_search_key sk;
|
|
|
+ struct inode *inode;
|
|
|
+ int ret;
|
|
|
+ size_t buf_size;
|
|
|
|
|
|
if (!capable(CAP_SYS_ADMIN))
|
|
|
return -EPERM;
|
|
|
|
|
|
- args = memdup_user(argp, sizeof(*args));
|
|
|
- if (IS_ERR(args))
|
|
|
- return PTR_ERR(args);
|
|
|
+ uargs = (struct btrfs_ioctl_search_args __user *)argp;
|
|
|
+
|
|
|
+ if (copy_from_user(&sk, &uargs->key, sizeof(sk)))
|
|
|
+ return -EFAULT;
|
|
|
+
|
|
|
+ buf_size = sizeof(uargs->buf);
|
|
|
|
|
|
inode = file_inode(file);
|
|
|
- ret = search_ioctl(inode, args);
|
|
|
- if (ret == 0 && copy_to_user(argp, args, sizeof(*args)))
|
|
|
+ ret = search_ioctl(inode, &sk, &buf_size, uargs->buf);
|
|
|
+
|
|
|
+ /*
|
|
|
+ * In the origin implementation an overflow is handled by returning a
|
|
|
+ * search header with a len of zero, so reset ret.
|
|
|
+ */
|
|
|
+ if (ret == -EOVERFLOW)
|
|
|
+ ret = 0;
|
|
|
+
|
|
|
+ if (ret == 0 && copy_to_user(&uargs->key, &sk, sizeof(sk)))
|
|
|
ret = -EFAULT;
|
|
|
- kfree(args);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+static noinline int btrfs_ioctl_tree_search_v2(struct file *file,
|
|
|
+ void __user *argp)
|
|
|
+{
|
|
|
+ struct btrfs_ioctl_search_args_v2 __user *uarg;
|
|
|
+ struct btrfs_ioctl_search_args_v2 args;
|
|
|
+ struct inode *inode;
|
|
|
+ int ret;
|
|
|
+ size_t buf_size;
|
|
|
+ const size_t buf_limit = 16 * 1024 * 1024;
|
|
|
+
|
|
|
+ if (!capable(CAP_SYS_ADMIN))
|
|
|
+ return -EPERM;
|
|
|
+
|
|
|
+ /* copy search header and buffer size */
|
|
|
+ uarg = (struct btrfs_ioctl_search_args_v2 __user *)argp;
|
|
|
+ if (copy_from_user(&args, uarg, sizeof(args)))
|
|
|
+ return -EFAULT;
|
|
|
+
|
|
|
+ buf_size = args.buf_size;
|
|
|
+
|
|
|
+ if (buf_size < sizeof(struct btrfs_ioctl_search_header))
|
|
|
+ return -EOVERFLOW;
|
|
|
+
|
|
|
+ /* limit result size to 16MB */
|
|
|
+ if (buf_size > buf_limit)
|
|
|
+ buf_size = buf_limit;
|
|
|
+
|
|
|
+ inode = file_inode(file);
|
|
|
+ ret = search_ioctl(inode, &args.key, &buf_size,
|
|
|
+ (char *)(&uarg->buf[0]));
|
|
|
+ if (ret == 0 && copy_to_user(&uarg->key, &args.key, sizeof(args.key)))
|
|
|
+ ret = -EFAULT;
|
|
|
+ else if (ret == -EOVERFLOW &&
|
|
|
+ copy_to_user(&uarg->buf_size, &buf_size, sizeof(buf_size)))
|
|
|
+ ret = -EFAULT;
|
|
|
+
|
|
|
return ret;
|
|
|
}
|
|
|
|
|
@@ -5198,6 +5291,8 @@ long btrfs_ioctl(struct file *file, unsigned int
|
|
|
return btrfs_ioctl_trans_end(file);
|
|
|
case BTRFS_IOC_TREE_SEARCH:
|
|
|
return btrfs_ioctl_tree_search(file, argp);
|
|
|
+ case BTRFS_IOC_TREE_SEARCH_V2:
|
|
|
+ return btrfs_ioctl_tree_search_v2(file, argp);
|
|
|
case BTRFS_IOC_INO_LOOKUP:
|
|
|
return btrfs_ioctl_ino_lookup(file, argp);
|
|
|
case BTRFS_IOC_INO_PATHS:
|