Commit 1c1c8747 authored by Al Viro's avatar Al Viro

btrfs: sanitize BTRFS_IOC_FILE_EXTENT_SAME

* don't assume that ->dest_count won't change between copy_from_user()
and memdup_user()
* use fdget instead of fget
* don't bother comparing superblocks when we'd already compared vfsmounts
* get rid of excessive goto
* use file_inode() instead of open-coding the sucker
Signed-off-by: default avatarAl Viro <viro@zeniv.linux.org.uk>
parent 208adb64
...@@ -2686,14 +2686,11 @@ static int btrfs_extent_same(struct inode *src, u64 loff, u64 len, ...@@ -2686,14 +2686,11 @@ static int btrfs_extent_same(struct inode *src, u64 loff, u64 len,
#define BTRFS_MAX_DEDUPE_LEN (16 * 1024 * 1024) #define BTRFS_MAX_DEDUPE_LEN (16 * 1024 * 1024)
static long btrfs_ioctl_file_extent_same(struct file *file, static long btrfs_ioctl_file_extent_same(struct file *file,
void __user *argp) struct btrfs_ioctl_same_args __user *argp)
{ {
struct btrfs_ioctl_same_args tmp;
struct btrfs_ioctl_same_args *same; struct btrfs_ioctl_same_args *same;
struct btrfs_ioctl_same_extent_info *info; struct btrfs_ioctl_same_extent_info *info;
struct inode *src = file->f_dentry->d_inode; struct inode *src = file_inode(file);
struct file *dst_file = NULL;
struct inode *dst;
u64 off; u64 off;
u64 len; u64 len;
int i; int i;
...@@ -2701,6 +2698,7 @@ static long btrfs_ioctl_file_extent_same(struct file *file, ...@@ -2701,6 +2698,7 @@ static long btrfs_ioctl_file_extent_same(struct file *file,
unsigned long size; unsigned long size;
u64 bs = BTRFS_I(src)->root->fs_info->sb->s_blocksize; u64 bs = BTRFS_I(src)->root->fs_info->sb->s_blocksize;
bool is_admin = capable(CAP_SYS_ADMIN); bool is_admin = capable(CAP_SYS_ADMIN);
u16 count;
if (!(file->f_mode & FMODE_READ)) if (!(file->f_mode & FMODE_READ))
return -EINVAL; return -EINVAL;
...@@ -2709,17 +2707,14 @@ static long btrfs_ioctl_file_extent_same(struct file *file, ...@@ -2709,17 +2707,14 @@ static long btrfs_ioctl_file_extent_same(struct file *file,
if (ret) if (ret)
return ret; return ret;
if (copy_from_user(&tmp, if (get_user(count, &argp->dest_count)) {
(struct btrfs_ioctl_same_args __user *)argp,
sizeof(tmp))) {
ret = -EFAULT; ret = -EFAULT;
goto out; goto out;
} }
size = sizeof(tmp) + size = offsetof(struct btrfs_ioctl_same_args __user, info[count]);
tmp.dest_count * sizeof(struct btrfs_ioctl_same_extent_info);
same = memdup_user((struct btrfs_ioctl_same_args __user *)argp, size); same = memdup_user(argp, size);
if (IS_ERR(same)) { if (IS_ERR(same)) {
ret = PTR_ERR(same); ret = PTR_ERR(same);
...@@ -2756,52 +2751,35 @@ static long btrfs_ioctl_file_extent_same(struct file *file, ...@@ -2756,52 +2751,35 @@ static long btrfs_ioctl_file_extent_same(struct file *file,
goto out; goto out;
/* pre-format output fields to sane values */ /* pre-format output fields to sane values */
for (i = 0; i < same->dest_count; i++) { for (i = 0; i < count; i++) {
same->info[i].bytes_deduped = 0ULL; same->info[i].bytes_deduped = 0ULL;
same->info[i].status = 0; same->info[i].status = 0;
} }
ret = 0; for (i = 0, info = same->info; i < count; i++, info++) {
for (i = 0; i < same->dest_count; i++) { struct inode *dst;
info = &same->info[i]; struct fd dst_file = fdget(info->fd);
if (!dst_file.file) {
dst_file = fget(info->fd);
if (!dst_file) {
info->status = -EBADF; info->status = -EBADF;
goto next; continue;
} }
dst = file_inode(dst_file.file);
if (!(is_admin || (dst_file->f_mode & FMODE_WRITE))) { if (!(is_admin || (dst_file.file->f_mode & FMODE_WRITE))) {
info->status = -EINVAL; info->status = -EINVAL;
goto next; } else if (file->f_path.mnt != dst_file.file->f_path.mnt) {
} info->status = -EXDEV;
} else if (S_ISDIR(dst->i_mode)) {
info->status = -EXDEV;
if (file->f_path.mnt != dst_file->f_path.mnt)
goto next;
dst = dst_file->f_dentry->d_inode;
if (src->i_sb != dst->i_sb)
goto next;
if (S_ISDIR(dst->i_mode)) {
info->status = -EISDIR; info->status = -EISDIR;
goto next; } else if (!S_ISREG(dst->i_mode)) {
}
if (!S_ISREG(dst->i_mode)) {
info->status = -EACCES; info->status = -EACCES;
goto next; } else {
info->status = btrfs_extent_same(src, off, len, dst,
info->logical_offset);
if (info->status == 0)
info->bytes_deduped += len;
} }
fdput(dst_file);
info->status = btrfs_extent_same(src, off, len, dst,
info->logical_offset);
if (info->status == 0)
info->bytes_deduped += len;
next:
if (dst_file)
fput(dst_file);
} }
ret = copy_to_user(argp, same, size); ret = copy_to_user(argp, same, size);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment