]> git.ipfire.org Git - thirdparty/kernel/linux.git/commit
btrfs: fix a double release on reserved extents in cow_one_range()
authorQu Wenruo <wqu@suse.com>
Mon, 9 Feb 2026 03:31:45 +0000 (14:01 +1030)
committerDavid Sterba <dsterba@suse.com>
Thu, 26 Feb 2026 14:03:26 +0000 (15:03 +0100)
commita4fe134fc1d8eb7dcd07e0961c5711b443decd89
treecdd0bc18b9d5c84f201f947c26714773b8a7d94c
parent2970525f789c080e7e82ecb09cd85a8bb1d284e4
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>
fs/btrfs/inode.c