Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

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

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
Loading
Loading
Loading
Loading
+24 −46
Original line number Diff line number Diff line
@@ -2686,14 +2686,11 @@ out_unlock:
#define BTRFS_MAX_DEDUPE_LEN	(16 * 1024 * 1024)

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_extent_info *info;
	struct inode *src = file->f_dentry->d_inode;
	struct file *dst_file = NULL;
	struct inode *dst;
	struct inode *src = file_inode(file);
	u64 off;
	u64 len;
	int i;
@@ -2701,6 +2698,7 @@ static long btrfs_ioctl_file_extent_same(struct file *file,
	unsigned long size;
	u64 bs = BTRFS_I(src)->root->fs_info->sb->s_blocksize;
	bool is_admin = capable(CAP_SYS_ADMIN);
	u16 count;

	if (!(file->f_mode & FMODE_READ))
		return -EINVAL;
@@ -2709,17 +2707,14 @@ static long btrfs_ioctl_file_extent_same(struct file *file,
	if (ret)
		return ret;

	if (copy_from_user(&tmp,
			   (struct btrfs_ioctl_same_args __user *)argp,
			   sizeof(tmp))) {
	if (get_user(count, &argp->dest_count)) {
		ret = -EFAULT;
		goto out;
	}

	size = sizeof(tmp) +
		tmp.dest_count * sizeof(struct btrfs_ioctl_same_extent_info);
	size = offsetof(struct btrfs_ioctl_same_args __user, info[count]);

	same = memdup_user((struct btrfs_ioctl_same_args __user *)argp, size);
	same = memdup_user(argp, size);

	if (IS_ERR(same)) {
		ret = PTR_ERR(same);
@@ -2756,52 +2751,35 @@ static long btrfs_ioctl_file_extent_same(struct file *file,
		goto out;

	/* 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].status = 0;
	}

	ret = 0;
	for (i = 0; i < same->dest_count; i++) {
		info = &same->info[i];

		dst_file = fget(info->fd);
		if (!dst_file) {
	for (i = 0, info = same->info; i < count; i++, info++) {
		struct inode *dst;
		struct fd dst_file = fdget(info->fd);
		if (!dst_file.file) {
			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;
			goto next;
		}

		} else if (file->f_path.mnt != dst_file.file->f_path.mnt) {
			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)) {
		} else if (S_ISDIR(dst->i_mode)) {
			info->status = -EISDIR;
			goto next;
		}

		if (!S_ISREG(dst->i_mode)) {
		} else if (!S_ISREG(dst->i_mode)) {
			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;

next:
		if (dst_file)
			fput(dst_file);
		}
		fdput(dst_file);
	}

	ret = copy_to_user(argp, same, size);