* [PATCH] btrfs: reloc: unlink orphan reloc roots before dropping them
@ 2026-03-10 7:54 ZhengYuan Huang
2026-03-10 8:12 ` Qu Wenruo
0 siblings, 1 reply; 5+ messages in thread
From: ZhengYuan Huang @ 2026-03-10 7:54 UTC (permalink / raw)
To: dsterba, clm, wqu
Cc: linux-btrfs, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
ZhengYuan Huang, stable
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().
Fix this by removing orphan relocation roots from
rc->dirty_subvol_roots before calling btrfs_drop_snapshot().
The bug is reproducible on 7.0.0-rc2-next-20260309 with our dynamic
metadata fuzzing tool that corrupts btrfs metadata at runtime. After
the fix, orphan relocation roots are removed from the dirty_subvol_roots
list before they can be freed, so the recovery path no longer writes
into freed memory.
Fixes: 30d40577e322 ("btrfs: reloc: Also queue orphan reloc tree for cleanup to avoid BUG_ON()")
Cc: stable@vger.kernel.org # 5.1+
Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
Root cause
==========
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)
===================================
The PoC is relatively large, so it is provided separately through google drive:
https://drive.google.com/drive/folders/1-e2ynmoQ4JzWs4F-jMtndh5pe3Vce2Zm
To reproduce the issue:
1. Build the ublk helper program from the ublk codebase, which is
used to provide the runtime corruption capability:
g++ -std=c++20 -fcoroutines -O2 -o standalone_replay \
standalone_replay_btrfs.cpp targets/ublksrv_tgt.cpp \
-I. -Iinclude -Itargets/include \
-L./lib/.libs -lublksrv -luring -lpthread
2. Attach the crafted image through ublk:
./standalone_replay add -t loop -f /path/to/image
3. Mount the image:
mount -o loop /path/to/image /mnt
This reliably reproduces the bug.
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
=============
BUG: KASAN: slab-use-after-free in __list_del include/linux/list.h:204 [inline]
BUG: KASAN: slab-use-after-free in __list_del_entry include/linux/list.h:226 [inline]
BUG: KASAN: slab-use-after-free in list_del_init include/linux/list.h:295 [inline]
BUG: KASAN: slab-use-after-free in clean_dirty_subvols+0x2dc/0x350 fs/btrfs/relocation.c:1472
Write of size 8 at addr ffff888014172838 by task syz-executor/265
CPU: 1 UID: 0 PID: 265 Comm: syz-executor Not tainted 6.18.0+ #14 PREEMPT(voluntary)
Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0xbe/0x130 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0xd1/0x650 mm/kasan/report.c:482
kasan_report+0xfb/0x140 mm/kasan/report.c:595
__asan_report_store8_noabort+0x17/0x30 mm/kasan/report_generic.c:386
__list_del include/linux/list.h:204 [inline]
__list_del_entry include/linux/list.h:226 [inline]
list_del_init include/linux/list.h:295 [inline]
clean_dirty_subvols+0x2dc/0x350 fs/btrfs/relocation.c:1472
btrfs_recover_relocation+0xe64/0x11d0 fs/btrfs/relocation.c:4207
btrfs_start_pre_rw_mount+0xa4d/0x1810 fs/btrfs/disk-io.c:3130
open_ctree+0x5824/0x5fe0 fs/btrfs/disk-io.c:3640
btrfs_fill_super fs/btrfs/super.c:987 [inline]
btrfs_get_tree_super fs/btrfs/super.c:1951 [inline]
btrfs_get_tree_subvol fs/btrfs/super.c:2094 [inline]
btrfs_get_tree+0x111c/0x2190 fs/btrfs/super.c:2128
vfs_get_tree+0x9a/0x370 fs/super.c:1758
fc_mount fs/namespace.c:1199 [inline]
do_new_mount_fc fs/namespace.c:3642 [inline]
do_new_mount fs/namespace.c:3718 [inline]
path_mount+0x5b8/0x1ea0 fs/namespace.c:4028
do_mount fs/namespace.c:4041 [inline]
__do_sys_mount fs/namespace.c:4229 [inline]
__se_sys_mount fs/namespace.c:4206 [inline]
__x64_sys_mount+0x282/0x320 fs/namespace.c:4206
x64_sys_call+0x1a7d/0x26a0 arch/x86/include/generated/asm/syscalls_64.h:166
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x93/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x76/0x7e
RIP: 0033:0x7dcca37a8fde
Code: 0f 1f 40 00 48 c7 c2 b0 ff ff ff f7 d8 64 89 02 b8 ff ff ff ff c3 66 0f 1f 44 00 00 f3 0f 1e fa 49 89 ca b8 a5 00 00 00 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 b0 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffc521c2e08 EFLAGS: 00000246 ORIG_RAX: 00000000000000a5
RAX: ffffffffffffffda RBX: 00007ffc521c31b0 RCX: 00007dcca37a8fde
RDX: 00007ffc521c35d0 RSI: 00007ffc521c31b0 RDI: 00007dcca3865d4f
RBP: 00007dcca3865d4f R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000400 R11: 0000000000000246 R12: 00007ffc521c35d0
R13: 00007dcca3865dde R14: 00000000ffffffff R15: 585858582e7a7973
</TASK>
Allocated by task 265:
kasan_save_stack+0x39/0x70 mm/kasan/common.c:56
kasan_save_track+0x14/0x40 mm/kasan/common.c:77
kasan_save_alloc_info+0x37/0x60 mm/kasan/generic.c:573
poison_kmalloc_redzone mm/kasan/common.c:400 [inline]
__kasan_kmalloc+0xc3/0xd0 mm/kasan/common.c:417
kasan_kmalloc include/linux/kasan.h:262 [inline]
__kmalloc_cache_noprof+0x25f/0x800 mm/slub.c:5771
kmalloc_noprof include/linux/slab.h:957 [inline]
kzalloc_noprof include/linux/slab.h:1094 [inline]
btrfs_alloc_root+0xb6/0xd20 fs/btrfs/disk-io.c:643
read_tree_root_path+0x15d/0xaa0 fs/btrfs/disk-io.c:1025
btrfs_read_tree_root+0x42/0x70 fs/btrfs/disk-io.c:1086
btrfs_recover_relocation+0x298/0x11d0 fs/btrfs/relocation.c:4104
btrfs_start_pre_rw_mount+0xa4d/0x1810 fs/btrfs/disk-io.c:3130
open_ctree+0x5824/0x5fe0 fs/btrfs/disk-io.c:3640
btrfs_fill_super fs/btrfs/super.c:987 [inline]
btrfs_get_tree_super fs/btrfs/super.c:1951 [inline]
btrfs_get_tree_subvol fs/btrfs/super.c:2094 [inline]
btrfs_get_tree+0x111c/0x2190 fs/btrfs/super.c:2128
vfs_get_tree+0x9a/0x370 fs/super.c:1758
fc_mount fs/namespace.c:1199 [inline]
do_new_mount_fc fs/namespace.c:3642 [inline]
do_new_mount fs/namespace.c:3718 [inline]
path_mount+0x5b8/0x1ea0 fs/namespace.c:4028
do_mount fs/namespace.c:4041 [inline]
__do_sys_mount fs/namespace.c:4229 [inline]
__se_sys_mount fs/namespace.c:4206 [inline]
__x64_sys_mount+0x282/0x320 fs/namespace.c:4206
x64_sys_call+0x1a7d/0x26a0 arch/x86/include/generated/asm/syscalls_64.h:166
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x93/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x76/0x7e
Freed by task 265:
kasan_save_stack+0x39/0x70 mm/kasan/common.c:56
kasan_save_track+0x14/0x40 mm/kasan/common.c:77
__kasan_save_free_info+0x3b/0x60 mm/kasan/generic.c:587
kasan_save_free_info mm/kasan/kasan.h:406 [inline]
poison_slab_object mm/kasan/common.c:252 [inline]
__kasan_slab_free+0x6f/0xa0 mm/kasan/common.c:284
kasan_slab_free include/linux/kasan.h:234 [inline]
slab_free_hook mm/slub.c:2543 [inline]
slab_free mm/slub.c:6642 [inline]
kfree+0x2bf/0x6b0 mm/slub.c:6849
btrfs_put_root fs/btrfs/disk-io.c:1851 [inline]
btrfs_put_root+0x1f7/0x2f0 fs/btrfs/disk-io.c:1832
btrfs_drop_snapshot+0x1e78/0x2a60 fs/btrfs/extent-tree.c:6286
clean_dirty_subvols+0x23e/0x350 fs/btrfs/relocation.c:1496
btrfs_recover_relocation+0xe64/0x11d0 fs/btrfs/relocation.c:4207
btrfs_start_pre_rw_mount+0xa4d/0x1810 fs/btrfs/disk-io.c:3130
open_ctree+0x5824/0x5fe0 fs/btrfs/disk-io.c:3640
btrfs_fill_super fs/btrfs/super.c:987 [inline]
btrfs_get_tree_super fs/btrfs/super.c:1951 [inline]
btrfs_get_tree_subvol fs/btrfs/super.c:2094 [inline]
btrfs_get_tree+0x111c/0x2190 fs/btrfs/super.c:2128
vfs_get_tree+0x9a/0x370 fs/super.c:1758
fc_mount fs/namespace.c:1199 [inline]
do_new_mount_fc fs/namespace.c:3642 [inline]
do_new_mount fs/namespace.c:3718 [inline]
path_mount+0x5b8/0x1ea0 fs/namespace.c:4028
do_mount fs/namespace.c:4041 [inline]
__do_sys_mount fs/namespace.c:4229 [inline]
__se_sys_mount fs/namespace.c:4206 [inline]
__x64_sys_mount+0x282/0x320 fs/namespace.c:4206
x64_sys_call+0x1a7d/0x26a0 arch/x86/include/generated/asm/syscalls_64.h:166
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x93/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x76/0x7e
The buggy address belongs to the object at ffff888014172000
which belongs to the cache kmalloc-rnd-06-4k of size 4096
The buggy address is located 2104 bytes inside of
freed 4096-byte region [ffff888014172000, ffff888014173000)
---
fs/btrfs/relocation.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c
index 0765e06d00b8..2bff2db7afbd 100644
--- a/fs/btrfs/relocation.c
+++ b/fs/btrfs/relocation.c
@@ -1492,7 +1492,8 @@ static int clean_dirty_subvols(struct reloc_control *rc)
}
btrfs_put_root(root);
} else {
- /* Orphan reloc tree, just clean it up */
+ /* Orphan reloc tree, unlink it first and clean it up */
+ list_del_init(&root->reloc_dirty_list);
ret2 = btrfs_drop_snapshot(root, false, true);
if (ret2 < 0) {
btrfs_put_root(root);
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [PATCH] btrfs: reloc: unlink orphan reloc roots before dropping them
2026-03-10 7:54 [PATCH] btrfs: reloc: unlink orphan reloc roots before dropping them ZhengYuan Huang
@ 2026-03-10 8:12 ` Qu Wenruo
2026-03-10 8:41 ` ZhengYuan Huang
0 siblings, 1 reply; 5+ messages in thread
From: Qu Wenruo @ 2026-03-10 8:12 UTC (permalink / raw)
To: ZhengYuan Huang, dsterba, clm, wqu
Cc: linux-btrfs, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
stable
在 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@vger.kernel.org # 5.1+
> Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
> ---
> 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
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH] btrfs: reloc: unlink orphan reloc roots before dropping them
2026-03-10 8:12 ` Qu Wenruo
@ 2026-03-10 8:41 ` ZhengYuan Huang
2026-03-10 9:33 ` Qu Wenruo
0 siblings, 1 reply; 5+ messages in thread
From: ZhengYuan Huang @ 2026-03-10 8:41 UTC (permalink / raw)
To: Qu Wenruo
Cc: dsterba, clm, linux-btrfs, linux-kernel, baijiaju1990, r33s3n6,
zzzccc427, stable
On Tue, Mar 10, 2026 at 4:13 PM Qu Wenruo <wqu@suse.com> wrote:
> > [...]
>
> Put this important info into changelog, and this is not the first time I
> or other reviewing asking you to do it.
Thanks a lot for the detailed review and for being patient with my patch
submissions. I'm still learning the kernel patch submission style,
especially how much detail should go into the changelog above the "---" line
versus what should stay below it.
I think part of my confusion comes from seeing many patches with very concise
changelogs, so I have been trying to keep that part short, but I may
have overdone it and moved too much useful information below the separator.
From your feedback, my understanding is that the changelog should include
the essential root cause, the fix rationale, and the key crash symptom
(for example
a concise KASAN summary), while the material below "---" should be limited to
supplementary information such as full reproduction details or longer
logs. Is that
the right interpretation?
If there is a patch or changelog example that you think is a good reference
for this style, I would really appreciate it. I'd like to study it carefully
and improve how I write future submissions.
Thanks again for the guidance.
Thanks,
ZhengYuan Huang
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] btrfs: reloc: unlink orphan reloc roots before dropping them
2026-03-10 8:41 ` ZhengYuan Huang
@ 2026-03-10 9:33 ` Qu Wenruo
2026-03-10 9:43 ` ZhengYuan Huang
0 siblings, 1 reply; 5+ messages in thread
From: Qu Wenruo @ 2026-03-10 9:33 UTC (permalink / raw)
To: ZhengYuan Huang
Cc: dsterba, clm, linux-btrfs, linux-kernel, baijiaju1990, r33s3n6,
zzzccc427, stable
在 2026/3/10 19:11, ZhengYuan Huang 写道:
> On Tue, Mar 10, 2026 at 4:13 PM Qu Wenruo <wqu@suse.com> wrote:
>>> [...]
>>
>> Put this important info into changelog, and this is not the first time I
>> or other reviewing asking you to do it.
>
> Thanks a lot for the detailed review and for being patient with my patch
> submissions. I'm still learning the kernel patch submission style,
> especially how much detail should go into the changelog above the "---" line
> versus what should stay below it.
>
> I think part of my confusion comes from seeing many patches with very concise
> changelogs, so I have been trying to keep that part short, but I may
> have overdone it and moved too much useful information below the separator.
If you know the bug/cause/fix well, and pretty sure it will not affect
most users, sure short changelogs are good as long as you explained it well.
But if you already hit a KASAN/crash, paste the info itself will help
explaing the situation well enough, and such calltrace will help a lot
in the future for the following cases:
- The end user who hits a crash with similar call trace
Who want to know if it's already fix or some one else hits the same
problem.
If the call trace is included, one can determine if it's the same thus
if it's already fixed in the latest kernel.
- The engineer who is responsible for backporting
Such call trace will help him/her to determine if it's needed for
backport.
A KASAN report/crash with call trace will definitely be more obvious.
>
> From your feedback, my understanding is that the changelog should include
> the essential root cause, the fix rationale, and the key crash symptom
> (for example
> a concise KASAN summary), while the material below "---" should be limited to
> supplementary information such as full reproduction details or longer
> logs. Is that
> the right interpretation?
Yes.
>
> If there is a patch or changelog example that you think is a good reference
> for this style, I would really appreciate it. I'd like to study it carefully
> and improve how I write future submissions.
I just grabbed one from Johannes:
https://lore.kernel.org/linux-btrfs/20260224125113.14831-1-johannes.thumshirn@wdc.com/
And from Filipe:
https://lore.kernel.org/linux-btrfs/b99cee6ce652b926463a080ef052a2e8e37bff33.1772105193.git.fdmanana@suse.com/
And myself, which is more aligned to your style:
https://lore.kernel.org/linux-btrfs/4170e39bac4a2559ad0535f9bd74a89bc44a36d4.1771488629.git.wqu@suse.com/
Thanks,
Qu
>
> Thanks again for the guidance.
>
> Thanks,
> ZhengYuan Huang
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH] btrfs: reloc: unlink orphan reloc roots before dropping them
2026-03-10 9:33 ` Qu Wenruo
@ 2026-03-10 9:43 ` ZhengYuan Huang
0 siblings, 0 replies; 5+ messages in thread
From: ZhengYuan Huang @ 2026-03-10 9:43 UTC (permalink / raw)
To: Qu Wenruo
Cc: dsterba, clm, linux-btrfs, linux-kernel, baijiaju1990, r33s3n6,
zzzccc427, stable
On Tue, Mar 10, 2026 at 5:33 PM Qu Wenruo <wqu@suse.com> wrote:
> I just grabbed one from Johannes:
>
> https://lore.kernel.org/linux-btrfs/20260224125113.14831-1-johannes.thumshirn@wdc.com/
>
> And from Filipe:
>
> https://lore.kernel.org/linux-btrfs/b99cee6ce652b926463a080ef052a2e8e37bff33.1772105193.git.fdmanana@suse.com/
>
> And myself, which is more aligned to your style:
>
> https://lore.kernel.org/linux-btrfs/4170e39bac4a2559ad0535f9bd74a89bc44a36d4.1771488629.git.wqu@suse.com/
Thanks a lot for the clarification and for the example patches.
This is very helpful. I understand the changelog expectations much
better now, especially for crash/KASAN fixes. I'll study the examples
you linked and use them as references for future submissions.
Thanks again for the guidance.
Thanks,
ZhengYuan Huang
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-03-10 9:43 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-10 7:54 [PATCH] btrfs: reloc: unlink orphan reloc roots before dropping them ZhengYuan Huang
2026-03-10 8:12 ` Qu Wenruo
2026-03-10 8:41 ` ZhengYuan Huang
2026-03-10 9:33 ` Qu Wenruo
2026-03-10 9:43 ` ZhengYuan Huang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox