Skip to content

Commit a4fe134

Browse files
adam900710kdave
authored andcommitted
btrfs: fix a double release on reserved extents in cow_one_range()
[BUG] Commit c28214b ("btrfs: refactor the main loop of cow_file_range()") refactored the handling of COWing one range. However it changed the error handling of the reserved extent. The old cleanup looks like this: out_drop_extent_cache: btrfs_drop_extent_map_range(inode, start, start + cur_alloc_size - 1, false); out_reserve: btrfs_dec_block_group_reservations(fs_info, ins.objectid); btrfs_free_reserved_extent(fs_info, ins.objectid, ins.offset, true); [...] clear_bits = EXTENT_LOCKED | EXTENT_DELALLOC | EXTENT_DELALLOC_NEW | EXTENT_DEFRAG | EXTENT_CLEAR_META_RESV; page_ops = PAGE_UNLOCK | PAGE_START_WRITEBACK | PAGE_END_WRITEBACK; /* * For the range (2). If we reserved an extent for our delalloc range * (or a subrange) and failed to create the respective ordered extent, * then it means that when we reserved the extent we decremented the * extent's size from the data space_info's bytes_may_use counter and * incremented the space_info's bytes_reserved counter by the same * amount. We must make sure extent_clear_unlock_delalloc() does not try * to decrement again the data space_info's bytes_may_use counter, * therefore we do not pass it the flag EXTENT_CLEAR_DATA_RESV. */ if (cur_alloc_size) { extent_clear_unlock_delalloc(inode, start, start + cur_alloc_size - 1, locked_folio, &cached, clear_bits, page_ops); btrfs_qgroup_free_data(inode, NULL, start, cur_alloc_size, NULL); } Which only calls EXTENT_CLEAR_META_RESV. As the reserved extent is properly handled by btrfs_free_reserved_extent(). However the new cleanup is: extent_clear_unlock_delalloc(inode, file_offset, cur_end, locked_folio, cached, EXTENT_LOCKED | EXTENT_DELALLOC | EXTENT_DELALLOC_NEW | EXTENT_DEFRAG | EXTENT_DO_ACCOUNTING, PAGE_UNLOCK | PAGE_START_WRITEBACK | PAGE_END_WRITEBACK); btrfs_qgroup_free_data(inode, NULL, file_offset, cur_len, NULL); btrfs_dec_block_group_reservations(fs_info, ins->objectid); btrfs_free_reserved_extent(fs_info, ins->objectid, ins->offset, true); The flag EXTENT_DO_ACCOUNTING implies both EXTENT_CLEAR_META_RESV and EXTENT_CLEAR_DATA_RESV, which will release the bytes_may_use, which later btrfs_free_reserved_extent() will do again, causing incorrect double release (and may underflow bytes_may_use). [FIX] Use EXTENT_CLEAR_META_RESV to replace EXTENT_DO_ACCOUNTING, and add back the comments on why we only use EXTENT_CLEAR_META_RESV. Fixes: c28214b ("btrfs: refactor the main loop of cow_file_range()") Reported-by: Chris Mason <clm@meta.com> Link: https://lore.kernel.org/linux-btrfs/20260208184920.1102719-1-clm@meta.com/ Reviewed-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Qu Wenruo <wqu@suse.com> Signed-off-by: David Sterba <dsterba@suse.com>
1 parent 2970525 commit a4fe134

1 file changed

Lines changed: 16 additions & 1 deletion

File tree

fs/btrfs/inode.c

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1392,10 +1392,25 @@ static int cow_one_range(struct btrfs_inode *inode, struct folio *locked_folio,
13921392
return ret;
13931393

13941394
free_reserved:
1395+
/*
1396+
* If we have reserved an extent for the current range and failed to
1397+
* create the respective extent map or ordered extent, it means that
1398+
* when we reserved the extent we decremented the extent's size from
1399+
* the data space_info's bytes_may_use counter and
1400+
* incremented the space_info's bytes_reserved counter by the same
1401+
* amount.
1402+
*
1403+
* We must make sure extent_clear_unlock_delalloc() does not try
1404+
* to decrement again the data space_info's bytes_may_use counter, which
1405+
* will be handled by btrfs_free_reserved_extent().
1406+
*
1407+
* Therefore we do not pass it the flag EXTENT_CLEAR_DATA_RESV, but only
1408+
* EXTENT_CLEAR_META_RESV.
1409+
*/
13951410
extent_clear_unlock_delalloc(inode, file_offset, cur_end, locked_folio, cached,
13961411
EXTENT_LOCKED | EXTENT_DELALLOC |
13971412
EXTENT_DELALLOC_NEW |
1398-
EXTENT_DEFRAG | EXTENT_DO_ACCOUNTING,
1413+
EXTENT_DEFRAG | EXTENT_CLEAR_META_RESV,
13991414
PAGE_UNLOCK | PAGE_START_WRITEBACK |
14001415
PAGE_END_WRITEBACK);
14011416
btrfs_qgroup_free_data(inode, NULL, file_offset, cur_len, NULL);

0 commit comments

Comments
 (0)