Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

btrfs: fix a double release on reserved extents in cow_one_range()

[BUG]
Commit c28214bde6da ("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: c28214bde6da ("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>

authored by

Qu Wenruo and committed by
David Sterba
a4fe134f 2970525f

+16 -1
+16 -1
fs/btrfs/inode.c
··· 1392 1392 return ret; 1393 1393 1394 1394 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 + */ 1395 1410 extent_clear_unlock_delalloc(inode, file_offset, cur_end, locked_folio, cached, 1396 1411 EXTENT_LOCKED | EXTENT_DELALLOC | 1397 1412 EXTENT_DELALLOC_NEW | 1398 - EXTENT_DEFRAG | EXTENT_DO_ACCOUNTING, 1413 + EXTENT_DEFRAG | EXTENT_CLEAR_META_RESV, 1399 1414 PAGE_UNLOCK | PAGE_START_WRITEBACK | 1400 1415 PAGE_END_WRITEBACK); 1401 1416 btrfs_qgroup_free_data(inode, NULL, file_offset, cur_len, NULL);