Skip to content

Commit a649684

Browse files
boryaskdave
authored andcommitted
btrfs: fix start transaction qgroup rsv double free
btrfs_start_transaction reserves metadata space of the PERTRANS type before it identifies a transaction to start/join. This allows flushing when reserving that space without a deadlock. However, it results in a race which temporarily breaks qgroup rsv accounting. T1 T2 start_transaction do_stuff start_transaction qgroup_reserve_meta_pertrans commit_transaction qgroup_free_meta_all_pertrans hit an error starting txn goto reserve_fail qgroup_free_meta_pertrans (already freed!) The basic issue is that there is nothing preventing another commit from committing before start_transaction finishes (in fact sometimes we intentionally wait for it) so any error path that frees the reserve is at risk of this race. While this exact space was getting freed anyway, and it's not a huge deal to double free it (just a warning, the free code catches this), it can result in incorrectly freeing some other pertrans reservation in this same reservation, which could then lead to spuriously granting reservations we might not have the space for. Therefore, I do believe it is worth fixing. To fix it, use the existing prealloc->pertrans conversion mechanism. When we first reserve the space, we reserve prealloc space and only when we are sure we have a transaction do we convert it to pertrans. This way any racing commits do not blow away our reservation, but we still get a pertrans reservation that is freed when _this_ transaction gets committed. This issue can be reproduced by running generic/269 with either qgroups or squotas enabled via mkfs on the scratch device. Reviewed-by: Josef Bacik <[email protected]> CC: [email protected] # 5.10+ Signed-off-by: Boris Burkov <[email protected]> Signed-off-by: David Sterba <[email protected]>
1 parent e28b021 commit a649684

File tree

1 file changed

+16
-3
lines changed

1 file changed

+16
-3
lines changed

fs/btrfs/transaction.c

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -591,8 +591,13 @@ start_transaction(struct btrfs_root *root, unsigned int num_items,
591591
u64 delayed_refs_bytes = 0;
592592

593593
qgroup_reserved = num_items * fs_info->nodesize;
594-
ret = btrfs_qgroup_reserve_meta_pertrans(root, qgroup_reserved,
595-
enforce_qgroups);
594+
/*
595+
* Use prealloc for now, as there might be a currently running
596+
* transaction that could free this reserved space prematurely
597+
* by committing.
598+
*/
599+
ret = btrfs_qgroup_reserve_meta_prealloc(root, qgroup_reserved,
600+
enforce_qgroups, false);
596601
if (ret)
597602
return ERR_PTR(ret);
598603

@@ -705,6 +710,14 @@ start_transaction(struct btrfs_root *root, unsigned int num_items,
705710
h->reloc_reserved = reloc_reserved;
706711
}
707712

713+
/*
714+
* Now that we have found a transaction to be a part of, convert the
715+
* qgroup reservation from prealloc to pertrans. A different transaction
716+
* can't race in and free our pertrans out from under us.
717+
*/
718+
if (qgroup_reserved)
719+
btrfs_qgroup_convert_reserved_meta(root, qgroup_reserved);
720+
708721
got_it:
709722
if (!current->journal_info)
710723
current->journal_info = h;
@@ -752,7 +765,7 @@ start_transaction(struct btrfs_root *root, unsigned int num_items,
752765
btrfs_block_rsv_release(fs_info, &fs_info->trans_block_rsv,
753766
num_bytes, NULL);
754767
reserve_fail:
755-
btrfs_qgroup_free_meta_pertrans(root, qgroup_reserved);
768+
btrfs_qgroup_free_meta_prealloc(root, qgroup_reserved);
756769
return ERR_PTR(ret);
757770
}
758771

0 commit comments

Comments
 (0)