WARNING in btrfs_put_root

Status: moderation: reported on 2026/11/08 03:40
Subsystems: btrfs
Labels: prio:normal
[Documentation on labels]
Reported-by: syzbot+819d6f2a57af1519c023@syzkaller.appspotmail.com
First crash: 1h16m, last: 1h16m
✨ AI-generated root cause summary:

When a Btrfs filesystem encounters a transaction abort (reported in state EA), inodes being evicted during unmount (particularly via delayed iputs in close_ctree(), which runs after btrfs_error_commit_super()) fail to persist their delayed inode updates in btrfs_commit_inode_delayed_inode() because transaction joining fails. In btrfs_evict_inode(), this error causes execution to jump directly to the out label, bypassing btrfs_kill_delayed_inode_items(). Because the delayed items remain uncleaned, __btrfs_release_delayed_node() re-queues the delayed node into delayed_root->node_list and retains a reference, preventing refcount_dec_and_test() from hitting zero and skipping xa_erase(&root->delayed_nodes, ...). When the root is finally freed in btrfs_put_root(), root->delayed_nodes is still non-empty, triggering WARN_ON(!xa_empty(&root->delayed_nodes)).

▶ ▼ ✨ Full AI root cause analysis
Based on kernel commit d164c7d754f0:

Root Cause Analysis

1. Context and Involved Entities

  • Source Files:
    • fs/btrfs/inode.c: Inode eviction handling (btrfs_evict_inode()).
    • fs/btrfs/disk-io.c: Filesystem teardown and root destruction (close_ctree(), btrfs_put_root(), btrfs_free_fs_info()).
    • fs/btrfs/delayed-inode.c: Delayed node tracking, commit, and release (btrfs_commit_inode_delayed_inode(), btrfs_remove_delayed_node(), __btrfs_release_delayed_node()).
  • Key Structs and Fields:
    • struct btrfs_root::delayed_nodes: XArray tracking delayed nodes associated with inodes in that root.
    • struct btrfs_inode::delayed_node: Pointer caching the associated struct btrfs_delayed_node.
    • struct btrfs_delayed_node::refs: Reference count of the delayed node (one held by btrfs_inode::delayed_node, one held by btrfs_delayed_root::node_list when count > 0, plus any transient caller references).
    • struct btrfs_delayed_node::count: Count of pending delayed items/updates.
    • struct btrfs_fs_info::delayed_root: Global delayed root tracking lists (node_list, prepare_list).

2. Triggering Sequence and Lifecycle Invariant Violation

  1. Transaction Abort State (state EA): During filesystem operation or unmount, an error occurred and the current transaction was aborted (BTRFS_FS_STATE_TRANS_ABORTED set, reflected in the state EA log message before unmount).

  2. Unmount Sequence in close_ctree(): During unmount (generic_shutdown_super() $\to$ btrfs_put_super() $\to$ close_ctree()):

    • At line 4394 of fs/btrfs/disk-io.c, close_ctree() checks BTRFS_FS_ERROR(fs_info) and invokes btrfs_error_commit_super(fs_info). This calls btrfs_cleanup_transaction(fs_info), which invokes btrfs_destroy_delayed_inodes(fs_info) to destroy any delayed items currently in fs_info->delayed_root.node_list and verifies the list with btrfs_assert_delayed_root_empty(fs_info).
    • Subsequently, at lines 4455 and 4478 of fs/btrfs/disk-io.c, close_ctree() calls btrfs_run_delayed_iputs(fs_info). This processes pending delayed iputs generated by preceding operations and ordered extents, calling iput(&inode->vfs_inode).
  3. Error Handling Defect in btrfs_evict_inode(): When iput() drops the last reference to an inode, VFS calls btrfs_evict_inode():

    ret = btrfs_commit_inode_delayed_inode(BTRFS_I(inode));
    if (ret)
            goto out;
    
    btrfs_kill_delayed_inode_items(BTRFS_I(inode));
    ...
    out:
    btrfs_remove_delayed_node(BTRFS_I(inode));
    clear_inode:
    clear_inode(inode);
    
    • In btrfs_commit_inode_delayed_inode(), btrfs_join_transaction() fails with -EROFS because the transaction has been aborted.
    • Because ret < 0, btrfs_evict_inode() immediately executes goto out;, bypassing btrfs_kill_delayed_inode_items(). The delayed node retains BTRFS_DELAYED_NODE_INODE_DIRTY and its item count (delayed_node->count > 0).
  4. Reference Imbalance and Omission from XArray Erase: At label out:, btrfs_evict_inode() invokes btrfs_remove_delayed_node():

    void btrfs_remove_delayed_node(struct btrfs_inode *inode)
    {
            struct btrfs_delayed_node *delayed_node;
    
            delayed_node = READ_ONCE(inode->delayed_node);
            if (!delayed_node)
                    return;
    
            inode->delayed_node = NULL;
            btrfs_release_delayed_node(delayed_node, &delayed_node->inode_cache_tracker);
    }
    

    In __btrfs_release_delayed_node():

    mutex_lock(&delayed_node->mutex);
    if (delayed_node->count)
            btrfs_queue_delayed_node(delayed_root, delayed_node, mod);
    else
            btrfs_dequeue_delayed_node(delayed_root, delayed_node);
    mutex_unlock(&delayed_node->mutex);
    
    btrfs_delayed_node_ref_tracker_free(delayed_node, tracker);
    if (refcount_dec_and_test(&delayed_node->refs)) {
            struct btrfs_root *root = delayed_node->root;
    
            xa_erase(&root->delayed_nodes, delayed_node->inode_id);
            ...
            kmem_cache_free(delayed_node_cache, delayed_node);
    }
    
    • Because delayed_node->count remains $> 0$, btrfs_queue_delayed_node() queues the node into delayed_root->node_list (and increments node->refs if not already queued).
    • When refcount_dec_and_test(&delayed_node->refs) decrements the reference held by inode->delayed_node, delayed_node->refs does not reach zero because delayed_root->node_list still holds a reference.
    • As a result, xa_erase(&root->delayed_nodes, delayed_node->inode_id) is skipped, leaving the delayed node stored in root->delayed_nodes.
    • The inode itself is cleared and deallocated, removing it from root->inodes.
    • Because btrfs_destroy_delayed_inodes() in close_ctree() ran prior to btrfs_run_delayed_iputs(), no further cleanup pass iterates or kills delayed nodes.
  5. Failure Manifestation: After close_ctree() finishes, btrfs_kill_super() calls btrfs_free_fs_info(), which calls btrfs_put_root(fs_info->fs_root). When dropping the last reference to the root:

    if (WARN_ON(!xa_empty(&root->inodes)))
            xa_destroy(&root->inodes);
    if (WARN_ON(!xa_empty(&root->delayed_nodes)))
            xa_destroy(&root->delayed_nodes);
    

    xa_empty(&root->inodes) is true (all inodes were destroyed), but xa_empty(&root->delayed_nodes) is false, triggering the reported warning at fs/btrfs/disk-io.c:1844.

