linux/fs/btrfs
Omar Sandoval 9d274c19a7 btrfs: fix crash on racing fsync and size-extending write into prealloc
We have been seeing crashes on duplicate keys in
btrfs_set_item_key_safe():

  BTRFS critical (device vdb): slot 4 key (450 108 8192) new key (450 108 8192)
  ------------[ cut here ]------------
  kernel BUG at fs/btrfs/ctree.c:2620!
  invalid opcode: 0000 [#1] PREEMPT SMP PTI
  CPU: 0 PID: 3139 Comm: xfs_io Kdump: loaded Not tainted 6.9.0 #6
  Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-2.fc40 04/01/2014
  RIP: 0010:btrfs_set_item_key_safe+0x11f/0x290 [btrfs]

With the following stack trace:

  #0  btrfs_set_item_key_safe (fs/btrfs/ctree.c:2620:4)
  #1  btrfs_drop_extents (fs/btrfs/file.c:411:4)
  #2  log_one_extent (fs/btrfs/tree-log.c:4732:9)
  #3  btrfs_log_changed_extents (fs/btrfs/tree-log.c:4955:9)
  #4  btrfs_log_inode (fs/btrfs/tree-log.c:6626:9)
  #5  btrfs_log_inode_parent (fs/btrfs/tree-log.c:7070:8)
  #6  btrfs_log_dentry_safe (fs/btrfs/tree-log.c:7171:8)
  #7  btrfs_sync_file (fs/btrfs/file.c:1933:8)
  #8  vfs_fsync_range (fs/sync.c:188:9)
  #9  vfs_fsync (fs/sync.c:202:9)
  #10 do_fsync (fs/sync.c:212:9)
  #11 __do_sys_fdatasync (fs/sync.c:225:9)
  #12 __se_sys_fdatasync (fs/sync.c:223:1)
  #13 __x64_sys_fdatasync (fs/sync.c:223:1)
  #14 do_syscall_x64 (arch/x86/entry/common.c:52:14)
  #15 do_syscall_64 (arch/x86/entry/common.c:83:7)
  #16 entry_SYSCALL_64+0xaf/0x14c (arch/x86/entry/entry_64.S:121)

So we're logging a changed extent from fsync, which is splitting an
extent in the log tree. But this split part already exists in the tree,
triggering the BUG().

This is the state of the log tree at the time of the crash, dumped with
drgn (https://github.com/osandov/drgn/blob/main/contrib/btrfs_tree.py)
to get more details than btrfs_print_leaf() gives us:

  >>> print_extent_buffer(prog.crashed_thread().stack_trace()[0]["eb"])
  leaf 33439744 level 0 items 72 generation 9 owner 18446744073709551610
  leaf 33439744 flags 0x100000000000000
  fs uuid e5bd3946-400c-4223-8923-190ef1f18677
  chunk uuid d58cb17e-6d02-494a-829a-18b7d8a399da
          item 0 key (450 INODE_ITEM 0) itemoff 16123 itemsize 160
                  generation 7 transid 9 size 8192 nbytes 8473563889606862198
                  block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0
                  sequence 204 flags 0x10(PREALLOC)
                  atime 1716417703.220000000 (2024-05-22 15:41:43)
                  ctime 1716417704.983333333 (2024-05-22 15:41:44)
                  mtime 1716417704.983333333 (2024-05-22 15:41:44)
                  otime 17592186044416.000000000 (559444-03-08 01:40:16)
          item 1 key (450 INODE_REF 256) itemoff 16110 itemsize 13
                  index 195 namelen 3 name: 193
          item 2 key (450 XATTR_ITEM 1640047104) itemoff 16073 itemsize 37
                  location key (0 UNKNOWN.0 0) type XATTR
                  transid 7 data_len 1 name_len 6
                  name: user.a
                  data a
          item 3 key (450 EXTENT_DATA 0) itemoff 16020 itemsize 53
                  generation 9 type 1 (regular)
                  extent data disk byte 303144960 nr 12288
                  extent data offset 0 nr 4096 ram 12288
                  extent compression 0 (none)
          item 4 key (450 EXTENT_DATA 4096) itemoff 15967 itemsize 53
                  generation 9 type 2 (prealloc)
                  prealloc data disk byte 303144960 nr 12288
                  prealloc data offset 4096 nr 8192
          item 5 key (450 EXTENT_DATA 8192) itemoff 15914 itemsize 53
                  generation 9 type 2 (prealloc)
                  prealloc data disk byte 303144960 nr 12288
                  prealloc data offset 8192 nr 4096
  ...

So the real problem happened earlier: notice that items 4 (4k-12k) and 5
(8k-12k) overlap. Both are prealloc extents. Item 4 straddles i_size and
item 5 starts at i_size.

Here is the state of the filesystem tree at the time of the crash:

  >>> root = prog.crashed_thread().stack_trace()[2]["inode"].root
  >>> ret, nodes, slots = btrfs_search_slot(root, BtrfsKey(450, 0, 0))
  >>> print_extent_buffer(nodes[0])
  leaf 30425088 level 0 items 184 generation 9 owner 5
  leaf 30425088 flags 0x100000000000000
  fs uuid e5bd3946-400c-4223-8923-190ef1f18677
  chunk uuid d58cb17e-6d02-494a-829a-18b7d8a399da
  	...
          item 179 key (450 INODE_ITEM 0) itemoff 4907 itemsize 160
                  generation 7 transid 7 size 4096 nbytes 12288
                  block group 0 mode 100600 links 1 uid 0 gid 0 rdev 0
                  sequence 6 flags 0x10(PREALLOC)
                  atime 1716417703.220000000 (2024-05-22 15:41:43)
                  ctime 1716417703.220000000 (2024-05-22 15:41:43)
                  mtime 1716417703.220000000 (2024-05-22 15:41:43)
                  otime 1716417703.220000000 (2024-05-22 15:41:43)
          item 180 key (450 INODE_REF 256) itemoff 4894 itemsize 13
                  index 195 namelen 3 name: 193
          item 181 key (450 XATTR_ITEM 1640047104) itemoff 4857 itemsize 37
                  location key (0 UNKNOWN.0 0) type XATTR
                  transid 7 data_len 1 name_len 6
                  name: user.a
                  data a
          item 182 key (450 EXTENT_DATA 0) itemoff 4804 itemsize 53
                  generation 9 type 1 (regular)
                  extent data disk byte 303144960 nr 12288
                  extent data offset 0 nr 8192 ram 12288
                  extent compression 0 (none)
          item 183 key (450 EXTENT_DATA 8192) itemoff 4751 itemsize 53
                  generation 9 type 2 (prealloc)
                  prealloc data disk byte 303144960 nr 12288
                  prealloc data offset 8192 nr 4096

Item 5 in the log tree corresponds to item 183 in the filesystem tree,
but nothing matches item 4. Furthermore, item 183 is the last item in
the leaf.

btrfs_log_prealloc_extents() is responsible for logging prealloc extents
beyond i_size. It first truncates any previously logged prealloc extents
that start beyond i_size. Then, it walks the filesystem tree and copies
the prealloc extent items to the log tree.

If it hits the end of a leaf, then it calls btrfs_next_leaf(), which
unlocks the tree and does another search. However, while the filesystem
tree is unlocked, an ordered extent completion may modify the tree. In
particular, it may insert an extent item that overlaps with an extent
item that was already copied to the log tree.

This may manifest in several ways depending on the exact scenario,
including an EEXIST error that is silently translated to a full sync,
overlapping items in the log tree, or this crash. This particular crash
is triggered by the following sequence of events:

- Initially, the file has i_size=4k, a regular extent from 0-4k, and a
  prealloc extent beyond i_size from 4k-12k. The prealloc extent item is
  the last item in its B-tree leaf.
- The file is fsync'd, which copies its inode item and both extent items
  to the log tree.
- An xattr is set on the file, which sets the
  BTRFS_INODE_COPY_EVERYTHING flag.
- The range 4k-8k in the file is written using direct I/O. i_size is
  extended to 8k, but the ordered extent is still in flight.
- The file is fsync'd. Since BTRFS_INODE_COPY_EVERYTHING is set, this
  calls copy_inode_items_to_log(), which calls
  btrfs_log_prealloc_extents().
- btrfs_log_prealloc_extents() finds the 4k-12k prealloc extent in the
  filesystem tree. Since it starts before i_size, it skips it. Since it
  is the last item in its B-tree leaf, it calls btrfs_next_leaf().
- btrfs_next_leaf() unlocks the path.
- The ordered extent completion runs, which converts the 4k-8k part of
  the prealloc extent to written and inserts the remaining prealloc part
  from 8k-12k.
- btrfs_next_leaf() does a search and finds the new prealloc extent
  8k-12k.
- btrfs_log_prealloc_extents() copies the 8k-12k prealloc extent into
  the log tree. Note that it overlaps with the 4k-12k prealloc extent
  that was copied to the log tree by the first fsync.
- fsync calls btrfs_log_changed_extents(), which tries to log the 4k-8k
  extent that was written.
- This tries to drop the range 4k-8k in the log tree, which requires
  adjusting the start of the 4k-12k prealloc extent in the log tree to
  8k.
- btrfs_set_item_key_safe() sees that there is already an extent
  starting at 8k in the log tree and calls BUG().

Fix this by detecting when we're about to insert an overlapping file
extent item in the log tree and truncating the part that would overlap.

CC: stable@vger.kernel.org # 6.1+
Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Omar Sandoval <osandov@fb.com>
Signed-off-by: David Sterba <dsterba@suse.com>
2024-06-05 18:06:30 +02:00
..
tests btrfs: use btrfs_is_testing() everywhere 2024-05-07 21:31:07 +02:00
accessors.c btrfs: remove unused included headers 2024-03-04 16:24:46 +01:00
accessors.h btrfs: move balance args conversion helpers to volumes.c 2024-03-04 16:24:52 +01:00
acl.c btrfs: remove unused included headers 2024-03-04 16:24:46 +01:00
acl.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
async-thread.c btrfs: remove unused included headers 2024-03-04 16:24:46 +01:00
async-thread.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
backref.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
backref.h btrfs: uninline some static inline helpers from backref.h 2024-03-04 16:24:53 +01:00
bio.c btrfs: introduce offload_csum_mode to tweak checksum offloading behavior 2024-03-04 16:24:52 +01:00
bio.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
block-group.c btrfs: zoned: don't skip block groups with 100% zone unusable 2024-03-26 16:42:39 +01:00
block-group.h btrfs: mark btrfs_put_caching_control() static 2024-03-05 17:13:23 +01:00
block-rsv.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
block-rsv.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
btrfs_inode.h btrfs: ensure fast fsync waits for ordered extents after a write failure 2024-05-28 16:35:12 +02:00
compression.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
compression.h btrfs: compression: migrate compression/decompression paths to folios 2024-05-07 21:31:02 +02:00
ctree.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
ctree.h btrfs: add forward declarations and headers, part 3 2024-03-04 16:24:49 +01:00
defrag.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
defrag.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
delalloc-space.c btrfs: remove unused included headers 2024-03-04 16:24:46 +01:00
delalloc-space.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
delayed-inode.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
delayed-inode.h btrfs: uninline btrfs_init_delayed_root() 2024-03-04 16:24:53 +01:00
delayed-ref.c btrfs: replace btrfs_delayed_*_ref with btrfs_*_ref 2024-05-07 21:31:05 +02:00
delayed-ref.h btrfs: replace btrfs_delayed_*_ref with btrfs_*_ref 2024-05-07 21:31:05 +02:00
dev-replace.c for-6.9-tag 2024-03-12 12:28:34 -07:00
dev-replace.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
dir-item.c btrfs: abort transaction on generation mismatch when marking eb as dirty 2023-10-12 16:44:07 +02:00
dir-item.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
discard.c btrfs: unexport btrfs_run_discard_work and make it static 2023-06-19 13:59:25 +02:00
discard.h btrfs: unexport btrfs_run_discard_work and make it static 2023-06-19 13:59:25 +02:00
disk-io.c btrfs: count super block write errors in device instead of tracking folio error state 2024-05-07 21:31:11 +02:00
disk-io.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
export.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
export.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
extent_io.c btrfs: count super block write errors in device instead of tracking folio error state 2024-05-07 21:31:11 +02:00
extent_io.h btrfs: add a cached state to extent_clear_unlock_delalloc 2024-05-07 21:31:10 +02:00
extent_map.c btrfs: add tracepoints for extent map shrinker events 2024-05-07 21:31:07 +02:00
extent_map.h btrfs: add extra comments on extent_map members 2024-05-07 21:31:08 +02:00
extent-io-tree.c btrfs: rename err to ret in convert_extent_bit() 2024-05-07 21:31:01 +02:00
extent-io-tree.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
extent-tree.c btrfs: fix end of tree detection when searching for data extent ref 2024-05-15 17:57:39 +02:00
extent-tree.h btrfs: add forward declarations and headers, part 3 2024-03-04 16:24:49 +01:00
file-item.c btrfs: simplify the inline extent map creation 2024-05-07 21:31:08 +02:00
file-item.h btrfs: remove search_commit parameter from btrfs_lookup_csums_list() 2024-05-07 21:31:03 +02:00
file.c btrfs: ensure fast fsync waits for ordered extents after a write failure 2024-05-28 16:35:12 +02:00
file.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
free-space-cache.c btrfs: remove SLAB_MEM_SPREAD flag use 2024-03-05 17:13:23 +01:00
free-space-cache.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
free-space-tree.c btrfs: move transaction abort to the error site btrfs_rebuild_free_space_tree() 2024-03-04 16:24:48 +01:00
free-space-tree.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
fs.c btrfs: sysfs: update fs features directory asynchronously 2023-02-13 17:50:35 +01:00
fs.h btrfs: remove duplicate included header from fs.h 2024-05-07 21:31:10 +02:00
inode-item.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
inode-item.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
inode.c btrfs: add a cached state to extent_clear_unlock_delalloc 2024-05-07 21:31:10 +02:00
ioctl.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
ioctl.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
Kconfig btrfs: check-integrity: remove CONFIG_BTRFS_FS_CHECK_INTEGRITY option 2023-10-12 16:44:05 +02:00
locking.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
locking.h btrfs: locking: rename __btrfs_tree_lock() and __btrfs_tree_read_lock() 2024-05-07 21:31:00 +02:00
lru_cache.c btrfs: fix typos found by codespell 2023-12-15 23:00:04 +01:00
lru_cache.h btrfs: open code trivial btrfs_lru_cache_size() 2024-03-04 16:24:53 +01:00
lzo.c btrfs: compression: migrate compression/decompression paths to folios 2024-05-07 21:31:02 +02:00
Makefile btrfs: add support for inserting raid stripe extents 2023-10-12 16:44:09 +02:00
messages.c btrfs: remove colon from messages with state 2024-04-18 01:46:35 +02:00
messages.h btrfs: constify fs_info parameter in __btrfs_panic() 2023-12-15 20:27:02 +01:00
misc.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
ordered-data.c btrfs: ensure fast fsync waits for ordered extents after a write failure 2024-05-28 16:35:12 +02:00
ordered-data.h btrfs: handle errors in btrfs_reloc_clone_csums properly 2024-05-07 21:31:09 +02:00
orphan.c btrfs: remove unused included headers 2024-03-04 16:24:46 +01:00
orphan.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
print-tree.c btrfs: new inline ref storing owning subvol of data extents 2023-10-12 16:44:11 +02:00
print-tree.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
props.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
props.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
qgroup.c btrfs: qgroup: fix qgroup id collision across mounts 2024-05-15 17:57:09 +02:00
qgroup.h btrfs: qgroup: validate btrfs_qgroup_inherit parameter 2024-03-05 17:13:24 +01:00
raid56.c btrfs: raid56: extra debugging for raid6 syndrome generation 2024-03-04 16:24:52 +01:00
raid56.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
raid-stripe-tree.c btrfs: remove unused included headers 2024-03-04 16:24:46 +01:00
raid-stripe-tree.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
rcu-string.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
ref-verify.c btrfs: rename btrfs_data_ref->ino to ->objectid 2024-05-07 21:31:05 +02:00
ref-verify.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
reflink.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
reflink.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
relocation.c btrfs: handle errors in btrfs_reloc_clone_csums properly 2024-05-07 21:31:09 +02:00
relocation.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
root-tree.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
root-tree.h btrfs: qgroup: fix qgroup prealloc rsv leak in subvolume operations 2024-04-02 19:18:23 +02:00
scrub.c btrfs: scrub: initialize ret in scrub_simple_mirror() to fix compilation warning 2024-05-15 17:57:32 +02:00
scrub.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
send.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
send.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
space-info.c btrfs: remove unused included headers 2024-03-04 16:24:46 +01:00
space-info.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
subpage.c btrfs: subpage: make writer lock utilize bitmap 2024-03-05 17:13:23 +01:00
subpage.h btrfs: subpage: make reader lock utilize bitmap 2024-03-05 17:13:23 +01:00
super.c btrfs: re-introduce 'norecovery' mount option 2024-05-21 15:27:17 +02:00
super.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
sysfs.c btrfs: use btrfs_is_testing() everywhere 2024-05-07 21:31:07 +02:00
sysfs.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
transaction.c btrfs: rename werr and err to ret in __btrfs_wait_marked_extents() 2024-05-07 21:31:08 +02:00
transaction.h btrfs: remove no longer used btrfs_transaction_in_commit() 2024-03-04 16:24:52 +01:00
tree-checker.c btrfs: use btrfs_is_testing() everywhere 2024-05-07 21:31:07 +02:00
tree-checker.h btrfs: make sure that WRITTEN is set on all metadata blocks 2024-05-02 22:11:13 +02:00
tree-log.c btrfs: fix crash on racing fsync and size-extending write into prealloc 2024-06-05 18:06:30 +02:00
tree-log.h btrfs: uninline some static inline helpers from tree-log.h 2024-03-04 16:24:53 +01:00
tree-mod-log.c btrfs: change root->root_key.objectid to btrfs_root_id() 2024-05-07 21:31:06 +02:00
tree-mod-log.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
ulist.c btrfs: remove unused included headers 2024-03-04 16:24:46 +01:00
ulist.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
uuid-tree.c btrfs: unify handling of return values of btrfs_insert_empty_items() 2024-03-04 16:24:48 +01:00
uuid-tree.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
verity.c btrfs: remove unused included headers 2024-03-04 16:24:46 +01:00
verity.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
volumes.c btrfs: remove no longer used btrfs_clone_chunk_map() 2024-05-07 21:31:03 +02:00
volumes.h btrfs: count super block write errors in device instead of tracking folio error state 2024-05-07 21:31:11 +02:00
xattr.c btrfs: rename err to ret in btrfs_initxattrs() 2024-05-07 21:31:00 +02:00
xattr.h btrfs: add forward declarations and headers, part 1 2024-03-04 16:24:49 +01:00
zlib.c btrfs: compression: migrate compression/decompression paths to folios 2024-05-07 21:31:02 +02:00
zoned.c btrfs: zoned: fix use-after-free due to race with dev replace 2024-05-15 17:57:25 +02:00
zoned.h btrfs: add forward declarations and headers, part 2 2024-03-04 16:24:49 +01:00
zstd.c btrfs: compression: migrate compression/decompression paths to folios 2024-05-07 21:31:02 +02:00