linux-stable/fs
Filipe Manana 49d640d294 btrfs: fix race when detecting delalloc ranges during fiemap
[ Upstream commit 978b63f746 ]

For fiemap we recently stopped locking the target extent range for the
whole duration of the fiemap call, in order to avoid a deadlock in a
scenario where the fiemap buffer happens to be a memory mapped range of
the same file. This use case is very unlikely to be useful in practice but
it may be triggered by fuzz testing (syzbot, etc).

This however introduced a race that makes us miss delalloc ranges for
file regions that are currently holes, so the caller of fiemap will not
be aware that there's data for some file regions. This can be quite
serious for some use cases - for example in coreutils versions before 9.0,
the cp program used fiemap to detect holes and data in the source file,
copying only regions with data (extents or delalloc) from the source file
to the destination file in order to preserve holes (see the documentation
for its --sparse command line option). This means that if cp was used
with a source file that had delalloc in a hole, the destination file could
end up without that data, which is effectively a data loss issue, if it
happened to hit the race described below.

The race happens like this:

1) Fiemap is called, without the FIEMAP_FLAG_SYNC flag, for a file that
   has delalloc in the file range [64M, 65M[, which is currently a hole;

2) Fiemap locks the inode in shared mode, then starts iterating the
   inode's subvolume tree searching for file extent items, without having
   the whole fiemap target range locked in the inode's io tree - the
   change introduced recently by commit b0ad381fa7 ("btrfs: fix
   deadlock with fiemap and extent locking"). It only locks ranges in
   the io tree when it finds a hole or prealloc extent since that
   commit;

3) Note that fiemap clones each leaf before using it, and this is to
   avoid deadlocks when locking a file range in the inode's io tree and
   the fiemap buffer is memory mapped to some file, because writing
   to the page with btrfs_page_mkwrite() will wait on any ordered extent
   for the page's range and the ordered extent needs to lock the range
   and may need to modify the same leaf, therefore leading to a deadlock
   on the leaf;

4) While iterating the file extent items in the cloned leaf before
   finding the hole in the range [64M, 65M[, the delalloc in that range
   is flushed and its ordered extent completes - meaning the corresponding
   file extent item is in the inode's subvolume tree, but not present in
   the cloned leaf that fiemap is iterating over;

5) When fiemap finds the hole in the [64M, 65M[ range by seeing the gap in
   the cloned leaf (or a file extent item with disk_bytenr == 0 in case
   the NO_HOLES feature is not enabled), it will lock that file range in
   the inode's io tree and then search for delalloc by checking for the
   EXTENT_DELALLOC bit in the io tree for that range and ordered extents
   (with btrfs_find_delalloc_in_range()). But it finds nothing since the
   delalloc in that range was already flushed and the ordered extent
   completed and is gone - as a result fiemap will not report that there's
   delalloc or an extent for the range [64M, 65M[, so user space will be
   mislead into thinking that there's a hole in that range.

This could actually be sporadically triggered with test case generic/094
from fstests, which reports a missing extent/delalloc range like this:

#  generic/094 2s ... - output mismatch (see /home/fdmanana/git/hub/xfstests/results//generic/094.out.bad)
#      --- tests/generic/094.out	2020-06-10 19:29:03.830519425 +0100
#      +++ /home/fdmanana/git/hub/xfstests/results//generic/094.out.bad	2024-02-28 11:00:00.381071525 +0000
#      @@ -1,3 +1,9 @@
#       QA output created by 094
#       fiemap run with sync
#       fiemap run without sync
#      +ERROR: couldn't find extent at 7
#      +map is 'HHDDHPPDPHPH'
#      +logical: [       5..       6] phys:   301517..  301518 flags: 0x800 tot: 2
#      +logical: [       8..       8] phys:   301520..  301520 flags: 0x800 tot: 1
#      ...
#      (Run 'diff -u /home/fdmanana/git/hub/xfstests/tests/generic/094.out /home/fdmanana/git/hub/xfstests/results//generic/094.out.bad'  to see the entire diff)

So in order to fix this, while still avoiding deadlocks in the case where
the fiemap buffer is memory mapped to the same file, change fiemap to work
like the following:

1) Always lock the whole range in the inode's io tree before starting to
   iterate the inode's subvolume tree searching for file extent items,
   just like we did before commit b0ad381fa7 ("btrfs: fix deadlock with
   fiemap and extent locking");

2) Now instead of writing to the fiemap buffer every time we have an extent
   to report, write instead to a temporary buffer (1 page), and when that
   buffer becomes full, stop iterating the file extent items, unlock the
   range in the io tree, release the search path, submit all the entries
   kept in that buffer to the fiemap buffer, and then resume the search
   for file extent items after locking again the remainder of the range in
   the io tree.

   The buffer having a size of a page, allows for 146 entries in a system
   with 4K pages. This is a large enough value to have a good performance
   by avoiding too many restarts of the search for file extent items.
   In other words this preserves the huge performance gains made in the
   last two years to fiemap, while avoiding the deadlocks in case the
   fiemap buffer is memory mapped to the same file (useless in practice,
   but possible and exercised by fuzz testing and syzbot).

Fixes: b0ad381fa7 ("btrfs: fix deadlock with fiemap and extent locking")
Reviewed-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
2024-04-10 16:35:46 +02:00
..
9p 9p: Fix initialisation of netfs_inode for 9p 2024-02-05 20:14:32 +00:00
adfs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
affs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
afs afs: Revert "afs: Hide silly-rename files from userspace" 2024-03-26 18:20:03 -04:00
autofs v6.6-vfs.autofs 2023-08-28 11:39:14 -07:00
befs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
bfs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
btrfs btrfs: fix race when detecting delalloc ranges during fiemap 2024-04-10 16:35:46 +02:00
cachefiles cachefiles: fix memory leak in cachefiles_add_cache() 2024-03-01 13:35:00 +01:00
ceph ceph: stop copying to iter at EOF on sync reads 2024-03-26 18:20:11 -04:00
coda v6.6-vfs.ctime 2023-08-28 09:31:32 -07:00
configfs configfs: convert to ctime accessor functions 2023-07-13 10:28:05 +02:00
cramfs v6.6-vfs.super 2023-08-28 11:04:18 -07:00
crypto
debugfs debugfs: fix automount d_fsdata usage 2024-01-20 11:51:37 +01:00
devpts v6.6-vfs.misc 2023-08-28 10:17:14 -07:00
dlm dlm: fix user space lkb refcounting 2024-04-03 15:28:32 +02:00
ecryptfs ecryptfs: Reject casefold directory inodes 2024-02-05 20:14:17 +00:00
efivarfs efivarfs: Request at most 512 bytes for variable names 2024-03-06 14:48:41 +00:00
efs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
erofs erofs: fix lockdep false positives on initializing erofs_pseudo_mnt 2024-03-26 18:19:40 -04:00
exfat exfat: support handle zero-size directory 2023-11-28 17:19:44 +00:00
exportfs exportfs: remove kernel-doc warnings in exportfs 2023-08-29 17:45:22 -04:00
ext2 quota: Properly annotate i_dquot arrays with __rcu 2024-03-26 18:19:46 -04:00
ext4 ext4: fix corruption during on-line resize 2024-04-03 15:28:28 +02:00
f2fs f2fs: truncate page cache before clearing flags when aborting atomic write 2024-04-03 15:28:33 +02:00
fat fat: fix uninitialized field in nostale filehandles 2024-04-03 15:28:20 +02:00
freevxfs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
fscache netfs, fscache: Prevent Oops in fscache_put_cache() 2024-01-31 16:19:01 -08:00
fuse fuse: don't unhash root 2024-04-03 15:28:25 +02:00
gfs2 gfs2: fix kernel BUG in gfs2_quota_cleanup 2024-01-25 15:35:17 -08:00
hfs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
hfsplus for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
hostfs hostfs: convert to ctime accessor functions 2023-07-24 10:30:00 +02:00
hpfs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
hugetlbfs mm: hugetlb pages should not be reserved by shmat() if SHM_NORESERVE 2024-02-23 09:25:16 +01:00
iomap iomap: clear the per-folio dirty bits on all writeback failures 2024-03-26 18:19:16 -04:00
isofs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
jbd2 jbd2: fix soft lockup in journal_finish_inode_data_buffers() 2024-01-20 11:51:43 +01:00
jffs2 jffs2: convert to ctime accessor functions 2023-07-24 10:30:01 +02:00
jfs quota: Properly annotate i_dquot arrays with __rcu 2024-03-26 18:19:46 -04:00
kernfs fs/kernfs/dir: obey S_ISGID 2024-02-05 20:14:32 +00:00
lockd SUNRPC: Add enum svc_auth_status 2023-08-29 17:45:22 -04:00
minix for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
netfs netfs: Only call folio_start_fscache() one time for each folio 2023-09-18 12:03:46 -07:00
nfs NFS: Read unlock folio on nfs_page_create_from_folio() error 2024-04-03 15:28:29 +02:00
nfs_common
nfsd NFSD: Fix nfsd_clid_class use of __string_len() macro 2024-04-03 15:28:35 +02:00
nilfs2 nilfs2: prevent kernel bug at submit_bh_wbc() 2024-04-03 15:28:34 +02:00
nls nls: Hide new NLS_UCS2_UTILS 2023-08-31 12:07:34 -05:00
notify fanotify: limit reporting of event with non-decodeable file handles 2023-10-19 16:19:20 +02:00
ntfs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
ntfs3 fs/ntfs3: fix build without CONFIG_NTFS3_LZX_XPRESS 2024-03-02 18:23:09 +01:00
ocfs2 quota: Properly annotate i_dquot arrays with __rcu 2024-03-26 18:19:46 -04:00
omfs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
openpromfs openpromfs: convert to ctime accessor functions 2023-07-24 10:30:03 +02:00
orangefs fs: drop the timespec64 argument from update_time 2023-08-11 09:04:57 +02:00
overlayfs ovl: Always reject mounting over case-insensitive directories 2024-03-26 18:19:18 -04:00
proc fs/proc: do_task_stat: move thread_group_cputime_adjusted() outside of lock_task_sighand() 2024-02-23 09:25:17 +01:00
pstore pstore: inode: Only d_invalidate() is needed 2024-03-26 18:19:34 -04:00
qnx4 for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
qnx6 for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
quota quota: Properly annotate i_dquot arrays with __rcu 2024-03-26 18:19:46 -04:00
ramfs ramfs: convert to ctime accessor functions 2023-07-24 10:30:04 +02:00
reiserfs quota: Properly annotate i_dquot arrays with __rcu 2024-03-26 18:19:46 -04:00
romfs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
smb cifs: Fix duplicate fscache cookie warnings 2024-04-10 16:35:43 +02:00
squashfs squashfs: convert to ctime accessor functions 2023-07-24 10:30:05 +02:00
sysfs
sysv for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
tracefs eventfs: Keep all directory links at 1 2024-02-23 09:25:25 +01:00
ubifs ubifs: Set page uptodate in the correct place 2024-04-03 15:28:20 +02:00
udf \n 2023-08-30 12:10:50 -07:00
ufs for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
unicode
vboxsf v6.6-vfs.ctime 2023-08-28 09:31:32 -07:00
verity fsverity: skip PKCS#7 parser when keyring is empty 2023-08-20 10:33:43 -07:00
xfs xfs: remove conditional building of rt geometry validator functions 2024-04-03 15:28:49 +02:00
zonefs zonefs: Improve error handling 2024-02-23 09:25:13 +01:00
aio.c fs/aio: Check IOCB_AIO_RW before the struct aio_kiocb conversion 2024-04-03 15:28:44 +02:00
anon_inodes.c
attr.c v6.6-vfs.misc 2023-08-28 10:17:14 -07:00
bad_inode.c fs: drop the timespec64 argument from update_time 2023-08-11 09:04:57 +02:00
binfmt_elf.c Merge branch 'expand-stack' 2023-06-28 20:35:21 -07:00
binfmt_elf_fdpic.c fs: binfmt_elf_efpic: fix personality for ELF-FDPIC 2023-09-29 17:20:45 -07:00
binfmt_elf_test.c
binfmt_flat.c
binfmt_misc.c fs: convert to ctime accessor functions 2023-07-13 10:28:04 +02:00
binfmt_script.c
buffer.c iomap: add a workaround for racy i_size updates on block devices 2023-09-25 08:55:00 -07:00
char_dev.c
compat_binfmt_elf.c
coredump.c
d_path.c
dax.c mm: convert DAX lock/unlock page to lock/unlock folio 2024-01-10 17:16:53 +01:00
dcache.c fast_dput(): handle underflows gracefully 2024-02-05 20:14:26 +00:00
direct-io.c - Yosry Ahmed brought back some cgroup v1 stats in OOM logs. 2023-06-28 10:28:11 -07:00
drop_caches.c fs: drop_caches: draining pages before dropping caches 2023-08-18 10:12:11 -07:00
eventfd.c eventfd: prevent underflow for eventfd semaphores 2023-07-11 11:41:34 +02:00
eventpoll.c epoll: simplify ep_alloc() 2023-07-26 14:56:07 +02:00
exec.c exec: Fix NOMMU linux_binprm::exec in transfer_args_to_stack() 2024-04-03 15:28:55 +02:00
fcntl.c fs: Fix rw_hint validation 2024-03-26 18:19:17 -04:00
fhandle.c do_sys_name_to_handle(): use kzalloc() to fix kernel-infoleak 2024-03-26 18:19:15 -04:00
file.c v6.6-vfs.misc 2023-08-28 10:17:14 -07:00
file_table.c fs: use __fput_sync in close(2) 2023-08-08 19:36:51 +02:00
filesystems.c
fs-writeback.c writeback, cgroup: switch inodes with dirty timestamps to release dying cgwbs 2023-11-20 11:58:52 +01:00
fs_context.c fs: factor out vfs_parse_monolithic_sep() helper 2023-10-12 18:53:36 +03:00
fs_parser.c
fs_pin.c
fs_struct.c kill do_each_thread() 2023-08-21 13:46:25 -07:00
fs_types.c
fsopen.c fs: add FSCONFIG_CMD_CREATE_EXCL 2023-08-14 18:48:02 +02:00
init.c
inode.c filemap: add a per-mapping stable writes flag 2023-12-03 07:33:03 +01:00
internal.h for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
ioctl.c lsm: new security_file_ioctl_compat() hook 2024-01-31 16:18:54 -08:00
Kconfig for-6.6/block-2023-08-28 2023-08-29 20:21:42 -07:00
Kconfig.binfmt riscv: support the elf-fdpic binfmt loader 2023-08-23 14:17:43 -07:00
kernel_read_file.c fs: Fix kernel-doc warnings 2023-08-19 12:12:12 +02:00
libfs.c fs: new accessor methods for atime and mtime 2024-01-05 15:19:40 +01:00
locks.c NFSD 6.6 Release Notes 2023-08-31 15:32:18 -07:00
Makefile fs: add CONFIG_BUFFER_HEAD 2023-08-02 09:13:09 -06:00
mbcache.c
mnt_idmapping.c
mount.h
mpage.c
namei.c rename(): fix the locking of subdirectories 2024-01-31 16:18:57 -08:00
namespace.c fs: relax mount_setattr() permission checks 2024-02-23 09:25:15 +01:00
nsfs.c fs: convert to ctime accessor functions 2023-07-13 10:28:04 +02:00
open.c cred: get rid of CONFIG_DEBUG_CREDENTIALS 2023-12-20 17:01:51 +01:00
pipe.c pipe: wakeup wr_wait after setting max_usage 2024-01-31 16:19:09 -08:00
pnode.c
pnode.h
posix_acl.c fs: convert to ctime accessor functions 2023-07-13 10:28:04 +02:00
proc_namespace.c
read_write.c fs: Fix one kernel-doc comment 2023-08-15 08:32:45 +02:00
readdir.c vfs: get rid of old '->iterate' directory operation 2023-08-06 15:08:35 +02:00
remap_range.c
select.c fs/select: rework stack allocation hack for clang 2024-03-26 18:19:17 -04:00
seq_file.c
signalfd.c
splice.c - Some swap cleanups from Ma Wupeng ("fix WARN_ON in add_to_avail_list") 2023-08-29 14:25:26 -07:00
stack.c fs: convert to ctime accessor functions 2023-07-13 10:28:04 +02:00
stat.c fs: Pass AT_GETATTR_NOSEC flag to getattr interface function 2023-12-03 07:33:03 +01:00
statfs.c
super.c fs: export sget_dev() 2023-08-31 12:47:15 +02:00
sync.c
sysctls.c
timerfd.c
userfaultfd.c mm: userfaultfd: remove stale comment about core dump locking 2023-08-24 16:20:27 -07:00
utimes.c
xattr.c tmpfs,xattr: GFP_KERNEL_ACCOUNT for simple xattrs 2023-08-22 10:57:46 +02:00