linux/fs
Filipe Manana d4597898ba btrfs: fix race between reflinking and ordered extent completion
While doing a reflink operation, if an ordered extent for a file range
that does not overlap with the source and destination ranges of the
reflink operation happens, we can end up having a failure in the reflink
operation and return -EINVAL to user space.

The following sequence of steps explains how this can happen:

1) We have the page at file offset 315392 dirty (under delalloc);

2) A reflink operation for this file starts, using the same file as both
   source and destination, the source range is [372736, 409600) (length of
   36864 bytes) and the destination range is [208896, 245760);

3) At btrfs_remap_file_range_prep(), we flush all delalloc in the source
   and destination ranges, and wait for any ordered extents in those range
   to complete;

4) Still at btrfs_remap_file_range_prep(), we then flush all delalloc in
   the inode, but we neither wait for it to complete nor any ordered
   extents to complete. This results in starting delalloc for the page at
   file offset 315392 and creating an ordered extent for that single page
   range;

5) We then move to btrfs_clone() and enter the loop to find file extent
   items to copy from the source range to destination range;

6) In the first iteration we end up at last file extent item stored in
   leaf A:

   (...)
   item 131 key (143616 108 315392) itemoff 5101 itemsize 53
            extent data disk bytenr 1903988736 nr 73728
            extent data offset 12288 nr 61440 ram 73728

   This represents the file range [315392, 376832), which overlaps with
   the source range to clone.

   @datal is set to 61440, key.offset is 315392 and @next_key_min_offset
   is therefore set to 376832 (315392 + 61440).

   @off (372736) is > key.offset (315392), so @new_key.offset is set to
   the value of @destoff (208896).

   @new_key.offset == @last_dest_end (208896) so @drop_start is set to
   208896 (@new_key.offset).

   @datal is adjusted to 4096, as @off is > @key.offset.

   So in this iteration we call btrfs_replace_file_extents() for the range
   [208896, 212991] (a single page, which is
   [@drop_start, @new_key.offset + @datal - 1]).

   @last_dest_end is set to 212992 (@new_key.offset + @datal =
   208896 + 4096 = 212992).

   Before the next iteration of the loop, @key.offset is set to the value
   376832, which is @next_key_min_offset;

7) On the second iteration btrfs_search_slot() leaves us again at leaf A,
   but this time pointing beyond the last slot of leaf A, as that's where
   a key with offset 376832 should be at if it existed. So end up calling
   btrfs_next_leaf();

8) btrfs_next_leaf() releases the path, but before it searches again the
   tree for the next key/leaf, the ordered extent for the single page
   range at file offset 315392 completes. That results in trimming the
   file extent item we processed before, adjusting its key offset from
   315392 to 319488, reducing its length from 61440 to 57344 and inserting
   a new file extent item for that single page range, with a key offset of
   315392 and a length of 4096.

   Leaf A now looks like:

     (...)
     item 132 key (143616 108 315392) itemoff 4995 itemsize 53
              extent data disk bytenr 1801666560 nr 4096
              extent data offset 0 nr 4096 ram 4096
     item 133 key (143616 108 319488) itemoff 4942 itemsize 53
              extent data disk bytenr 1903988736 nr 73728
              extent data offset 16384 nr 57344 ram 73728

9) When btrfs_next_leaf() returns, it gives us a path pointing to leaf A
   at slot 133, since it's the first key that follows what was the last
   key we saw (143616 108 315392). In fact it's the same item we processed
   before, but its key offset was changed, so it counts as a new key;

10) So now we have:

    @key.offset == 319488
    @datal == 57344

    @off (372736) is > key.offset (319488), so @new_key.offset is set to
    208896 (@destoff value).

    @new_key.offset (208896) != @last_dest_end (212992), so @drop_start
    is set to 212992 (@last_dest_end value).

    @datal is adjusted to 4096 because @off > @key.offset.

    So in this iteration we call btrfs_replace_file_extents() for the
    invalid range of [212992, 212991] (which is
    [@drop_start, @new_key.offset + @datal - 1]).

    This range is empty, the end offset is smaller than the start offset
    so btrfs_replace_file_extents() returns -EINVAL, which we end up
    returning to user space and fail the reflink operation.

    This all happens because the range of this file extent item was
    already processed in the previous iteration.

This scenario can be triggered very sporadically by fsx from fstests, for
example with test case generic/522.

