mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 01:04:41 +01:00
btrfs: fix a double release on reserved extents in cow_one_range()
[BUG] Commitc28214bde6("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:c28214bde6("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>
This commit is contained in:
parent
2970525f78
commit
a4fe134fc1
1 changed files with 16 additions and 1 deletions
|
|
@ -1392,10 +1392,25 @@ static int cow_one_range(struct btrfs_inode *inode, struct folio *locked_folio,
|
|||
return ret;
|
||||
|
||||
free_reserved:
|
||||
/*
|
||||
* If we have reserved an extent for the current range and failed to
|
||||
* create the respective extent map or ordered extent, 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, which
|
||||
* will be handled by btrfs_free_reserved_extent().
|
||||
*
|
||||
* Therefore we do not pass it the flag EXTENT_CLEAR_DATA_RESV, but only
|
||||
* EXTENT_CLEAR_META_RESV.
|
||||
*/
|
||||
extent_clear_unlock_delalloc(inode, file_offset, cur_end, locked_folio, cached,
|
||||
EXTENT_LOCKED | EXTENT_DELALLOC |
|
||||
EXTENT_DELALLOC_NEW |
|
||||
EXTENT_DEFRAG | EXTENT_DO_ACCOUNTING,
|
||||
EXTENT_DEFRAG | EXTENT_CLEAR_META_RESV,
|
||||
PAGE_UNLOCK | PAGE_START_WRITEBACK |
|
||||
PAGE_END_WRITEBACK);
|
||||
btrfs_qgroup_free_data(inode, NULL, file_offset, cur_len, NULL);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue