* [PATCH v5 0/2] btrfs: fix a UAF where btrfs_root::dirty_list is freed but still referred
@ 2026-10-11 8:21 Qu Wenruo
2026-10-11 8:21 ` [PATCH v5 1/2] btrfs: prevent use-after-free in btrfs_transaction::switch_commits Qu Wenruo
2026-10-11 8:21 ` [PATCH v5 2/2] btrfs: fix use-after-free on quota enable allocation failure Qu Wenruo
0 siblings, 2 replies; 11+ messages in thread
From: Qu Wenruo @ 2026-10-11 8:21 UTC (permalink / raw)
To: linux-btrfs
[CHANGELOG]
v5:
- Drop the first patch
As we no longer put the list_del_init() call inside
btrfs_put_root(), the existing code is already safe without the need
for extra spinlock, as the existing callers are already holding a
trans handle, thus won't race with transaction cleanup/committing.
- Also drop the spinlock from the 2nd patch
As we are already safe without that lock.
- Update the commit message of the last commit
As we no long hold trans_lock for every btrfs_root::dirty_list caller.
v4:
- Also cleanup qgroup swapped blocks and dirty_log_pages
Which are only released during switch_commit_roots().
v3:
- Use Yalagada's v2 fix as the final UAF fix
The delayed list_del_init() call inside btrfs_put_root() is not
safe as another racing ioctl can grab the quota root, extending
its lifespan.
v2:
- Add extra patches to address Sashiko's comment
* Make all root->dirty_list users to hold trans_lock
* Release root->dirty_list from cur_trans->switch_commits during
transaction cleanup
The first patch is an existing bug exposed by Sashiko, which also
affects the last UAF fix.
The last one is the final UAF fix for the bug reported by syzbot.
Qu Wenruo (1):
btrfs: prevent use-after-free in btrfs_transaction::switch_commits
Yalagada Pavan Kumar (1):
btrfs: fix use-after-free on quota enable allocation failure
fs/btrfs/disk-io.c | 10 ++++++++++
fs/btrfs/qgroup.c | 7 +++++++
2 files changed, 17 insertions(+)
--
2.56.0
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v5 1/2] btrfs: prevent use-after-free in btrfs_transaction::switch_commits
2026-10-11 8:21 [PATCH v5 0/2] btrfs: fix a UAF where btrfs_root::dirty_list is freed but still referred Qu Wenruo
@ 2026-10-11 8:21 ` Qu Wenruo
2026-10-12 10:41 ` Filipe Manana
2026-10-11 8:21 ` [PATCH v5 2/2] btrfs: fix use-after-free on quota enable allocation failure Qu Wenruo
1 sibling, 1 reply; 11+ messages in thread
From: Qu Wenruo @ 2026-10-11 8:21 UTC (permalink / raw)
To: linux-btrfs; +Cc: Boris Burkov
If a transaction is aborted during committing,
btrfs_transaction::switch_commits can be non-empty, but the
btrfs_transaction structure can be freed at the last
btrfs_put_transaction() inside cleanup_transaction().
The btrfs_root::dirty_list entry still linked into switch_commits will
point to already released memory, causing use-after-free.
Address this problem by manually deleting all entries inside
btrfs_transaction::switch_commits.
And since we're here, also release the dirty_log_pages and qgroup
swapped blocks for that root, which are only released during a
successful switch_commit_roots() call.
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
fs/btrfs/disk-io.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 66b054eb98c6..02adda065097 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -4966,6 +4966,16 @@ void btrfs_cleanup_one_transaction(struct btrfs_transaction *cur_trans)
list_del_init(&dev->post_commit_list);
}
+ while (!list_empty(&cur_trans->switch_commits)) {
+ struct btrfs_root *root;
+
+ root = list_first_entry(&cur_trans->switch_commits,
+ struct btrfs_root, dirty_list);
+ list_del_init(&root->dirty_list);
+ btrfs_extent_io_tree_release(&root->dirty_log_pages);
+ btrfs_qgroup_clean_swapped_blocks(root);
+ }
+
btrfs_destroy_delayed_refs(cur_trans);
cur_trans->state = TRANS_STATE_COMMIT_START;
--
2.56.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH v5 2/2] btrfs: fix use-after-free on quota enable allocation failure
2026-10-11 8:21 [PATCH v5 0/2] btrfs: fix a UAF where btrfs_root::dirty_list is freed but still referred Qu Wenruo
2026-10-11 8:21 ` [PATCH v5 1/2] btrfs: prevent use-after-free in btrfs_transaction::switch_commits Qu Wenruo
@ 2026-10-11 8:21 ` Qu Wenruo
2026-10-12 10:47 ` Filipe Manana
2026-10-12 15:46 ` Daniel Vacek
1 sibling, 2 replies; 11+ messages in thread
From: Qu Wenruo @ 2026-10-11 8:21 UTC (permalink / raw)
To: linux-btrfs
Cc: Yalagada Pavan Kumar, syzbot+947286c775f432b073a8, Boris Burkov
From: Yalagada Pavan Kumar <pavankumaryalagada@gmail.com>
[BUG]
There is a syzbot report that a use-after-free bug is triggered after a
failed btrfs_quota_enable():
loop0: detected capacity change from 32768 to 0
==================================================================
BUG: KASAN: slab-use-after-free in __list_add_valid_or_report+0x4e/0x130 lib/list_debug.c:29
Read of size 8 at addr ffff888040e0a528 by task syz.0.0/5324
CPU: 0 UID: 0 PID: 5324 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
__list_add_valid_or_report+0x4e/0x130 lib/list_debug.c:29
__list_add_valid include/linux/list.h:104 [inline]
__list_add include/linux/list.h:169 [inline]
list_add include/linux/list.h:192 [inline]
list_move include/linux/list.h:345 [inline]
add_root_to_dirty_list+0x38b/0x470 fs/btrfs/ctree.c:232
btrfs_force_cow_block+0xf09/0x1bf0 fs/btrfs/ctree.c:553
btrfs_cow_block+0x3f1/0xaf0 fs/btrfs/ctree.c:696
btrfs_search_slot+0xd70/0x2d20 fs/btrfs/ctree.c:-1
btrfs_search_prev_slot fs/btrfs/free-space-tree.c:134 [inline]
remove_free_space_extent fs/btrfs/free-space-tree.c:732 [inline]
__btrfs_remove_from_free_space_tree fs/btrfs/free-space-tree.c:837 [inline]
btrfs_remove_from_free_space_tree+0x4d6/0xda0 fs/btrfs/free-space-tree.c:866
alloc_reserved_extent+0x4a/0x2b0 fs/btrfs/extent-tree.c:4976
alloc_reserved_tree_block fs/btrfs/extent-tree.c:5155 [inline]
run_delayed_tree_ref fs/btrfs/extent-tree.c:1815 [inline]
run_one_delayed_ref fs/btrfs/extent-tree.c:1851 [inline]
btrfs_run_delayed_refs_for_head fs/btrfs/extent-tree.c:2058 [inline]
__btrfs_run_delayed_refs+0x18b5/0x43b0 fs/btrfs/extent-tree.c:2134
btrfs_run_delayed_refs+0xdc/0x2a0 fs/btrfs/extent-tree.c:2246
btrfs_commit_transaction+0x28a/0x3170 fs/btrfs/transaction.c:2267
sync_filesystem+0x1d2/0x240 fs/sync.c:66
btrfs_reconfigure+0x2dc/0x2100 fs/btrfs/super.c:1511
reconfigure_super+0x232/0x8f0 fs/super.c:1128
do_remount fs/namespace.c:3410 [inline]
path_mount+0xd4e/0x1050 fs/namespace.c:4160
do_mount fs/namespace.c:4181 [inline]
__do_sys_mount fs/namespace.c:4397 [inline]
__se_sys_mount+0x31d/0x420 fs/namespace.c:4374
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f9c8dd9f3ca
</TASK>
Allocated by task 5323:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:409 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:426
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
_kmalloc_noprof include/linux/slab.h:991 [inline]
_kzalloc_noprof include/linux/slab.h:1312 [inline]
btrfs_alloc_root+0x75/0x840 fs/btrfs/disk-io.c:638
btrfs_create_tree+0xa8/0x5c0 fs/btrfs/disk-io.c:832
btrfs_quota_enable+0x3d6/0x1e20 fs/btrfs/qgroup.c:1074
btrfs_ioctl_quota_ctl+0x186/0x1f0 fs/btrfs/ioctl.c:3581
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:597 [inline]
__se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 5323:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:264 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:296
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6508 [inline]
kfree+0x1c5/0x650 mm/slub.c:6801
btrfs_quota_enable+0x1208/0x1e20 fs/btrfs/qgroup.c:1292
btrfs_ioctl_quota_ctl+0x186/0x1f0 fs/btrfs/ioctl.c:3581
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:597 [inline]
__se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
[CAUSE]
During btrfs_quota_enable(), a temporary quota_root is created, then
qgroup items are created for each subvolume and inserted into that root.
If there are enough subvolumes, the level of quota_root will be bumped,
and add_root_to_dirty_list() will be called, adding
quota_root->dirty_list into fs_info->dirty_cowonly_roots.
But if a problem is hit during btrfs_quota_enable() later, that
quota_root will be released through btrfs_put_root(), causing the whole
quota_root structure to be released, while leaving
fs_info->dirty_cowonly_roots still pointing to the released
quota_root->dirty_list.
Later access through fs_info->dirty_cowonly_roots will trigger a
use-after-free bug.
[FIX]
We can just call list_del_init(&root->dirty_list) when
btrfs_quota_enable() failed.
Also to follow the existing error handling behavior, abort the
transaction when the memory preallocation failed.
Reported-by: syzbot+947286c775f432b073a8@syzkaller.appspotmail.com
Link: https://lore.kernel.org/linux-btrfs/6ac5a5e1.9ad129c8.3ce5b3.0010.GAE@google.com/
Closes: https://syzkaller.appspot.com/bug?extid=947286c775f432b073a8
Signed-off-by: Yalagada Pavan Kumar <pavankumaryalagada@gmail.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
[ Change the commit message to include a proper analysis. ]
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
---
fs/btrfs/qgroup.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
index cf8dfd5c692b..a0651fbfe778 100644
--- a/fs/btrfs/qgroup.c
+++ b/fs/btrfs/qgroup.c
@@ -1207,6 +1207,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
prealloc = kzalloc_obj(*prealloc, GFP_NOFS);
if (!prealloc) {
ret = -ENOMEM;
+ btrfs_abort_transaction(trans, ret);
goto out;
}
qgroup = add_qgroup_rb(fs_info, prealloc, BTRFS_FS_TREE_OBJECTID);
@@ -1296,6 +1297,12 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
* sysfs entries.
*/
btrfs_free_qgroup_config(fs_info);
+
+ if (quota_root) {
+ spin_lock(&fs_info->trans_lock);
+ list_del_init("a_root->dirty_list);
+ spin_unlock(&fs_info->trans_lock);
+ }
btrfs_put_root(quota_root);
}
mutex_unlock(&fs_info->qgroup_ioctl_lock);
--
2.56.0
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH v5 1/2] btrfs: prevent use-after-free in btrfs_transaction::switch_commits
2026-10-11 8:21 ` [PATCH v5 1/2] btrfs: prevent use-after-free in btrfs_transaction::switch_commits Qu Wenruo
@ 2026-10-12 10:41 ` Filipe Manana
2026-10-12 21:13 ` Qu Wenruo
0 siblings, 1 reply; 11+ messages in thread
From: Filipe Manana @ 2026-10-12 10:41 UTC (permalink / raw)
To: Qu Wenruo; +Cc: linux-btrfs, Boris Burkov
On Sun, Oct 11, 2026 at 9:22 AM Qu Wenruo <wqu@suse.com> wrote:
>
> If a transaction is aborted during committing,
> btrfs_transaction::switch_commits can be non-empty, but the
> btrfs_transaction structure can be freed at the last
> btrfs_put_transaction() inside cleanup_transaction().
>
> The btrfs_root::dirty_list entry still linked into switch_commits will
> point to already released memory, causing use-after-free.
>
> Address this problem by manually deleting all entries inside
> btrfs_transaction::switch_commits.
>
> And since we're here, also release the dirty_log_pages and qgroup
> swapped blocks for that root, which are only released during a
> successful switch_commit_roots() call.
>
> Reviewed-by: Boris Burkov <boris@bur.io>
> Signed-off-by: Qu Wenruo <wqu@suse.com>
> ---
> fs/btrfs/disk-io.c | 10 ++++++++++
> 1 file changed, 10 insertions(+)
>
> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
> index 66b054eb98c6..02adda065097 100644
> --- a/fs/btrfs/disk-io.c
> +++ b/fs/btrfs/disk-io.c
> @@ -4966,6 +4966,16 @@ void btrfs_cleanup_one_transaction(struct btrfs_transaction *cur_trans)
> list_del_init(&dev->post_commit_list);
> }
>
> + while (!list_empty(&cur_trans->switch_commits)) {
> + struct btrfs_root *root;
> +
> + root = list_first_entry(&cur_trans->switch_commits,
> + struct btrfs_root, dirty_list);
> + list_del_init(&root->dirty_list);
> + btrfs_extent_io_tree_release(&root->dirty_log_pages);
> + btrfs_qgroup_clean_swapped_blocks(root);
> + }
Sashiko has some comments which seem valid to me, roots still in the
dirty_cowonly_roots will get their dirty_log_pages and swapped blocks
leaked.
> +
> btrfs_destroy_delayed_refs(cur_trans);
>
> cur_trans->state = TRANS_STATE_COMMIT_START;
> --
> 2.56.0
>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 2/2] btrfs: fix use-after-free on quota enable allocation failure
2026-10-11 8:21 ` [PATCH v5 2/2] btrfs: fix use-after-free on quota enable allocation failure Qu Wenruo
@ 2026-10-12 10:47 ` Filipe Manana
2026-10-12 15:46 ` Daniel Vacek
1 sibling, 0 replies; 11+ messages in thread
From: Filipe Manana @ 2026-10-12 10:47 UTC (permalink / raw)
To: Qu Wenruo
Cc: linux-btrfs, Yalagada Pavan Kumar, syzbot+947286c775f432b073a8,
Boris Burkov
On Sun, Oct 11, 2026 at 9:22 AM Qu Wenruo <wqu@suse.com> wrote:
>
> From: Yalagada Pavan Kumar <pavankumaryalagada@gmail.com>
>
> [BUG]
> There is a syzbot report that a use-after-free bug is triggered after a
> failed btrfs_quota_enable():
>
> loop0: detected capacity change from 32768 to 0
> ==================================================================
> BUG: KASAN: slab-use-after-free in __list_add_valid_or_report+0x4e/0x130 lib/list_debug.c:29
> Read of size 8 at addr ffff888040e0a528 by task syz.0.0/5324
>
> CPU: 0 UID: 0 PID: 5324 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
> Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> Call Trace:
> <TASK>
> dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
> print_address_description+0x55/0x1e0 mm/kasan/report.c:378
> print_report+0x58/0x70 mm/kasan/report.c:482
> kasan_report+0x117/0x150 mm/kasan/report.c:595
> __list_add_valid_or_report+0x4e/0x130 lib/list_debug.c:29
> __list_add_valid include/linux/list.h:104 [inline]
> __list_add include/linux/list.h:169 [inline]
> list_add include/linux/list.h:192 [inline]
> list_move include/linux/list.h:345 [inline]
> add_root_to_dirty_list+0x38b/0x470 fs/btrfs/ctree.c:232
> btrfs_force_cow_block+0xf09/0x1bf0 fs/btrfs/ctree.c:553
> btrfs_cow_block+0x3f1/0xaf0 fs/btrfs/ctree.c:696
> btrfs_search_slot+0xd70/0x2d20 fs/btrfs/ctree.c:-1
> btrfs_search_prev_slot fs/btrfs/free-space-tree.c:134 [inline]
> remove_free_space_extent fs/btrfs/free-space-tree.c:732 [inline]
> __btrfs_remove_from_free_space_tree fs/btrfs/free-space-tree.c:837 [inline]
> btrfs_remove_from_free_space_tree+0x4d6/0xda0 fs/btrfs/free-space-tree.c:866
> alloc_reserved_extent+0x4a/0x2b0 fs/btrfs/extent-tree.c:4976
> alloc_reserved_tree_block fs/btrfs/extent-tree.c:5155 [inline]
> run_delayed_tree_ref fs/btrfs/extent-tree.c:1815 [inline]
> run_one_delayed_ref fs/btrfs/extent-tree.c:1851 [inline]
> btrfs_run_delayed_refs_for_head fs/btrfs/extent-tree.c:2058 [inline]
> __btrfs_run_delayed_refs+0x18b5/0x43b0 fs/btrfs/extent-tree.c:2134
> btrfs_run_delayed_refs+0xdc/0x2a0 fs/btrfs/extent-tree.c:2246
> btrfs_commit_transaction+0x28a/0x3170 fs/btrfs/transaction.c:2267
> sync_filesystem+0x1d2/0x240 fs/sync.c:66
> btrfs_reconfigure+0x2dc/0x2100 fs/btrfs/super.c:1511
> reconfigure_super+0x232/0x8f0 fs/super.c:1128
> do_remount fs/namespace.c:3410 [inline]
> path_mount+0xd4e/0x1050 fs/namespace.c:4160
> do_mount fs/namespace.c:4181 [inline]
> __do_sys_mount fs/namespace.c:4397 [inline]
> __se_sys_mount+0x31d/0x420 fs/namespace.c:4374
> do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
> do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> RIP: 0033:0x7f9c8dd9f3ca
> </TASK>
>
> Allocated by task 5323:
> kasan_save_stack mm/kasan/common.c:57 [inline]
> kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
> poison_kmalloc_redzone mm/kasan/common.c:409 [inline]
> __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:426
> kasan_kmalloc include/linux/kasan.h:263 [inline]
> __kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
> _kmalloc_noprof include/linux/slab.h:991 [inline]
> _kzalloc_noprof include/linux/slab.h:1312 [inline]
> btrfs_alloc_root+0x75/0x840 fs/btrfs/disk-io.c:638
> btrfs_create_tree+0xa8/0x5c0 fs/btrfs/disk-io.c:832
> btrfs_quota_enable+0x3d6/0x1e20 fs/btrfs/qgroup.c:1074
> btrfs_ioctl_quota_ctl+0x186/0x1f0 fs/btrfs/ioctl.c:3581
> vfs_ioctl fs/ioctl.c:51 [inline]
> __do_sys_ioctl fs/ioctl.c:597 [inline]
> __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
> do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
> do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> Freed by task 5323:
> kasan_save_stack mm/kasan/common.c:57 [inline]
> kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
> kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
> poison_slab_object mm/kasan/common.c:264 [inline]
> __kasan_slab_free+0x5c/0x80 mm/kasan/common.c:296
> kasan_slab_free include/linux/kasan.h:235 [inline]
> slab_free_hook mm/slub.c:2748 [inline]
> slab_free mm/slub.c:6508 [inline]
> kfree+0x1c5/0x650 mm/slub.c:6801
> btrfs_quota_enable+0x1208/0x1e20 fs/btrfs/qgroup.c:1292
> btrfs_ioctl_quota_ctl+0x186/0x1f0 fs/btrfs/ioctl.c:3581
> vfs_ioctl fs/ioctl.c:51 [inline]
> __do_sys_ioctl fs/ioctl.c:597 [inline]
> __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
> do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
> do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> [CAUSE]
> During btrfs_quota_enable(), a temporary quota_root is created, then
> qgroup items are created for each subvolume and inserted into that root.
>
> If there are enough subvolumes, the level of quota_root will be bumped,
> and add_root_to_dirty_list() will be called, adding
> quota_root->dirty_list into fs_info->dirty_cowonly_roots.
>
> But if a problem is hit during btrfs_quota_enable() later, that
> quota_root will be released through btrfs_put_root(), causing the whole
> quota_root structure to be released, while leaving
> fs_info->dirty_cowonly_roots still pointing to the released
> quota_root->dirty_list.
>
> Later access through fs_info->dirty_cowonly_roots will trigger a
> use-after-free bug.
>
> [FIX]
> We can just call list_del_init(&root->dirty_list) when
> btrfs_quota_enable() failed.
>
> Also to follow the existing error handling behavior, abort the
> transaction when the memory preallocation failed.
>
> Reported-by: syzbot+947286c775f432b073a8@syzkaller.appspotmail.com
> Link: https://lore.kernel.org/linux-btrfs/6ac5a5e1.9ad129c8.3ce5b3.0010.GAE@google.com/
> Closes: https://syzkaller.appspot.com/bug?extid=947286c775f432b073a8
> Signed-off-by: Yalagada Pavan Kumar <pavankumaryalagada@gmail.com>
> Reviewed-by: Qu Wenruo <wqu@suse.com>
> [ Change the commit message to include a proper analysis. ]
> Reviewed-by: Boris Burkov <boris@bur.io>
> Signed-off-by: Qu Wenruo <wqu@suse.com>
> ---
> fs/btrfs/qgroup.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
> index cf8dfd5c692b..a0651fbfe778 100644
> --- a/fs/btrfs/qgroup.c
> +++ b/fs/btrfs/qgroup.c
> @@ -1207,6 +1207,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
> prealloc = kzalloc_obj(*prealloc, GFP_NOFS);
> if (!prealloc) {
> ret = -ENOMEM;
> + btrfs_abort_transaction(trans, ret);
> goto out;
> }
> qgroup = add_qgroup_rb(fs_info, prealloc, BTRFS_FS_TREE_OBJECTID);
> @@ -1296,6 +1297,12 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
> * sysfs entries.
> */
> btrfs_free_qgroup_config(fs_info);
> +
> + if (quota_root) {
> + spin_lock(&fs_info->trans_lock);
> + list_del_init("a_root->dirty_list);
> + spin_unlock(&fs_info->trans_lock);
> + }
> btrfs_put_root(quota_root);
It looks a bit odd to have left btrfs_put_root() outside the if block
that tests for a non NULL quota root.
While's harmless, it feels odd.
Otherwise:
Reviewed-by: Filipe Manana <fdmanana@suse.com>
> }
> mutex_unlock(&fs_info->qgroup_ioctl_lock);
> --
> 2.56.0
>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 2/2] btrfs: fix use-after-free on quota enable allocation failure
2026-10-11 8:21 ` [PATCH v5 2/2] btrfs: fix use-after-free on quota enable allocation failure Qu Wenruo
2026-10-12 10:47 ` Filipe Manana
@ 2026-10-12 15:46 ` Daniel Vacek
2026-10-12 17:19 ` Filipe Manana
2026-10-12 21:17 ` Qu Wenruo
1 sibling, 2 replies; 11+ messages in thread
From: Daniel Vacek @ 2026-10-12 15:46 UTC (permalink / raw)
To: Qu Wenruo
Cc: linux-btrfs, Yalagada Pavan Kumar, syzbot+947286c775f432b073a8,
Boris Burkov
On Sun, 11 Oct 2026 at 10:22, Qu Wenruo <wqu@suse.com> wrote:
> From: Yalagada Pavan Kumar <pavankumaryalagada@gmail.com>
>
> [BUG]
> There is a syzbot report that a use-after-free bug is triggered after a
> failed btrfs_quota_enable():
>
> loop0: detected capacity change from 32768 to 0
> ==================================================================
> BUG: KASAN: slab-use-after-free in __list_add_valid_or_report+0x4e/0x130 lib/list_debug.c:29
> Read of size 8 at addr ffff888040e0a528 by task syz.0.0/5324
>
> CPU: 0 UID: 0 PID: 5324 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
> Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> Call Trace:
> <TASK>
> dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
> print_address_description+0x55/0x1e0 mm/kasan/report.c:378
> print_report+0x58/0x70 mm/kasan/report.c:482
> kasan_report+0x117/0x150 mm/kasan/report.c:595
> __list_add_valid_or_report+0x4e/0x130 lib/list_debug.c:29
> __list_add_valid include/linux/list.h:104 [inline]
> __list_add include/linux/list.h:169 [inline]
> list_add include/linux/list.h:192 [inline]
> list_move include/linux/list.h:345 [inline]
> add_root_to_dirty_list+0x38b/0x470 fs/btrfs/ctree.c:232
> btrfs_force_cow_block+0xf09/0x1bf0 fs/btrfs/ctree.c:553
> btrfs_cow_block+0x3f1/0xaf0 fs/btrfs/ctree.c:696
> btrfs_search_slot+0xd70/0x2d20 fs/btrfs/ctree.c:-1
> btrfs_search_prev_slot fs/btrfs/free-space-tree.c:134 [inline]
> remove_free_space_extent fs/btrfs/free-space-tree.c:732 [inline]
> __btrfs_remove_from_free_space_tree fs/btrfs/free-space-tree.c:837 [inline]
> btrfs_remove_from_free_space_tree+0x4d6/0xda0 fs/btrfs/free-space-tree.c:866
> alloc_reserved_extent+0x4a/0x2b0 fs/btrfs/extent-tree.c:4976
> alloc_reserved_tree_block fs/btrfs/extent-tree.c:5155 [inline]
> run_delayed_tree_ref fs/btrfs/extent-tree.c:1815 [inline]
> run_one_delayed_ref fs/btrfs/extent-tree.c:1851 [inline]
> btrfs_run_delayed_refs_for_head fs/btrfs/extent-tree.c:2058 [inline]
> __btrfs_run_delayed_refs+0x18b5/0x43b0 fs/btrfs/extent-tree.c:2134
> btrfs_run_delayed_refs+0xdc/0x2a0 fs/btrfs/extent-tree.c:2246
> btrfs_commit_transaction+0x28a/0x3170 fs/btrfs/transaction.c:2267
> sync_filesystem+0x1d2/0x240 fs/sync.c:66
> btrfs_reconfigure+0x2dc/0x2100 fs/btrfs/super.c:1511
> reconfigure_super+0x232/0x8f0 fs/super.c:1128
> do_remount fs/namespace.c:3410 [inline]
> path_mount+0xd4e/0x1050 fs/namespace.c:4160
> do_mount fs/namespace.c:4181 [inline]
> __do_sys_mount fs/namespace.c:4397 [inline]
> __se_sys_mount+0x31d/0x420 fs/namespace.c:4374
> do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
> do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
> RIP: 0033:0x7f9c8dd9f3ca
> </TASK>
>
> Allocated by task 5323:
> kasan_save_stack mm/kasan/common.c:57 [inline]
> kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
> poison_kmalloc_redzone mm/kasan/common.c:409 [inline]
> __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:426
> kasan_kmalloc include/linux/kasan.h:263 [inline]
> __kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
> _kmalloc_noprof include/linux/slab.h:991 [inline]
> _kzalloc_noprof include/linux/slab.h:1312 [inline]
> btrfs_alloc_root+0x75/0x840 fs/btrfs/disk-io.c:638
> btrfs_create_tree+0xa8/0x5c0 fs/btrfs/disk-io.c:832
> btrfs_quota_enable+0x3d6/0x1e20 fs/btrfs/qgroup.c:1074
> btrfs_ioctl_quota_ctl+0x186/0x1f0 fs/btrfs/ioctl.c:3581
> vfs_ioctl fs/ioctl.c:51 [inline]
> __do_sys_ioctl fs/ioctl.c:597 [inline]
> __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
> do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
> do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> Freed by task 5323:
> kasan_save_stack mm/kasan/common.c:57 [inline]
> kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
> kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
> poison_slab_object mm/kasan/common.c:264 [inline]
> __kasan_slab_free+0x5c/0x80 mm/kasan/common.c:296
> kasan_slab_free include/linux/kasan.h:235 [inline]
> slab_free_hook mm/slub.c:2748 [inline]
> slab_free mm/slub.c:6508 [inline]
> kfree+0x1c5/0x650 mm/slub.c:6801
> btrfs_quota_enable+0x1208/0x1e20 fs/btrfs/qgroup.c:1292
> btrfs_ioctl_quota_ctl+0x186/0x1f0 fs/btrfs/ioctl.c:3581
> vfs_ioctl fs/ioctl.c:51 [inline]
> __do_sys_ioctl fs/ioctl.c:597 [inline]
> __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
> do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
> do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>
> [CAUSE]
> During btrfs_quota_enable(), a temporary quota_root is created, then
> qgroup items are created for each subvolume and inserted into that root.
>
> If there are enough subvolumes, the level of quota_root will be bumped,
> and add_root_to_dirty_list() will be called, adding
> quota_root->dirty_list into fs_info->dirty_cowonly_roots.
>
> But if a problem is hit during btrfs_quota_enable() later, that
> quota_root will be released through btrfs_put_root(), causing the whole
> quota_root structure to be released, while leaving
> fs_info->dirty_cowonly_roots still pointing to the released
> quota_root->dirty_list.
>
> Later access through fs_info->dirty_cowonly_roots will trigger a
> use-after-free bug.
>
> [FIX]
> We can just call list_del_init(&root->dirty_list) when
> btrfs_quota_enable() failed.
>
> Also to follow the existing error handling behavior, abort the
> transaction when the memory preallocation failed.
>
> Reported-by: syzbot+947286c775f432b073a8@syzkaller.appspotmail.com
> Link: https://lore.kernel.org/linux-btrfs/6ac5a5e1.9ad129c8.3ce5b3.0010.GAE@google.com/
> Closes: https://syzkaller.appspot.com/bug?extid=947286c775f432b073a8
> Signed-off-by: Yalagada Pavan Kumar <pavankumaryalagada@gmail.com>
> Reviewed-by: Qu Wenruo <wqu@suse.com>
> [ Change the commit message to include a proper analysis. ]
> Reviewed-by: Boris Burkov <boris@bur.io>
> Signed-off-by: Qu Wenruo <wqu@suse.com>
> ---
> fs/btrfs/qgroup.c | 7 +++++++
> 1 file changed, 7 insertions(+)
>
> diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
> index cf8dfd5c692b..a0651fbfe778 100644
> --- a/fs/btrfs/qgroup.c
> +++ b/fs/btrfs/qgroup.c
> @@ -1207,6 +1207,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
> prealloc = kzalloc_obj(*prealloc, GFP_NOFS);
> if (!prealloc) {
> ret = -ENOMEM;
> + btrfs_abort_transaction(trans, ret);
> goto out;
> }
> qgroup = add_qgroup_rb(fs_info, prealloc, BTRFS_FS_TREE_OBJECTID);
> @@ -1296,6 +1297,12 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
> * sysfs entries.
> */
> btrfs_free_qgroup_config(fs_info);
> +
> + if (quota_root) {
How about
+ if (quota_root && !list_empty("a_root->dirty_list)) {
to avoid taking the lock if not needed?
--nX
> + spin_lock(&fs_info->trans_lock);
> + list_del_init("a_root->dirty_list);
> + spin_unlock(&fs_info->trans_lock);
> + }
> btrfs_put_root(quota_root);
> }
> mutex_unlock(&fs_info->qgroup_ioctl_lock);
> --
> 2.56.0
>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 2/2] btrfs: fix use-after-free on quota enable allocation failure
2026-10-12 15:46 ` Daniel Vacek
@ 2026-10-12 17:19 ` Filipe Manana
2026-10-13 5:32 ` Daniel Vacek
2026-10-12 21:17 ` Qu Wenruo
1 sibling, 1 reply; 11+ messages in thread
From: Filipe Manana @ 2026-10-12 17:19 UTC (permalink / raw)
To: Daniel Vacek
Cc: Qu Wenruo, linux-btrfs, Yalagada Pavan Kumar,
syzbot+947286c775f432b073a8, Boris Burkov
On Mon, Oct 12, 2026 at 6:16 PM Daniel Vacek <neelx@suse.com> wrote:
>
> On Sun, 11 Oct 2026 at 10:22, Qu Wenruo <wqu@suse.com> wrote:
> > From: Yalagada Pavan Kumar <pavankumaryalagada@gmail.com>
> >
> > [BUG]
> > There is a syzbot report that a use-after-free bug is triggered after a
> > failed btrfs_quota_enable():
> >
> > loop0: detected capacity change from 32768 to 0
> > ==================================================================
> > BUG: KASAN: slab-use-after-free in __list_add_valid_or_report+0x4e/0x130 lib/list_debug.c:29
> > Read of size 8 at addr ffff888040e0a528 by task syz.0.0/5324
> >
> > CPU: 0 UID: 0 PID: 5324 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
> > Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> > Call Trace:
> > <TASK>
> > dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
> > print_address_description+0x55/0x1e0 mm/kasan/report.c:378
> > print_report+0x58/0x70 mm/kasan/report.c:482
> > kasan_report+0x117/0x150 mm/kasan/report.c:595
> > __list_add_valid_or_report+0x4e/0x130 lib/list_debug.c:29
> > __list_add_valid include/linux/list.h:104 [inline]
> > __list_add include/linux/list.h:169 [inline]
> > list_add include/linux/list.h:192 [inline]
> > list_move include/linux/list.h:345 [inline]
> > add_root_to_dirty_list+0x38b/0x470 fs/btrfs/ctree.c:232
> > btrfs_force_cow_block+0xf09/0x1bf0 fs/btrfs/ctree.c:553
> > btrfs_cow_block+0x3f1/0xaf0 fs/btrfs/ctree.c:696
> > btrfs_search_slot+0xd70/0x2d20 fs/btrfs/ctree.c:-1
> > btrfs_search_prev_slot fs/btrfs/free-space-tree.c:134 [inline]
> > remove_free_space_extent fs/btrfs/free-space-tree.c:732 [inline]
> > __btrfs_remove_from_free_space_tree fs/btrfs/free-space-tree.c:837 [inline]
> > btrfs_remove_from_free_space_tree+0x4d6/0xda0 fs/btrfs/free-space-tree.c:866
> > alloc_reserved_extent+0x4a/0x2b0 fs/btrfs/extent-tree.c:4976
> > alloc_reserved_tree_block fs/btrfs/extent-tree.c:5155 [inline]
> > run_delayed_tree_ref fs/btrfs/extent-tree.c:1815 [inline]
> > run_one_delayed_ref fs/btrfs/extent-tree.c:1851 [inline]
> > btrfs_run_delayed_refs_for_head fs/btrfs/extent-tree.c:2058 [inline]
> > __btrfs_run_delayed_refs+0x18b5/0x43b0 fs/btrfs/extent-tree.c:2134
> > btrfs_run_delayed_refs+0xdc/0x2a0 fs/btrfs/extent-tree.c:2246
> > btrfs_commit_transaction+0x28a/0x3170 fs/btrfs/transaction.c:2267
> > sync_filesystem+0x1d2/0x240 fs/sync.c:66
> > btrfs_reconfigure+0x2dc/0x2100 fs/btrfs/super.c:1511
> > reconfigure_super+0x232/0x8f0 fs/super.c:1128
> > do_remount fs/namespace.c:3410 [inline]
> > path_mount+0xd4e/0x1050 fs/namespace.c:4160
> > do_mount fs/namespace.c:4181 [inline]
> > __do_sys_mount fs/namespace.c:4397 [inline]
> > __se_sys_mount+0x31d/0x420 fs/namespace.c:4374
> > do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
> > do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
> > entry_SYSCALL_64_after_hwframe+0x77/0x7f
> > RIP: 0033:0x7f9c8dd9f3ca
> > </TASK>
> >
> > Allocated by task 5323:
> > kasan_save_stack mm/kasan/common.c:57 [inline]
> > kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
> > poison_kmalloc_redzone mm/kasan/common.c:409 [inline]
> > __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:426
> > kasan_kmalloc include/linux/kasan.h:263 [inline]
> > __kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
> > _kmalloc_noprof include/linux/slab.h:991 [inline]
> > _kzalloc_noprof include/linux/slab.h:1312 [inline]
> > btrfs_alloc_root+0x75/0x840 fs/btrfs/disk-io.c:638
> > btrfs_create_tree+0xa8/0x5c0 fs/btrfs/disk-io.c:832
> > btrfs_quota_enable+0x3d6/0x1e20 fs/btrfs/qgroup.c:1074
> > btrfs_ioctl_quota_ctl+0x186/0x1f0 fs/btrfs/ioctl.c:3581
> > vfs_ioctl fs/ioctl.c:51 [inline]
> > __do_sys_ioctl fs/ioctl.c:597 [inline]
> > __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
> > do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
> > do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
> > entry_SYSCALL_64_after_hwframe+0x77/0x7f
> >
> > Freed by task 5323:
> > kasan_save_stack mm/kasan/common.c:57 [inline]
> > kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
> > kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
> > poison_slab_object mm/kasan/common.c:264 [inline]
> > __kasan_slab_free+0x5c/0x80 mm/kasan/common.c:296
> > kasan_slab_free include/linux/kasan.h:235 [inline]
> > slab_free_hook mm/slub.c:2748 [inline]
> > slab_free mm/slub.c:6508 [inline]
> > kfree+0x1c5/0x650 mm/slub.c:6801
> > btrfs_quota_enable+0x1208/0x1e20 fs/btrfs/qgroup.c:1292
> > btrfs_ioctl_quota_ctl+0x186/0x1f0 fs/btrfs/ioctl.c:3581
> > vfs_ioctl fs/ioctl.c:51 [inline]
> > __do_sys_ioctl fs/ioctl.c:597 [inline]
> > __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
> > do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
> > do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
> > entry_SYSCALL_64_after_hwframe+0x77/0x7f
> >
> > [CAUSE]
> > During btrfs_quota_enable(), a temporary quota_root is created, then
> > qgroup items are created for each subvolume and inserted into that root.
> >
> > If there are enough subvolumes, the level of quota_root will be bumped,
> > and add_root_to_dirty_list() will be called, adding
> > quota_root->dirty_list into fs_info->dirty_cowonly_roots.
> >
> > But if a problem is hit during btrfs_quota_enable() later, that
> > quota_root will be released through btrfs_put_root(), causing the whole
> > quota_root structure to be released, while leaving
> > fs_info->dirty_cowonly_roots still pointing to the released
> > quota_root->dirty_list.
> >
> > Later access through fs_info->dirty_cowonly_roots will trigger a
> > use-after-free bug.
> >
> > [FIX]
> > We can just call list_del_init(&root->dirty_list) when
> > btrfs_quota_enable() failed.
> >
> > Also to follow the existing error handling behavior, abort the
> > transaction when the memory preallocation failed.
> >
> > Reported-by: syzbot+947286c775f432b073a8@syzkaller.appspotmail.com
> > Link: https://lore.kernel.org/linux-btrfs/6ac5a5e1.9ad129c8.3ce5b3.0010.GAE@google.com/
> > Closes: https://syzkaller.appspot.com/bug?extid=947286c775f432b073a8
> > Signed-off-by: Yalagada Pavan Kumar <pavankumaryalagada@gmail.com>
> > Reviewed-by: Qu Wenruo <wqu@suse.com>
> > [ Change the commit message to include a proper analysis. ]
> > Reviewed-by: Boris Burkov <boris@bur.io>
> > Signed-off-by: Qu Wenruo <wqu@suse.com>
> > ---
> > fs/btrfs/qgroup.c | 7 +++++++
> > 1 file changed, 7 insertions(+)
> >
> > diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
> > index cf8dfd5c692b..a0651fbfe778 100644
> > --- a/fs/btrfs/qgroup.c
> > +++ b/fs/btrfs/qgroup.c
> > @@ -1207,6 +1207,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
> > prealloc = kzalloc_obj(*prealloc, GFP_NOFS);
> > if (!prealloc) {
> > ret = -ENOMEM;
> > + btrfs_abort_transaction(trans, ret);
> > goto out;
> > }
> > qgroup = add_qgroup_rb(fs_info, prealloc, BTRFS_FS_TREE_OBJECTID);
> > @@ -1296,6 +1297,12 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
> > * sysfs entries.
> > */
> > btrfs_free_qgroup_config(fs_info);
> > +
> > + if (quota_root) {
>
> How about
>
> + if (quota_root && !list_empty("a_root->dirty_list)) {
>
> to avoid taking the lock if not needed?
What's the point?
Is an uncommon error path in a sporadically used ioctl worth optimizing?
>
> --nX
>
> > + spin_lock(&fs_info->trans_lock);
> > + list_del_init("a_root->dirty_list);
> > + spin_unlock(&fs_info->trans_lock);
> > + }
> > btrfs_put_root(quota_root);
> > }
> > mutex_unlock(&fs_info->qgroup_ioctl_lock);
> > --
> > 2.56.0
> >
> >
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 1/2] btrfs: prevent use-after-free in btrfs_transaction::switch_commits
2026-10-12 10:41 ` Filipe Manana
@ 2026-10-12 21:13 ` Qu Wenruo
2026-10-12 22:11 ` Qu Wenruo
0 siblings, 1 reply; 11+ messages in thread
From: Qu Wenruo @ 2026-10-12 21:13 UTC (permalink / raw)
To: Filipe Manana, Qu Wenruo; +Cc: linux-btrfs, Boris Burkov
在 2026/10/12 21:11, Filipe Manana 写道:
> On Sun, Oct 11, 2026 at 9:22 AM Qu Wenruo <wqu@suse.com> wrote:
>>
>> If a transaction is aborted during committing,
>> btrfs_transaction::switch_commits can be non-empty, but the
>> btrfs_transaction structure can be freed at the last
>> btrfs_put_transaction() inside cleanup_transaction().
>>
>> The btrfs_root::dirty_list entry still linked into switch_commits will
>> point to already released memory, causing use-after-free.
>>
>> Address this problem by manually deleting all entries inside
>> btrfs_transaction::switch_commits.
>>
>> And since we're here, also release the dirty_log_pages and qgroup
>> swapped blocks for that root, which are only released during a
>> successful switch_commit_roots() call.
>>
>> Reviewed-by: Boris Burkov <boris@bur.io>
>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>> ---
>> fs/btrfs/disk-io.c | 10 ++++++++++
>> 1 file changed, 10 insertions(+)
>>
>> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
>> index 66b054eb98c6..02adda065097 100644
>> --- a/fs/btrfs/disk-io.c
>> +++ b/fs/btrfs/disk-io.c
>> @@ -4966,6 +4966,16 @@ void btrfs_cleanup_one_transaction(struct btrfs_transaction *cur_trans)
>> list_del_init(&dev->post_commit_list);
>> }
>>
>> + while (!list_empty(&cur_trans->switch_commits)) {
>> + struct btrfs_root *root;
>> +
>> + root = list_first_entry(&cur_trans->switch_commits,
>> + struct btrfs_root, dirty_list);
>> + list_del_init(&root->dirty_list);
>> + btrfs_extent_io_tree_release(&root->dirty_log_pages);
>> + btrfs_qgroup_clean_swapped_blocks(root);
>> + }
>
> Sashiko has some comments which seem valid to me, roots still in the
> dirty_cowonly_roots will get their dirty_log_pages and swapped blocks
> leaked.
That's in fact an existing problem.
If the root is still in dirty_cowonly_roots list, it will be the same no
matter if we have this patchset or not.
In previous attempts I tried to address this in btrfs_put_root(), but
that has extra problems like some call sites can end a trans then call
btrfs_put_root(), which requires extra locking.
I tend to address this problem in a dedicated patch at close_tree() time
instead.
Thanks,
Qu
>
>> +
>> btrfs_destroy_delayed_refs(cur_trans);
>>
>> cur_trans->state = TRANS_STATE_COMMIT_START;
>> --
>> 2.56.0
>>
>>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 2/2] btrfs: fix use-after-free on quota enable allocation failure
2026-10-12 15:46 ` Daniel Vacek
2026-10-12 17:19 ` Filipe Manana
@ 2026-10-12 21:17 ` Qu Wenruo
1 sibling, 0 replies; 11+ messages in thread
From: Qu Wenruo @ 2026-10-12 21:17 UTC (permalink / raw)
To: Daniel Vacek, Qu Wenruo
Cc: linux-btrfs, Yalagada Pavan Kumar, syzbot+947286c775f432b073a8,
Boris Burkov
在 2026/10/13 02:16, Daniel Vacek 写道:
> On Sun, 11 Oct 2026 at 10:22, Qu Wenruo <wqu@suse.com> wrote:
>> From: Yalagada Pavan Kumar <pavankumaryalagada@gmail.com>
>>
>> [BUG]
>> There is a syzbot report that a use-after-free bug is triggered after a
>> failed btrfs_quota_enable():
>>
>> loop0: detected capacity change from 32768 to 0
>> ==================================================================
>> BUG: KASAN: slab-use-after-free in __list_add_valid_or_report+0x4e/0x130 lib/list_debug.c:29
>> Read of size 8 at addr ffff888040e0a528 by task syz.0.0/5324
>>
>> CPU: 0 UID: 0 PID: 5324 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
>> Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
>> Call Trace:
>> <TASK>
>> dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
>> print_address_description+0x55/0x1e0 mm/kasan/report.c:378
>> print_report+0x58/0x70 mm/kasan/report.c:482
>> kasan_report+0x117/0x150 mm/kasan/report.c:595
>> __list_add_valid_or_report+0x4e/0x130 lib/list_debug.c:29
>> __list_add_valid include/linux/list.h:104 [inline]
>> __list_add include/linux/list.h:169 [inline]
>> list_add include/linux/list.h:192 [inline]
>> list_move include/linux/list.h:345 [inline]
>> add_root_to_dirty_list+0x38b/0x470 fs/btrfs/ctree.c:232
>> btrfs_force_cow_block+0xf09/0x1bf0 fs/btrfs/ctree.c:553
>> btrfs_cow_block+0x3f1/0xaf0 fs/btrfs/ctree.c:696
>> btrfs_search_slot+0xd70/0x2d20 fs/btrfs/ctree.c:-1
>> btrfs_search_prev_slot fs/btrfs/free-space-tree.c:134 [inline]
>> remove_free_space_extent fs/btrfs/free-space-tree.c:732 [inline]
>> __btrfs_remove_from_free_space_tree fs/btrfs/free-space-tree.c:837 [inline]
>> btrfs_remove_from_free_space_tree+0x4d6/0xda0 fs/btrfs/free-space-tree.c:866
>> alloc_reserved_extent+0x4a/0x2b0 fs/btrfs/extent-tree.c:4976
>> alloc_reserved_tree_block fs/btrfs/extent-tree.c:5155 [inline]
>> run_delayed_tree_ref fs/btrfs/extent-tree.c:1815 [inline]
>> run_one_delayed_ref fs/btrfs/extent-tree.c:1851 [inline]
>> btrfs_run_delayed_refs_for_head fs/btrfs/extent-tree.c:2058 [inline]
>> __btrfs_run_delayed_refs+0x18b5/0x43b0 fs/btrfs/extent-tree.c:2134
>> btrfs_run_delayed_refs+0xdc/0x2a0 fs/btrfs/extent-tree.c:2246
>> btrfs_commit_transaction+0x28a/0x3170 fs/btrfs/transaction.c:2267
>> sync_filesystem+0x1d2/0x240 fs/sync.c:66
>> btrfs_reconfigure+0x2dc/0x2100 fs/btrfs/super.c:1511
>> reconfigure_super+0x232/0x8f0 fs/super.c:1128
>> do_remount fs/namespace.c:3410 [inline]
>> path_mount+0xd4e/0x1050 fs/namespace.c:4160
>> do_mount fs/namespace.c:4181 [inline]
>> __do_sys_mount fs/namespace.c:4397 [inline]
>> __se_sys_mount+0x31d/0x420 fs/namespace.c:4374
>> do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
>> do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>> RIP: 0033:0x7f9c8dd9f3ca
>> </TASK>
>>
>> Allocated by task 5323:
>> kasan_save_stack mm/kasan/common.c:57 [inline]
>> kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
>> poison_kmalloc_redzone mm/kasan/common.c:409 [inline]
>> __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:426
>> kasan_kmalloc include/linux/kasan.h:263 [inline]
>> __kmalloc_cache_noprof+0x321/0x600 mm/slub.c:5563
>> _kmalloc_noprof include/linux/slab.h:991 [inline]
>> _kzalloc_noprof include/linux/slab.h:1312 [inline]
>> btrfs_alloc_root+0x75/0x840 fs/btrfs/disk-io.c:638
>> btrfs_create_tree+0xa8/0x5c0 fs/btrfs/disk-io.c:832
>> btrfs_quota_enable+0x3d6/0x1e20 fs/btrfs/qgroup.c:1074
>> btrfs_ioctl_quota_ctl+0x186/0x1f0 fs/btrfs/ioctl.c:3581
>> vfs_ioctl fs/ioctl.c:51 [inline]
>> __do_sys_ioctl fs/ioctl.c:597 [inline]
>> __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
>> do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
>> do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>>
>> Freed by task 5323:
>> kasan_save_stack mm/kasan/common.c:57 [inline]
>> kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
>> kasan_save_free_info+0x40/0x50 mm/kasan/generic.c:584
>> poison_slab_object mm/kasan/common.c:264 [inline]
>> __kasan_slab_free+0x5c/0x80 mm/kasan/common.c:296
>> kasan_slab_free include/linux/kasan.h:235 [inline]
>> slab_free_hook mm/slub.c:2748 [inline]
>> slab_free mm/slub.c:6508 [inline]
>> kfree+0x1c5/0x650 mm/slub.c:6801
>> btrfs_quota_enable+0x1208/0x1e20 fs/btrfs/qgroup.c:1292
>> btrfs_ioctl_quota_ctl+0x186/0x1f0 fs/btrfs/ioctl.c:3581
>> vfs_ioctl fs/ioctl.c:51 [inline]
>> __do_sys_ioctl fs/ioctl.c:597 [inline]
>> __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
>> do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
>> do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
>> entry_SYSCALL_64_after_hwframe+0x77/0x7f
>>
>> [CAUSE]
>> During btrfs_quota_enable(), a temporary quota_root is created, then
>> qgroup items are created for each subvolume and inserted into that root.
>>
>> If there are enough subvolumes, the level of quota_root will be bumped,
>> and add_root_to_dirty_list() will be called, adding
>> quota_root->dirty_list into fs_info->dirty_cowonly_roots.
>>
>> But if a problem is hit during btrfs_quota_enable() later, that
>> quota_root will be released through btrfs_put_root(), causing the whole
>> quota_root structure to be released, while leaving
>> fs_info->dirty_cowonly_roots still pointing to the released
>> quota_root->dirty_list.
>>
>> Later access through fs_info->dirty_cowonly_roots will trigger a
>> use-after-free bug.
>>
>> [FIX]
>> We can just call list_del_init(&root->dirty_list) when
>> btrfs_quota_enable() failed.
>>
>> Also to follow the existing error handling behavior, abort the
>> transaction when the memory preallocation failed.
>>
>> Reported-by: syzbot+947286c775f432b073a8@syzkaller.appspotmail.com
>> Link: https://lore.kernel.org/linux-btrfs/6ac5a5e1.9ad129c8.3ce5b3.0010.GAE@google.com/
>> Closes: https://syzkaller.appspot.com/bug?extid=947286c775f432b073a8
>> Signed-off-by: Yalagada Pavan Kumar <pavankumaryalagada@gmail.com>
>> Reviewed-by: Qu Wenruo <wqu@suse.com>
>> [ Change the commit message to include a proper analysis. ]
>> Reviewed-by: Boris Burkov <boris@bur.io>
>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>> ---
>> fs/btrfs/qgroup.c | 7 +++++++
>> 1 file changed, 7 insertions(+)
>>
>> diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
>> index cf8dfd5c692b..a0651fbfe778 100644
>> --- a/fs/btrfs/qgroup.c
>> +++ b/fs/btrfs/qgroup.c
>> @@ -1207,6 +1207,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
>> prealloc = kzalloc_obj(*prealloc, GFP_NOFS);
>> if (!prealloc) {
>> ret = -ENOMEM;
>> + btrfs_abort_transaction(trans, ret);
>> goto out;
>> }
>> qgroup = add_qgroup_rb(fs_info, prealloc, BTRFS_FS_TREE_OBJECTID);
>> @@ -1296,6 +1297,12 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
>> * sysfs entries.
>> */
>> btrfs_free_qgroup_config(fs_info);
>> +
>> + if (quota_root) {
>
> How about
>
> + if (quota_root && !list_empty("a_root->dirty_list)) {
>
> to avoid taking the lock if not needed?
I'd say we're already in the error path, we shouldn't bother efficiency
at all.
>
> --nX
>
>> + spin_lock(&fs_info->trans_lock);
>> + list_del_init("a_root->dirty_list);
>> + spin_unlock(&fs_info->trans_lock);
>> + }
>> btrfs_put_root(quota_root);
>> }
>> mutex_unlock(&fs_info->qgroup_ioctl_lock);
>> --
>> 2.56.0
>>
>>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 1/2] btrfs: prevent use-after-free in btrfs_transaction::switch_commits
2026-10-12 21:13 ` Qu Wenruo
@ 2026-10-12 22:11 ` Qu Wenruo
0 siblings, 0 replies; 11+ messages in thread
From: Qu Wenruo @ 2026-10-12 22:11 UTC (permalink / raw)
To: Filipe Manana, Qu Wenruo; +Cc: linux-btrfs, Boris Burkov
在 2026/10/13 07:43, Qu Wenruo 写道:
>
>
> 在 2026/10/12 21:11, Filipe Manana 写道:
>> On Sun, Oct 11, 2026 at 9:22 AM Qu Wenruo <wqu@suse.com> wrote:
>>>
>>> If a transaction is aborted during committing,
>>> btrfs_transaction::switch_commits can be non-empty, but the
>>> btrfs_transaction structure can be freed at the last
>>> btrfs_put_transaction() inside cleanup_transaction().
>>>
>>> The btrfs_root::dirty_list entry still linked into switch_commits will
>>> point to already released memory, causing use-after-free.
>>>
>>> Address this problem by manually deleting all entries inside
>>> btrfs_transaction::switch_commits.
>>>
>>> And since we're here, also release the dirty_log_pages and qgroup
>>> swapped blocks for that root, which are only released during a
>>> successful switch_commit_roots() call.
>>>
>>> Reviewed-by: Boris Burkov <boris@bur.io>
>>> Signed-off-by: Qu Wenruo <wqu@suse.com>
>>> ---
>>> fs/btrfs/disk-io.c | 10 ++++++++++
>>> 1 file changed, 10 insertions(+)
>>>
>>> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
>>> index 66b054eb98c6..02adda065097 100644
>>> --- a/fs/btrfs/disk-io.c
>>> +++ b/fs/btrfs/disk-io.c
>>> @@ -4966,6 +4966,16 @@ void btrfs_cleanup_one_transaction(struct
>>> btrfs_transaction *cur_trans)
>>> list_del_init(&dev->post_commit_list);
>>> }
>>>
>>> + while (!list_empty(&cur_trans->switch_commits)) {
>>> + struct btrfs_root *root;
>>> +
>>> + root = list_first_entry(&cur_trans->switch_commits,
>>> + struct btrfs_root, dirty_list);
>>> + list_del_init(&root->dirty_list);
>>> + btrfs_extent_io_tree_release(&root->dirty_log_pages);
>>> + btrfs_qgroup_clean_swapped_blocks(root);
>>> + }
>>
>> Sashiko has some comments which seem valid to me, roots still in the
>> dirty_cowonly_roots will get their dirty_log_pages and swapped blocks
>> leaked.
>
> That's in fact an existing problem.
>
> If the root is still in dirty_cowonly_roots list, it will be the same no
> matter if we have this patchset or not.
>
> In previous attempts I tried to address this in btrfs_put_root(), but
> that has extra problems like some call sites can end a trans then call
> btrfs_put_root(), which requires extra locking.
>
> I tend to address this problem in a dedicated patch at close_tree() time
> instead.
This is in fact not a big deal.
The truth is that after a transaction is aborted, no one will ever read
fs_info->dirty_cowonly_roots.
Even if some root is freed (only possible at unmount time though), as
long as no one is reading the list, everything is fine and there will be
no leaking memory.
But still, manually removing dirty_list from that list during
btrfs_free_fs_roots() would be more sane.
Thanks,
Qu
>
> Thanks,
> Qu
>
>>
>>> +
>>> btrfs_destroy_delayed_refs(cur_trans);
>>>
>>> cur_trans->state = TRANS_STATE_COMMIT_START;
>>> --
>>> 2.56.0
>>>
>>>
>>
>
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 2/2] btrfs: fix use-after-free on quota enable allocation failure
2026-10-12 17:19 ` Filipe Manana
@ 2026-10-13 5:32 ` Daniel Vacek
0 siblings, 0 replies; 11+ messages in thread
From: Daniel Vacek @ 2026-10-13 5:32 UTC (permalink / raw)
To: Filipe Manana
Cc: Qu Wenruo, linux-btrfs, Yalagada Pavan Kumar,
syzbot+947286c775f432b073a8, Boris Burkov
On Mon, 12 Oct 2026 at 19:20, Filipe Manana <fdmanana@kernel.org> wrote:
> On Mon, Oct 12, 2026 at 6:16 PM Daniel Vacek <neelx@suse.com> wrote:
> >
> > On Sun, 11 Oct 2026 at 10:22, Qu Wenruo <wqu@suse.com> wrote:
> > > @@ -1296,6 +1297,12 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
> > > * sysfs entries.
> > > */
> > > btrfs_free_qgroup_config(fs_info);
> > > +
> > > + if (quota_root) {
> >
> > How about
> >
> > + if (quota_root && !list_empty("a_root->dirty_list)) {
> >
> > to avoid taking the lock if not needed?
>
> What's the point?
> Is an uncommon error path in a sporadically used ioctl worth optimizing?
Why not? It's not a huge effort; it's just one simple check.
But yeah, it's not a big deal.
--nX
> >
> > --nX
> >
> > > + spin_lock(&fs_info->trans_lock);
> > > + list_del_init("a_root->dirty_list);
> > > + spin_unlock(&fs_info->trans_lock);
> > > + }
> > > btrfs_put_root(quota_root);
> > > }
> > > mutex_unlock(&fs_info->qgroup_ioctl_lock);
> > > --
> > > 2.56.0
> > >
> > >
> >
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-10-13 5:32 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-11 8:21 [PATCH v5 0/2] btrfs: fix a UAF where btrfs_root::dirty_list is freed but still referred Qu Wenruo
2026-10-11 8:21 ` [PATCH v5 1/2] btrfs: prevent use-after-free in btrfs_transaction::switch_commits Qu Wenruo
2026-10-12 10:41 ` Filipe Manana
2026-10-12 21:13 ` Qu Wenruo
2026-10-12 22:11 ` Qu Wenruo
2026-10-11 8:21 ` [PATCH v5 2/2] btrfs: fix use-after-free on quota enable allocation failure Qu Wenruo
2026-10-12 10:47 ` Filipe Manana
2026-10-12 15:46 ` Daniel Vacek
2026-10-12 17:19 ` Filipe Manana
2026-10-13 5:32 ` Daniel Vacek
2026-10-12 21:17 ` Qu Wenruo
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.