* [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
* 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 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 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
* [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 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 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
* 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
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.