Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

Btrfs: fix clone vs chattr NODATASUM race

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: 0e7b824c4ef9 ("Btrfs: don't make a file partly checksummed through file clone")
CC: stable@vger.kernel.org # 4.4+
Signed-off-by: Omar Sandoval <osandov@fb.com>
Reviewed-by: Nikolay Borisov <nborisov@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
[ update changelog ]
Signed-off-by: David Sterba <dsterba@suse.com>

authored by

Omar Sandoval and committed by
David Sterba
b5c40d59 b89311ef

+7 -5
+7 -5
fs/btrfs/ioctl.c
··· 3808 3808 src->i_sb != inode->i_sb) 3809 3809 return -EXDEV; 3810 3810 3811 - /* don't make the dst file partly checksummed */ 3812 - if ((BTRFS_I(src)->flags & BTRFS_INODE_NODATASUM) != 3813 - (BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM)) 3814 - return -EINVAL; 3815 - 3816 3811 if (S_ISDIR(src->i_mode) || S_ISDIR(inode->i_mode)) 3817 3812 return -EISDIR; 3818 3813 ··· 3815 3820 btrfs_double_inode_lock(src, inode); 3816 3821 } else { 3817 3822 inode_lock(src); 3823 + } 3824 + 3825 + /* don't make the dst file partly checksummed */ 3826 + if ((BTRFS_I(src)->flags & BTRFS_INODE_NODATASUM) != 3827 + (BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM)) { 3828 + ret = -EINVAL; 3829 + goto out_unlock; 3818 3830 } 3819 3831 3820 3832 /* determine range to clone */