| From b5c40d598f5408bd0ca22dfffa82f03cd9433f23 Mon Sep 17 00:00:00 2001 |
| From: Omar Sandoval <osandov@fb.com> |
| Date: Tue, 22 May 2018 15:02:12 -0700 |
| Subject: Btrfs: fix clone vs chattr NODATASUM race |
| |
| From: Omar Sandoval <osandov@fb.com> |
| |
| commit b5c40d598f5408bd0ca22dfffa82f03cd9433f23 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: 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> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/btrfs/ioctl.c | 12 +++++++----- |
| 1 file changed, 7 insertions(+), 5 deletions(-) |
| |
| --- a/fs/btrfs/ioctl.c |
| +++ b/fs/btrfs/ioctl.c |
| @@ -3837,11 +3837,6 @@ static noinline int btrfs_clone_files(st |
| src->i_sb != inode->i_sb) |
| return -EXDEV; |
| |
| - /* don't make the dst file partly checksummed */ |
| - if ((BTRFS_I(src)->flags & BTRFS_INODE_NODATASUM) != |
| - (BTRFS_I(inode)->flags & BTRFS_INODE_NODATASUM)) |
| - return -EINVAL; |
| - |
| if (S_ISDIR(src->i_mode) || S_ISDIR(inode->i_mode)) |
| return -EISDIR; |
| |
| @@ -3851,6 +3846,13 @@ static noinline int btrfs_clone_files(st |
| inode_lock(src); |
| } |
| |
| + /* 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 */ |
| ret = -EINVAL; |
| if (off + len > src->i_size || off + len < off) |