f2fs: fix compat F2FS_IOC_{MOVE,GARBAGE_COLLECT}_RANGE
[ Upstream commit 34178b1bc4b5c936eab3adb4835578093095a571 ] Eric reported a ioctl bug in below link: https://lore.kernel.org/linux-f2fs-devel/20201103032234.GB2875@sol.localdomain/ That said, on some 32-bit architectures, u64 has only 32-bit alignment, notably i386 and x86_32, so that size of struct f2fs_gc_range compiled in x86_32 is 20 bytes, however the size in x86_64 is 24 bytes, binary compiled in x86_32 can not call F2FS_IOC_GARBAGE_COLLECT_RANGE successfully due to mismatched value of ioctl command in between binary and f2fs module, similarly, F2FS_IOC_MOVE_RANGE will fail too. In this patch we introduce two ioctls for compatibility of above special 32-bit binary: - F2FS_IOC32_GARBAGE_COLLECT_RANGE - F2FS_IOC32_MOVE_RANGE Reported-by: Eric Biggers <ebiggers@google.com> Signed-off-by: Chao Yu <yuchao0@huawei.com> Reviewed-by: Eric Biggers <ebiggers@google.com> Signed-off-by: Jaegeuk Kim <jaegeuk@kernel.org> Signed-off-by: Sasha Levin <sashal@kernel.org>
This commit is contained in:

committed by
Greg Kroah-Hartman

