Commit d04e33b7 authored by Omar Sandoval's avatar Omar Sandoval Committed by Stefan Bader

Btrfs: fix clone vs chattr NODATASUM race

BugLink: https://bugs.launchpad.net/bugs/1784382

commit b5c40d59 upstream.

In btrfs_clone_files(), we must check the NODATASUM flag while the
inodes are locked. Otherwise, it's possible that btrfs_ioctl_setflags()
will change the flags after we check and we can end up with a party
checksummed file.

The race window is only a few instructions in size, between the if and
the locks which is:

3834         if (S_ISDIR(src->i_mode) || S_ISDIR(inode->i_mode))
3835                 return -EISDIR;

where the setflags must be run and toggle the NODATASUM flag (provided
the file size is 0).  The clone will block on the inode lock, segflags
takes the inode lock, changes flags, releases log and clone continues.

Not impossible but still needs a lot of bad luck to hit unintentionally.

Fixes: 0e7b824c ("Btrfs: don't make a file partly checksummed through file clone")
CC: stable@vger.kernel.org # 4.4+
Signed-off-by: default avatarOmar Sandoval <osandov@fb.com>
Reviewed-by: default avatarNikolay Borisov <nborisov@suse.com>
Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
[ update changelog ]
Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
[ adjusted for 4.4 ]
Signed-off-by: default avatarNikolay Borisov <nborisov@suse.com>
Signed-off-by: default avatarKleber Sacilotto de Souza <kleber.souza@canonical.com>
Signed-off-by: default avatarKhalid Elmously <khalid.elmously@canonical.com>
parent 47d48274
...@@ -3923,11 +3923,6 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd, ...@@ -3923,11 +3923,6 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
if (!(src_file.file->f_mode & FMODE_READ)) if (!(src_file.file->f_mode & FMODE_READ))
goto out_fput; goto out_fput;
/* don't make the dst file partly checksummed */
if ((BTRFS_I(src)->flags & BTRFS_INODE_NODATASUM) !=
(BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM))
goto out_fput;
ret = -EISDIR; ret = -EISDIR;
if (S_ISDIR(src->i_mode) || S_ISDIR(inode->i_mode)) if (S_ISDIR(src->i_mode) || S_ISDIR(inode->i_mode))
goto out_fput; goto out_fput;
...@@ -3942,6 +3937,13 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd, ...@@ -3942,6 +3937,13 @@ static noinline long btrfs_ioctl_clone(struct file *file, unsigned long srcfd,
mutex_lock(&src->i_mutex); mutex_lock(&src->i_mutex);
} }
/* don't make the dst file partly checksummed */
if ((BTRFS_I(src)->flags & BTRFS_INODE_NODATASUM) !=
(BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM)) {
ret = -EINVAL;
goto out_unlock;
}
/* determine range to clone */ /* determine range to clone */
ret = -EINVAL; ret = -EINVAL;
if (off + len > src->i_size || off + len < off) if (off + len > src->i_size || off + len < off)
......
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