Re: [PATCH] btrfs: reloc: unlink orphan reloc roots before dropping them

From: Qu Wenruo

Date: Tue Mar 10 2026 - 04:14:47 EST




在 2026/3/10 18:24, ZhengYuan Huang 写道:
clean_dirty_subvols() walks rc->dirty_subvol_roots during relocation
recovery at mount time. That list can contain both normal subvolume
roots and orphan relocation roots.

For normal subvolume roots, clean_dirty_subvols() first removes
root->reloc_dirty_list from rc->dirty_subvol_roots and then drops the
associated relocation tree. But for orphan relocation roots it directly
calls btrfs_drop_snapshot(root, false, true) without unlinking
root->reloc_dirty_list first.

This leaves a freed btrfs_root still linked in rc->dirty_subvol_roots.
Later list_del_init() on a neighboring entry writes through that stale
list node, triggering a slab-use-after-free in clean_dirty_subvols().

The analyze is correct.

[...]
Fixes: 30d40577e322 ("btrfs: reloc: Also queue orphan reloc tree for cleanup to avoid BUG_ON()")
Cc: stable@xxxxxxxxxxxxxxx # 5.1+
Signed-off-by: ZhengYuan Huang <gality369@xxxxxxxxx>
---
Root cause
==========

Tell your AI/LLM or whatever to listen to the feedback.

clean_dirty_subvols() walks rc->dirty_subvol_roots, which can contain
both normal subvolume roots and orphan relocation roots.

For normal roots, it first removes root->reloc_dirty_list from the list
before dropping the related relocation tree. But for orphan relocation
roots it calls btrfs_drop_snapshot(root, false, true) directly, without
unlinking root->reloc_dirty_list first.

btrfs_drop_snapshot() can free the last reference to root via
btrfs_put_root(), leaving a freed btrfs_root still linked in
rc->dirty_subvol_roots. Later list_del_init() on a neighboring entry
writes through that stale list node and triggers the slab-use-after-free.

Reproduction (v6.18, x86_64, KASAN)
===================================

This section is useless as commit message, and that's the only part that should be kept after the "---" line.

[...]

Fix
===
Remove orphan relocation roots from rc->dirty_subvol_roots before
calling btrfs_drop_snapshot() on them.

That restores the normal list lifetime rule:
unlink from external containers first,
then allow the final put/free to happen.

This is a minimal fix. Since both branches now call
list_del_init(&root->reloc_dirty_list), it may be possible to move the
unlink before the if/else and simplify the flow. I left that out here to
avoid changing more than needed, but I can respin the patch that way if
preferred.

KASAN reports
=============

Put this important info into changelog, and this is not the first time I or other reviewing asking you to do it.

With all these fixed it looks good to me.

Thanks,
Qu