parent
b7ff0885de
commit
de2041d92d
137
fs/f2fs/file.c
137
fs/f2fs/file.c
@@ -2496,26 +2496,19 @@ out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int f2fs_ioc_gc_range(struct file *filp, unsigned long arg)
|
static int __f2fs_ioc_gc_range(struct file *filp, struct f2fs_gc_range *range)
|
||||||
{
|
{
|
||||||
struct inode *inode = file_inode(filp);
|
struct f2fs_sb_info *sbi = F2FS_I_SB(file_inode(filp));
|
||||||
struct f2fs_sb_info *sbi = F2FS_I_SB(inode);
|
|
||||||
struct f2fs_gc_range range;
|
|
||||||
u64 end;
|
u64 end;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
if (!capable(CAP_SYS_ADMIN))
|
if (!capable(CAP_SYS_ADMIN))
|
||||||
return -EPERM;
|
return -EPERM;
|
||||||
|
|
||||||
if (copy_from_user(&range, (struct f2fs_gc_range __user *)arg,
|
|
||||||
sizeof(range)))
|
|
||||||
return -EFAULT;
|
|
||||||
|
|
||||||
if (f2fs_readonly(sbi->sb))
|
if (f2fs_readonly(sbi->sb))
|
||||||
return -EROFS;
|
return -EROFS;
|
||||||
|
|
||||||
end = range.start + range.len;
|
end = range->start + range->len;
|
||||||
if (end < range.start || range.start < MAIN_BLKADDR(sbi) ||
|
if (end < range->start || range->start < MAIN_BLKADDR(sbi) ||
|
||||||
end >= MAX_BLKADDR(sbi))
|
end >= MAX_BLKADDR(sbi))
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
@@ -2524,7 +2517,7 @@ static int f2fs_ioc_gc_range(struct file *filp, unsigned long arg)
|
|||||||
return ret;
|
return ret;
|
||||||
|
|
||||||
do_more:
|
do_more:
|
||||||
if (!range.sync) {
|
if (!range->sync) {
|
||||||
if (!down_write_trylock(&sbi->gc_lock)) {
|
if (!down_write_trylock(&sbi->gc_lock)) {
|
||||||
ret = -EBUSY;
|
ret = -EBUSY;
|
||||||
goto out;
|
goto out;
|
||||||
@@ -2533,20 +2526,30 @@ do_more:
|
|||||||
down_write(&sbi->gc_lock);
|
down_write(&sbi->gc_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = f2fs_gc(sbi, range.sync, true, GET_SEGNO(sbi, range.start));
|
ret = f2fs_gc(sbi, range->sync, true, GET_SEGNO(sbi, range->start));
|
||||||
if (ret) {
|
if (ret) {
|
||||||
if (ret == -EBUSY)
|
if (ret == -EBUSY)
|
||||||
ret = -EAGAIN;
|
ret = -EAGAIN;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
range.start += BLKS_PER_SEC(sbi);
|
range->start += BLKS_PER_SEC(sbi);
|
||||||
if (range.start <= end)
|
if (range->start <= end)
|
||||||
goto do_more;
|
goto do_more;
|
||||||
out:
|
out:
|
||||||
mnt_drop_write_file(filp);
|
mnt_drop_write_file(filp);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int f2fs_ioc_gc_range(struct file *filp, unsigned long arg)
|
||||||
|
{
|
||||||
|
struct f2fs_gc_range range;
|
||||||
|
|
||||||
|
if (copy_from_user(&range, (struct f2fs_gc_range __user *)arg,
|
||||||
|
sizeof(range)))
|
||||||
|
return -EFAULT;
|
||||||
|
return __f2fs_ioc_gc_range(filp, &range);
|
||||||
|
}
|
||||||
|
|
||||||
static int f2fs_ioc_write_checkpoint(struct file *filp, unsigned long arg)
|
static int f2fs_ioc_write_checkpoint(struct file *filp, unsigned long arg)
|
||||||
{
|
{
|
||||||
struct inode *inode = file_inode(filp);
|
struct inode *inode = file_inode(filp);
|
||||||
@@ -2883,9 +2886,9 @@ out:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int f2fs_ioc_move_range(struct file *filp, unsigned long arg)
|
static int __f2fs_ioc_move_range(struct file *filp,
|
||||||
|
struct f2fs_move_range *range)
|
||||||
{
|
{
|
||||||
struct f2fs_move_range range;
|
|
||||||
struct fd dst;
|
struct fd dst;
|
||||||
int err;
|
int err;
|
||||||
|
|
||||||
@@ -2893,11 +2896,7 @@ static int f2fs_ioc_move_range(struct file *filp, unsigned long arg)
|
|||||||
!(filp->f_mode & FMODE_WRITE))
|
!(filp->f_mode & FMODE_WRITE))
|
||||||
return -EBADF;
|
return -EBADF;
|
||||||
|
|
||||||
if (copy_from_user(&range, (struct f2fs_move_range __user *)arg,
|
dst = fdget(range->dst_fd);
|
||||||
sizeof(range)))
|
|
||||||
return -EFAULT;
|
|
||||||
|
|
||||||
dst = fdget(range.dst_fd);
|
|
||||||
if (!dst.file)
|
if (!dst.file)
|
||||||
return -EBADF;
|
return -EBADF;
|
||||||
|
|
||||||
@@ -2910,8 +2909,8 @@ static int f2fs_ioc_move_range(struct file *filp, unsigned long arg)
|
|||||||
if (err)
|
if (err)
|
||||||
goto err_out;
|
goto err_out;
|
||||||
|
|
||||||
err = f2fs_move_file_range(filp, range.pos_in, dst.file,
|
err = f2fs_move_file_range(filp, range->pos_in, dst.file,
|
||||||
range.pos_out, range.len);
|
range->pos_out, range->len);
|
||||||
|
|
||||||
mnt_drop_write_file(filp);
|
mnt_drop_write_file(filp);
|
||||||
if (err)
|
if (err)
|
||||||
@@ -2925,6 +2924,16 @@ err_out:
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int f2fs_ioc_move_range(struct file *filp, unsigned long arg)
|
||||||
|
{
|
||||||
|
struct f2fs_move_range range;
|
||||||
|
|
||||||
|
if (copy_from_user(&range, (struct f2fs_move_range __user *)arg,
|
||||||
|
sizeof(range)))
|
||||||
|
return -EFAULT;
|
||||||
|
return __f2fs_ioc_move_range(filp, &range);
|
||||||
|
}
|
||||||
|
|
||||||
static int f2fs_ioc_flush_device(struct file *filp, unsigned long arg)
|
static int f2fs_ioc_flush_device(struct file *filp, unsigned long arg)
|
||||||
{
|
{
|
||||||
struct inode *inode = file_inode(filp);
|
struct inode *inode = file_inode(filp);
|
||||||
@@ -3961,13 +3970,8 @@ err:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
static long __f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||||
{
|
{
|
||||||
if (unlikely(f2fs_cp_error(F2FS_I_SB(file_inode(filp)))))
|
|
||||||
return -EIO;
|
|
||||||
if (!f2fs_is_checkpoint_ready(F2FS_I_SB(file_inode(filp))))
|
|
||||||
return -ENOSPC;
|
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case FS_IOC_GETFLAGS:
|
case FS_IOC_GETFLAGS:
|
||||||
return f2fs_ioc_getflags(filp, arg);
|
return f2fs_ioc_getflags(filp, arg);
|
||||||
@@ -4054,6 +4058,16 @@ long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
long f2fs_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
|
||||||
|
{
|
||||||
|
if (unlikely(f2fs_cp_error(F2FS_I_SB(file_inode(filp)))))
|
||||||
|
return -EIO;
|
||||||
|
if (!f2fs_is_checkpoint_ready(F2FS_I_SB(file_inode(filp))))
|
||||||
|
return -ENOSPC;
|
||||||
|
|
||||||
|
return __f2fs_ioctl(filp, cmd, arg);
|
||||||
|
}
|
||||||
|
|
||||||
static ssize_t f2fs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
|
static ssize_t f2fs_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
|
||||||
{
|
{
|
||||||
struct file *file = iocb->ki_filp;
|
struct file *file = iocb->ki_filp;
|
||||||
@@ -4176,8 +4190,63 @@ out:
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_COMPAT
|
#ifdef CONFIG_COMPAT
|
||||||
|
struct compat_f2fs_gc_range {
|
||||||
|
u32 sync;
|
||||||
|
compat_u64 start;
|
||||||
|
compat_u64 len;
|
||||||
|
};
|
||||||
|
#define F2FS_IOC32_GARBAGE_COLLECT_RANGE _IOW(F2FS_IOCTL_MAGIC, 11,\
|
||||||
|
struct compat_f2fs_gc_range)
|
||||||
|
|
||||||
|
static int f2fs_compat_ioc_gc_range(struct file *file, unsigned long arg)
|
||||||
|
{
|
||||||
|
struct compat_f2fs_gc_range __user *urange;
|
||||||
|
struct f2fs_gc_range range;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
urange = compat_ptr(arg);
|
||||||
|
err = get_user(range.sync, &urange->sync);
|
||||||
|
err |= get_user(range.start, &urange->start);
|
||||||
|
err |= get_user(range.len, &urange->len);
|
||||||
|
if (err)
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
return __f2fs_ioc_gc_range(file, &range);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct compat_f2fs_move_range {
|
||||||
|
u32 dst_fd;
|
||||||
|
compat_u64 pos_in;
|
||||||
|
compat_u64 pos_out;
|
||||||
|
compat_u64 len;
|
||||||
|
};
|
||||||
|
#define F2FS_IOC32_MOVE_RANGE _IOWR(F2FS_IOCTL_MAGIC, 9, \
|
||||||
|
struct compat_f2fs_move_range)
|
||||||
|
|
||||||
|
static int f2fs_compat_ioc_move_range(struct file *file, unsigned long arg)
|
||||||
|
{
|
||||||
|
struct compat_f2fs_move_range __user *urange;
|
||||||
|
struct f2fs_move_range range;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
urange = compat_ptr(arg);
|
||||||
|
err = get_user(range.dst_fd, &urange->dst_fd);
|
||||||
|
err |= get_user(range.pos_in, &urange->pos_in);
|
||||||
|
err |= get_user(range.pos_out, &urange->pos_out);
|
||||||
|
err |= get_user(range.len, &urange->len);
|
||||||
|
if (err)
|
||||||
|
return -EFAULT;
|
||||||
|
|
||||||
|
return __f2fs_ioc_move_range(file, &range);
|
||||||
|
}
|
||||||
|
|
||||||
long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||||
{
|
{
|
||||||
|
if (unlikely(f2fs_cp_error(F2FS_I_SB(file_inode(file)))))
|
||||||
|
return -EIO;
|
||||||
|
if (!f2fs_is_checkpoint_ready(F2FS_I_SB(file_inode(file))))
|
||||||
|
return -ENOSPC;
|
||||||
|
|
||||||
switch (cmd) {
|
switch (cmd) {
|
||||||
case FS_IOC32_GETFLAGS:
|
case FS_IOC32_GETFLAGS:
|
||||||
cmd = FS_IOC_GETFLAGS;
|
cmd = FS_IOC_GETFLAGS;
|
||||||
@@ -4188,6 +4257,10 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||||||
case FS_IOC32_GETVERSION:
|
case FS_IOC32_GETVERSION:
|
||||||
cmd = FS_IOC_GETVERSION;
|
cmd = FS_IOC_GETVERSION;
|
||||||
break;
|
break;
|
||||||
|
case F2FS_IOC32_GARBAGE_COLLECT_RANGE:
|
||||||
|
return f2fs_compat_ioc_gc_range(file, arg);
|
||||||
|
case F2FS_IOC32_MOVE_RANGE:
|
||||||
|
return f2fs_compat_ioc_move_range(file, arg);
|
||||||
case F2FS_IOC_START_ATOMIC_WRITE:
|
case F2FS_IOC_START_ATOMIC_WRITE:
|
||||||
case F2FS_IOC_COMMIT_ATOMIC_WRITE:
|
case F2FS_IOC_COMMIT_ATOMIC_WRITE:
|
||||||
case F2FS_IOC_START_VOLATILE_WRITE:
|
case F2FS_IOC_START_VOLATILE_WRITE:
|
||||||
@@ -4205,10 +4278,8 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||||||
case FS_IOC_GET_ENCRYPTION_KEY_STATUS:
|
case FS_IOC_GET_ENCRYPTION_KEY_STATUS:
|
||||||
case FS_IOC_GET_ENCRYPTION_NONCE:
|
case FS_IOC_GET_ENCRYPTION_NONCE:
|
||||||
case F2FS_IOC_GARBAGE_COLLECT:
|
case F2FS_IOC_GARBAGE_COLLECT:
|
||||||
case F2FS_IOC_GARBAGE_COLLECT_RANGE:
|
|
||||||
case F2FS_IOC_WRITE_CHECKPOINT:
|
case F2FS_IOC_WRITE_CHECKPOINT:
|
||||||
case F2FS_IOC_DEFRAGMENT:
|
case F2FS_IOC_DEFRAGMENT:
|
||||||
case F2FS_IOC_MOVE_RANGE:
|
|
||||||
case F2FS_IOC_FLUSH_DEVICE:
|
case F2FS_IOC_FLUSH_DEVICE:
|
||||||
case F2FS_IOC_GET_FEATURES:
|
case F2FS_IOC_GET_FEATURES:
|
||||||
case FS_IOC_FSGETXATTR:
|
case FS_IOC_FSGETXATTR:
|
||||||
@@ -4229,7 +4300,7 @@ long f2fs_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||||||
default:
|
default:
|
||||||
return -ENOIOCTLCMD;
|
return -ENOIOCTLCMD;
|
||||||
}
|
}
|
||||||
return f2fs_ioctl(file, cmd, (unsigned long) compat_ptr(arg));
|
return __f2fs_ioctl(file, cmd, (unsigned long) compat_ptr(arg));
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user