▶ ▼ ✨ AI Jobs (1)
ID Workflow Result Correct Bug Created Started Finished Revision
55041511 assessment-security DenialOfService: ✅ Exploitable: ❌ FilesystemTrigger: ✅ FuzzingArtifact: ❌ MemoryCorruption: ❌ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ❌ UserNamespace: ❌ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ WARNING in btrfs_put_root 2026/11/08 02:36 2026/11/08 02:37 2026/11/08 03:30 5cecaf1f

Sample crash report:
BTRFS info (device loop0 state EA): last unmount of filesystem c5fa74be-7b06-468c-a5da-b62cb7064054
------------[ cut here ]------------
!xa_empty(&root->delayed_nodes)
WARNING: fs/btrfs/disk-io.c:1844 at btrfs_put_root+0x1f1/0x250 fs/btrfs/disk-io.c:1844, CPU#1: syz-executor/5628
Modules linked in:
CPU: 1 UID: 0 PID: 5628 Comm: syz-executor Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 11/06/2026
RIP: 0010:btrfs_put_root+0x1f1/0x250 fs/btrfs/disk-io.c:1844
Code: cb 0e fe e8 91 88 a3 fd 48 8d bb 40 05 00 00 90 0f 0b 90 e8 41 8f de 07 e9 a3 fe ff ff e8 77 88 a3 fd 48 8d bb 90 05 00 00 90 <0f> 0b 90 e8 27 8f de 07 e9 b5 fe ff ff e8 5d 88 a3 fd 90 0f 0b 90
RSP: 0018:ffffc9000301fd18 EFLAGS: 00010293
RAX: ffffffff84258649 RBX: ffff88805339e000 RCX: ffff88807876be80
RDX: 0000000000000000 RSI: 0000000000000001 RDI: ffff88805339e590
RBP: 0000000000000001 R08: ffff88805339e62b R09: 1ffff1100a673cc5
R10: dffffc0000000000 R11: ffffed100a673cc6 R12: dffffc0000000000
R13: ffff888057bd3558 R14: ffff88805339e5d8 R15: dffffc0000000000
FS:  000055556ef27500(0000) GS:ffff88812350a000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 0000001b34916ff8 CR3: 0000000057876000 CR4: 0000000000350ef0
Call Trace:
 <TASK>
 btrfs_free_fs_info+0x252/0x360 fs/btrfs/disk-io.c:1257
 deactivate_locked_super+0xbe/0x110 fs/super.c:558
 cleanup_mnt+0x204/0x2a0 fs/namespace.c:1396
 task_work_run+0x1d9/0x260 kernel/task_work.c:233
 resume_user_mode_work include/linux/resume_user_mode.h:50 [inline]
 __exit_to_user_mode_loop kernel/entry/common.c:70 [inline]
 exit_to_user_mode_loop+0x204/0x770 kernel/entry/common.c:101
 __exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline]
 syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:230 [inline]
 syscall_exit_to_user_mode include/linux/entry-common.h:343 [inline]
 do_syscall_64+0x328/0x520 arch/x86/entry/syscall_64.c:89
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f22bd3a0d57
Code: a2 c7 05 9c a9 25 00 00 00 00 00 eb 96 e8 e1 12 00 00 90 31 f6 e9 09 00 00 00 66 0f 1f 84 00 00 00 00 00 b8 a6 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 01 c3 48 c7 c2 e8 ff ff ff f7 d8 64 89 02 b8
RSP: 002b:00007fff8aacb128 EFLAGS: 00000246 ORIG_RAX: 00000000000000a6
RAX: 0000000000000000 RBX: 0000000000000000 RCX: 00007f22bd3a0d57
RDX: 0000000000000000 RSI: 0000000000000009 RDI: 00007fff8aacb1e0
RBP: 00007fff8aacb1e0 R08: 00007fff8aacc1e0 R09: 00000000ffffffff
R10: 0000000000000000 R11: 0000000000000246 R12: 00007fff8aacc270
R13: 00007f22bd436fb3 R14: 0000000000020668 R15: 00007fff8aacc2b0
 </TASK>

Crashes (1):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/08 02:36 upstream d164c7d754f0 5cecaf1f .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-kasan-gce-root WARNING in btrfs_put_root
* Struck through repros no longer work on HEAD.