Root Cause Analysis
1. Context and Mechanism
In Btrfs, delayed references (btrfs_delayed_ref_root, btrfs_delayed_ref_head, and btrfs_delayed_ref_node) buffer backref and checksum updates in memory to minimize contention on the extent tree and checksum tree.
When a data extent is queued for deletion in add_delayed_ref_head() (fs/btrfs/delayed-ref.c):
- If
head_ref->is_data and head_ref->ref_mod < 0, Btrfs anticipates deleting checksum items when the data extent reference count drops to 0.
- It reserves metadata space in
trans->delayed_ref_csum_deletions by calculating btrfs_csum_bytes_to_leaves(fs_info, head_ref->num_bytes).
- It increments
delayed_refs->pending_csums += head_ref->num_bytes.
btrfs_update_delayed_refs_rsv(trans) subsequently increases fs_info->delayed_refs_rsv.size and fs_info->delayed_refs_rsv.reserved.
If that same block/extent range is freed and reallocated before the delayed ref entries are run, an existing delayed reference head is reused via update_existing_head_ref() (fs/btrfs/delayed-ref.c):
if (update->must_insert_reserved) {
existing->must_insert_reserved = update->must_insert_reserved;
existing->owning_root = update->owning_root;
existing->num_bytes = update->num_bytes;
}
...
old_ref_mod = existing->total_ref_mod;
existing->ref_mod += update->ref_mod;
existing->total_ref_mod += update->ref_mod;
if (existing->is_data) {
u64 csum_leaves =
btrfs_csum_bytes_to_leaves(fs_info,
existing->num_bytes);
if (existing->total_ref_mod >= 0 && old_ref_mod < 0) {
delayed_refs->pending_csums -= existing->num_bytes;
btrfs_delayed_refs_rsv_release(fs_info, 0, csum_leaves);
}
if (existing->total_ref_mod < 0 && old_ref_mod >= 0) {
delayed_refs->pending_csums += existing->num_bytes;
trans->delayed_ref_csum_deletions += csum_leaves;
}
}
2. The Flaw
Notice that existing->num_bytes = update->num_bytes; is executed before calculating csum_leaves = btrfs_csum_bytes_to_leaves(fs_info, existing->num_bytes) and updating delayed_refs->pending_csums.
If an extent is reallocated with a smaller size than the original extent (update->num_bytes < old existing->num_bytes), and the new reference modification transitions total_ref_mod from negative to non-negative (old_ref_mod < 0 && existing->total_ref_mod >= 0):
csum_leaves is calculated based on the new, smaller update->num_bytes rather than the original num_bytes that was used when reserving space.
delayed_refs->pending_csums is decremented by update->num_bytes rather than the original num_bytes, leaving pending_csums artificially positive.
btrfs_delayed_refs_rsv_release(fs_info, 0, csum_leaves) releases fewer bytes from delayed_refs_rsv than were added in add_delayed_ref_head().
As a result:
Classification Justification
- RootCauseFiles:
fs/btrfs/delayed-ref.c contains update_existing_head_ref(), where num_bytes is prematurely updated before checksum deletion leaf accounting is reconciled.
- MemoryCorruption:
false. The bug only affects space reservation counters and does not result in invalid memory access, out-of-bounds writes, or use-after-free conditions.
- InfoLeak:
"none". No kernel, heap, or pointer contents are disclosed to user space.
- RawDenialOfService:
true. The unaccounted reservation in delayed_refs_rsv permanently inflates space_info->bytes_may_use in the metadata space info. Over time, leaked metadata reservations prevent the filesystem from reserving metadata space for new transactions, causing premature and false -ENOSPC errors that deny write access to the filesystem.
- IsNetCandidate:
false. The defect is purely in the Btrfs filesystem storage reservation layer.
- IsKVMCandidate:
false. No virtualization or KVM subsystems are involved.