So fix this by having btrfs_clone() skip file extent items that cover a
file range that we have already processed.

CC: stable@vger.kernel.org # 5.10+
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2022-06-21 14:43:13 +02:00
..
9p Netfs prep for write helpers 2022-03-31 15:49:36 -07:00
adfs Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
affs Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
afs fscache: Remove the cookie parameter from fscache_clear_page_bits() 2022-04-08 23:54:37 +01:00
autofs
befs fs: allocate inode by using alloc_inode_sb() 2022-03-22 15:57:03 -07:00
bfs Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
btrfs btrfs: fix race between reflinking and ordered extent completion 2022-06-21 14:43:13 +02:00
cachefiles cachefiles: Fix KASAN slab-out-of-bounds in cachefiles_set_volume_xattr 2022-04-08 23:32:40 +01:00
ceph ceph: check folio PG_private bit instead of folio->private 2022-05-10 09:48:31 +02:00
cifs cifs: destage any unwritten data to the server before calling copychunk_write 2022-04-20 22:54:54 -05:00
coda Folio changes for 5.18 2022-03-22 17:03:12 -07:00
configfs configfs: fix a race in configfs_{,un}register_subsystem() 2022-02-22 18:30:28 +01:00
cramfs
crypto fs: Remove ->readpages address space operation 2022-04-01 13:45:33 -04:00
debugfs debugfs: Document that debugfs_create functions need not be error checked 2022-02-25 11:56:13 +01:00
devpts fsnotify: fix fsnotify hooks in pseudo filesystems 2022-01-24 14:17:02 +01:00
dlm driver core changes for 5.17-rc1 2022-01-12 11:11:34 -08:00
ecryptfs Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
efivarfs
efs fs: allocate inode by using alloc_inode_sb() 2022-03-22 15:57:03 -07:00
erofs iomap: add per-iomap_iter private data 2022-05-16 17:17:32 +02:00
exfat Description for this pull request: 2022-04-01 14:20:24 -07:00
exportfs
ext2 \n 2022-03-25 17:38:15 -07:00
ext4 iomap: add per-iomap_iter private data 2022-05-16 17:17:32 +02:00
f2fs iomap: add per-iomap_iter private data 2022-05-16 17:17:32 +02:00
fat Merge branch 'akpm' (patches from Andrew) 2022-03-24 14:14:07 -07:00
freevxfs fs: allocate inode by using alloc_inode_sb() 2022-03-22 15:57:03 -07:00
fscache fscache: remove FSCACHE_OLD_API Kconfig option 2022-04-08 23:54:37 +01:00
fuse fs: Remove ->readpages address space operation 2022-04-01 13:45:33 -04:00
gfs2 iomap: add per-iomap_iter private data 2022-05-16 17:17:32 +02:00
hfs Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
hfsplus Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
hostfs Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
hpfs Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
hugetlbfs mm, hugetlb: allow for "high" userspace addresses 2022-04-21 20:01:09 -07:00
iomap iomap: add per-iomap_iter private data 2022-05-16 17:17:32 +02:00
isofs fs: allocate inode by using alloc_inode_sb() 2022-03-22 15:57:03 -07:00
jbd2 Fix some syzbot-detected bugs, as well as other bugs found by I/O 2022-04-22 18:18:27 -07:00
jffs2 This pull request contains fixes for JFFS2, UBI and UBIFS 2022-03-31 16:09:41 -07:00
jfs A couple bug fixes 2022-03-29 18:17:30 -07:00
kernfs kernfs: fix NULL dereferencing in kernfs_remove 2022-04-27 19:32:07 +02:00
ksmbd ksmbd: set fixed sector size to FS_SECTOR_SIZE_INFORMATION 2022-04-14 20:56:13 -05:00
lockd NFSD: Move svc_serv_ops::svo_function into struct svc_serv 2022-02-28 10:26:40 -05:00
minix Merge branch 'akpm' (patches from Andrew) 2022-03-24 14:14:07 -07:00
netfs netfs: Split some core bits out into their own file 2022-03-18 09:29:05 +00:00
nfs nfs: fix broken handling of the softreval mount option 2022-05-09 13:02:54 -04:00
nfs_common
nfsd NFSD bug fixes for 5.18-rc: 2022-04-12 14:23:19 -10:00
nilfs2 nilfs2: get rid of nilfs_mapping_init() 2022-04-01 11:46:09 -07:00
nls
notify fanotify: do not allow setting dirent events in mask of non-dir 2022-05-09 11:49:09 +02:00
ntfs ntfs: Correct mark_ntfs_record_dirty() folio conversion 2022-04-01 14:40:44 -04:00
ntfs3 Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
ocfs2 ocfs2: fix crash when mount with quota enabled 2022-04-01 11:46:09 -07:00
omfs fs: Convert __set_page_dirty_buffers to block_dirty_folio 2022-03-16 13:37:04 -04:00
openpromfs fs: allocate inode by using alloc_inode_sb() 2022-03-22 15:57:03 -07:00
orangefs Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
overlayfs fs: allocate inode by using alloc_inode_sb() 2022-03-22 15:57:03 -07:00
proc procfs: prevent unprivileged processes accessing fdinfo dir 2022-05-09 17:34:28 -07:00
pstore pstore: Don't use semaphores in always-atomic-context code 2022-03-15 11:08:23 -07:00
qnx4 fs: allocate inode by using alloc_inode_sb() 2022-03-22 15:57:03 -07:00
qnx6 fs: allocate inode by using alloc_inode_sb() 2022-03-22 15:57:03 -07:00
quota quota: make dquot_quota_sync return errors from ->sync_fs 2022-01-30 08:59:47 -08:00
ramfs
reiserfs \n 2022-03-25 17:38:15 -07:00
romfs fs: allocate inode by using alloc_inode_sb() 2022-03-22 15:57:03 -07:00
smbfs_common smb3: fix ksmbd bigendian bug in oplock break, and move its struct to smbfs_common 2022-03-31 09:38:53 -05:00
squashfs Merge branch 'akpm' (patches from Andrew) 2022-03-22 16:11:53 -07:00
sysfs kobject: kobj_type: remove default_attrs 2022-04-05 15:39:19 +02:00
sysv Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
tracefs tracefs: Set the group ownership in apply_options() not parse_options() 2022-02-25 21:05:04 -05:00
ubifs This pull request contains fixes for JFFS2, UBI and UBIFS 2022-03-31 16:09:41 -07:00
udf udf: Avoid using stale lengthOfImpUse 2022-05-10 13:30:32 +02:00
ufs Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
unicode kbuild: unify cmd_copy and cmd_shipped 2022-02-14 10:37:32 +09:00
vboxsf Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
verity fs: Remove ->readpages address space operation 2022-04-01 13:45:33 -04:00
xfs iomap: add per-iomap_iter private data 2022-05-16 17:17:32 +02:00
zonefs iomap: add per-iomap_iter private data 2022-05-16 17:17:32 +02:00
aio.c Merge branch 'work.misc' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs 2022-04-01 19:57:03 -07:00
anon_inodes.c
attr.c
bad_inode.c
binfmt_aout.c
binfmt_elf_fdpic.c coredump: Snapshot the vmas in do_coredump 2022-03-08 12:55:29 -06:00
binfmt_elf_test.c binfmt_elf: Introduce KUnit test 2022-03-03 20:38:56 -08:00
binfmt_elf.c revert "fs/binfmt_elf: use PT_LOAD p_align values for static PIE" 2022-04-15 14:49:56 -07:00
binfmt_flat.c coredump: Don't compile flat_core_dump when coredumps are disabled 2022-03-09 10:37:07 -06:00
binfmt_misc.c Fix regression due to "fs: move binfmt_misc sysctl to its own file" 2022-02-09 09:50:02 -08:00
binfmt_script.c
buffer.c filemap: Remove AOP_FLAG_CONT_EXPAND 2022-04-01 14:40:44 -04:00
char_dev.c
compat_binfmt_elf.c binfmt_elf: Introduce KUnit test 2022-03-03 20:38:56 -08:00
coredump.c ptrace: Cleanups for v5.18 2022-03-28 17:29:53 -07:00
d_path.c
dax.c dax for 5.18 2022-03-24 18:12:09 -07:00
dcache.c mm: dcache: use kmem_cache_alloc_lru() to allocate dentry 2022-03-22 15:57:03 -07:00
direct-io.c block: remove the per-bio/request write hint 2022-03-07 12:45:57 -07:00
drop_caches.c
eventfd.c
eventpoll.c eventpoll: simplify sysctl declaration with register_sysctl() 2022-01-22 08:33:35 +02:00
exec.c ptrace: Cleanups for v5.18 2022-03-28 17:29:53 -07:00
fcntl.c fs: remove fs.f_write_hint 2022-03-08 17:55:03 -07:00
fhandle.c
file_table.c SUNRPC: Ensure we flush any closed sockets before xs_xprt_free() 2022-04-07 16:19:47 -04:00
file.c fs: fix fd table size alignment properly 2022-03-29 23:29:18 -07:00
filesystems.c
fs_context.c vfs: fs_context: fix up param length parsing in legacy_parse_param 2022-01-18 09:23:19 +02:00
fs_parser.c
fs_pin.c
fs_struct.c
fs_types.c
fs-writeback.c writeback: Avoid skipping inode writeback 2022-05-10 12:04:21 +02:00
fsopen.c
init.c
inode.c fs: introduce alloc_inode_sb() to allocate filesystems specific inode 2022-03-22 15:57:03 -07:00
internal.h Merge branch 'work.misc' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs 2022-04-01 19:57:03 -07:00
io_uring.c io_uring: assign non-fixed early for async work 2022-05-02 08:09:39 -06:00
io-wq.c ptrace: Cleanups for v5.18 2022-03-28 17:29:53 -07:00
io-wq.h io_uring: stop using io_wq_work as an fd placeholder 2022-04-11 17:06:20 -06:00
ioctl.c Fixes for 5.18-rc1: 2022-04-01 19:35:56 -07:00
Kconfig Folio changes for 5.18 2022-03-22 17:03:12 -07:00
Kconfig.binfmt execve updates for v5.18-rc1 2022-03-21 19:16:02 -07:00
kernel_read_file.c
libfs.c fs: Convert __set_page_dirty_no_writeback to noop_dirty_folio 2022-03-16 13:37:05 -04:00
locks.c fs: move locking sysctls where they are used 2022-01-22 08:33:36 +02:00
Makefile Fix from Christoph Hellwig merging the CONFIG_UNICODE_UTF8_DATA into the 2022-02-01 11:13:24 -08:00
mbcache.c
mount.h
mpage.c for-5.18/alloc-cleanups-2022-03-25 2022-03-26 11:59:30 -07:00
namei.c VFS: filename_create(): fix incorrect intent. 2022-04-14 15:53:43 -07:00
namespace.c fs: unset MNT_WRITE_HOLD on failure 2022-04-21 17:57:37 +02:00
no-block.c
nsfs.c
open.c fs: remove fs.f_write_hint 2022-03-08 17:55:03 -07:00
pipe.c Revert "fs/pipe: use kvcalloc to allocate a pipe_buffer array" 2022-04-20 12:07:53 -07:00
pnode.c
pnode.h
posix_acl.c fs: fix acl translation 2022-04-19 10:19:02 -07:00
proc_namespace.c
read_write.c Merge branch 'work.misc' of git://git.kernel.org/pub/scm/linux/kernel/git/viro/vfs 2022-04-01 19:57:03 -07:00
readdir.c
remap_range.c Filesystem folio changes for 5.18 2022-03-22 18:26:56 -07:00
select.c select: Fix indefinitely sleeping task in poll_schedule_timeout() 2022-01-11 09:03:05 -08:00
seq_file.c seq_file: fix NULL pointer arithmetic warning 2022-02-01 11:31:55 -05:00
signalfd.c Merge branch 'signal-for-v5.17' of git://git.kernel.org/pub/scm/linux/kernel/git/ebiederm/user-namespace 2022-01-17 05:49:30 +02:00
splice.c mm: Convert remove_mapping() to take a folio 2022-03-21 12:59:01 -04:00
stack.c
stat.c stat: fix inconsistency between struct stat and struct compat_stat 2022-04-12 13:35:08 -10:00
statfs.c
super.c vfs: make freeze_super abort when sync_filesystem returns error 2022-01-30 08:59:47 -08:00
sync.c vfs: make sync_filesystem return errors from ->sync_fs 2022-01-30 08:59:47 -08:00
sysctls.c fs: move namespace sysctls and declare fs base directory 2022-01-22 08:33:36 +02:00
timerfd.c
userfaultfd.c userfaultfd: provide unmasked address on page-fault 2022-03-22 15:57:08 -07:00
utimes.c
xattr.c fs: fix acl translation 2022-04-19 10:19:02 -07:00