kernel BUG in generic_shutdown_super

Status: moderation: reported on 2026/11/05 11:23
Subsystems: fs
Labels: prio:normal
[Documentation on labels]
Reported-by: syzbot+9e45538fe03ba1814567@syzkaller.appspotmail.com
First crash: 8h39m, last: 8h39m
✨ AI-generated root cause summary:

In nilfs2, dirty files queued for the segment constructor hold an active inode reference taken via igrab() in nilfs_set_file_dirty(), which prevents evict_inodes() from evicting them during generic_shutdown_super() prior to nilfs_put_super(). When nilfs_put_super() calls nilfs_detach_log_writer() -> nilfs_segctor_destroy(), unprocessed dirty files remaining in sci->sc_dirty_files (e.g. after segment construction fails or aborts) are passed to nilfs_dispose_list(), which calls iput() on each inode while nilfs->ns_writer is still active and nilfs_purging is not yet set. Calling iput() under these conditions can invoke write_inode_now() or nilfs_evict_inode(), re-dirtying inodes or deferring iput to sci->sc_iput_queue without completing eviction before nilfs_put_super() returns. Consequently, busy inodes remain linked on sb->s_inodes after put_super(), triggering the CHECK_DATA_CORRUPTION(!list_empty(&sb->s_inodes)) kernel BUG at fs/super.c:733.

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

Technical Root Cause Analysis: VFS Busy Inodes BUG During NILFS2 Unmount

1. Crash Site & Context

  • Panic Location: fs/super.c:731-733 inside generic_shutdown_super():
    CHECK_DATA_CORRUPTION(!list_empty(&sb->s_inodes),
            "VFS: Busy inodes after unmount of %s (%s)",
            sb->s_id, sb->s_type->name);
    
  • Call Trace: cleanup_mnt() $\to$ deactivate_locked_super() $\to$ kill_block_super() $\to$ generic_shutdown_super() $\to$ nilfs_put_super()
  • Preceding Kernel Warning:
    NILFS (loop4): disposed unprocessed dirty file(s) when stopping log writer
    VFS: Busy inodes after unmount of loop4 (nilfs2)
    

2. Involved Files, Functions, and Data Structures

  • Files:
    • fs/super.c: Superblock shutdown logic in generic_shutdown_super().
    • fs/nilfs2/super.c: nilfs_put_super().
    • fs/nilfs2/segment.c: nilfs_detach_log_writer(), nilfs_segctor_destroy(), nilfs_dispose_list(), nilfs_segctor_write_out(), nilfs_segctor_drop_written_files().
    • fs/nilfs2/inode.c: nilfs_set_file_dirty(), nilfs_evict_inode(), __nilfs_mark_inode_dirty(), nilfs_writepages().
  • Structures & Fields:
    • struct super_block: s_inodes, s_flags (SB_ACTIVE).
    • struct the_nilfs: ns_writer, ns_dirty_files, ns_inode_lock, ns_segctor_sem.
    • struct nilfs_sc_info: sc_dirty_files, sc_iput_queue, sc_iput_work.
    • struct nilfs_inode_info: i_dirty, i_state (NILFS_I_DIRTY, NILFS_I_QUEUED, NILFS_I_BUSY, NILFS_I_UPDATED), vfs_inode.

3. Root Cause Mechanism & Invariant Violation

A. VFS Superblock Teardown Order

During unmount, generic_shutdown_super(sb) executes in the following order:

  1. shrink_dcache_for_umount(sb): Drops all dentries, pruning VFS cache references.
  2. sb->s_flags &= ~SB_ACTIVE: Clears the active superblock flag.
  3. evict_inodes(sb): Iterates over sb->s_inodes and evicts all inodes with i_count == 0. Inodes with i_count > 0 are explicitly skipped.
  4. sop->put_super(sb) $\to$ nilfs_put_super(sb): Intended to release all remaining filesystem-internal inode references (metadata files and dirty file queues).
  5. CHECK_DATA_CORRUPTION(!list_empty(&sb->s_inodes), ...): Asserts that no inodes remain on sb->s_inodes.
B. Dirty File Inode Lifecycle in NILFS2

When a file is modified (e.g. nilfs_write_end() or nilfs_page_mkwrite() $\to$ nilfs_set_file_dirty()):

  • NILFS2 calls igrab(inode) to increment inode->i_count before adding ii->i_dirty to nilfs->ns_dirty_files.
  • Later, the segment constructor collects files from ns_dirty_files into sci->sc_dirty_files.
  • Because of this igrab(), any file residing on ns_dirty_files or sci->sc_dirty_files has i_count > 0. Consequently, step 3 (evict_inodes(sb)) skips them during unmount.
C. Teardown Flaw in nilfs_detach_log_writer() and nilfs_segctor_destroy()

When nilfs_put_super() calls nilfs_detach_log_writer():

  1. It calls nilfs_segctor_destroy(nilfs->ns_writer).
  2. Inside nilfs_segctor_destroy():
    • kthread_stop(sci->sc_task) stops the worker thread.
    • flush_work(&sci->sc_iput_work) flushes any pending deferred iput calls.
    • nilfs_segctor_write_out(sci) attempts to write remaining dirty blocks. If writing out fails (e.g. segment construction aborts due to I/O error, read-only degradation, or corruption), sci->sc_dirty_files is not cleared.
    • NILFS2 detects this and logs: "disposed unprocessed dirty file(s) when stopping log writer", then calls:
      nilfs_dispose_list(nilfs, &sci->sc_dirty_files, 1);
      
  3. In nilfs_dispose_list(), iput(&ii->vfs_inode) is executed for each unprocessed inode:
    • Crucially, this runs before nilfs->ns_writer = NULL is set and before set_nilfs_purging(nilfs) is set in nilfs_detach_log_writer().
    • When iput() drops i_count to 0 under !(sb->s_flags & SB_ACTIVE):
      • If i_nlink > 0, iput_final() invokes write_inode_now(inode, 1) $\to$ nilfs_writepages() $\to$ nilfs_construct_dsync_segment().
      • In nilfs_segctor_drop_written_files(), during_mount = !(sci->sc_super->s_flags & SB_ACTIVE) evaluates to true, causing written files to be appended to sci->sc_iput_queue and scheduling sci->sc_iput_work (which was already flushed and will not run before teardown finishes).
      • If i_nlink == 0, iput_final() enters nilfs_evict_inode(). Because nilfs->ns_writer is not yet NULL, nilfs_evict_inode() starts a transaction and marks metadata (ii->i_root->ifile) dirty rather than taking the abort/clear path.
    • Furthermore, if any inode in sc_dirty_files has another reference held (or failed to be completely unlinked/evicted synchronously), iput() does not drop i_count to 0.

As a result, one or more inodes remain linked to sb->s_inodes when nilfs_put_super() returns, triggering the CHECK_DATA_CORRUPTION BUG() at fs/super.c:733.


4. Verified Findings vs. Unverified/Inconclusive Aspects

  • Verified Facts:
    1. The panic directly manifests from !list_empty(&sb->s_inodes) after nilfs_put_super().
    2. The warning "disposed unprocessed dirty file(s) when stopping log writer" at fs/nilfs2/segment.c:2831 confirms that sci->sc_dirty_files was non-empty when nilfs_segctor_destroy() entered cleanup.
    3. nilfs_dispose_list() is called while nilfs->ns_writer is still non-NULL and nilfs_purging(nilfs) is not set.
    4. In nilfs_detach_log_writer(), set_nilfs_purging(nilfs) is only enabled after nilfs_segctor_destroy() completes.
  • Inconclusive / Unverified Aspects:
    • The exact failure condition during segment construction (e.g. injected error, -EROFS, or -ENOSPC) that caused nilfs_segctor_write_out() to leave files in sc_dirty_files was not recorded in the crash report.
    • Whether the remaining busy inode on sb->s_inodes was the dirty regular file itself (due to deferred iput via sc_iput_queue / during_mount), or a secondary metadata file (root->ifile / btnc_inode) dirtied during eviction cannot be determined from the register dump alone without a live memory dump of sb->s_inodes.
▶ ▼ ✨ AI Jobs (1)
ID Workflow Result Correct Bug Created Started Finished Revision
0a389876 assessment-security DenialOfService: ✅ Exploitable: ❌ FilesystemTrigger: ✅ FuzzingArtifact: ❌ MemoryCorruption: ❌ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ❌ UserNamespace: ❌ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ kernel BUG in generic_shutdown_super 2026/11/05 03:22 2026/11/05 03:22 2026/11/05 04:41 73438ed0

Sample crash report:
NILFS (loop4): disposed unprocessed dirty file(s) when stopping log writer
VFS: Busy inodes after unmount of loop4 (nilfs2)
------------[ cut here ]------------
kernel BUG at fs/super.c:733!
Oops: invalid opcode: 0000 [#1] SMP KASAN PTI
CPU: 0 UID: 0 PID: 5635 Comm: syz-executor Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/15/2026
RIP: 0010:generic_shutdown_super+0x2ca/0x2d0 fs/super.c:731
Code: 03 42 80 3c 28 00 74 08 4c 89 f7 e8 70 c3 eb ff 49 8b 16 48 81 c3 c0 06 00 00 48 c7 c7 e0 4d 40 8c 48 89 de e8 67 08 d2 fe 90 <0f> 0b 0f 1f 40 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
RSP: 0018:ffffc90004687d10 EFLAGS: 00010246
RAX: 0000000000000030 RBX: ffff888076bb06c0 RCX: b6550c5770e32b00
RDX: 0000000000000000 RSI: 8000000000000000 RDI: 0000000000000000
RBP: 1ffff1100ed760fa R08: ffffc90004687a87 R09: 1ffff920008d0f50
R10: dffffc0000000000 R11: fffff520008d0f51 R12: 0000000000000000
R13: dffffc0000000000 R14: ffffffff90728ae0 R15: ffff888076bb07d0
FS:  0000555589c0c500(0000) GS:ffff8881233f9000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f49d5acb000 CR3: 000000005d99c000 CR4: 00000000003526f0
Call Trace:
 <TASK>
 kill_block_super+0x44/0xa0 fs/super.c:1852
 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+0x32c/0x530 arch/x86/entry/syscall_64.c:107
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x00007fe2a29a0d57
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:00007ffda26f7da8 EFLAGS: 00000246 ORIG_RAX: 00000000000000a6
RAX: 0000000000000000 RBX: 00007fe2a2a36fb3 RCX: 00007fe2a29a0d57
RDX: 0000000000000000 RSI: 0000000000000009 RDI: 00007ffda26f7e60
RBP: 00007ffda26f7e60 R08: 00007ffda26f8e60 R09: 00000000ffffffff
R10: 0000000000000000 R11: 0000000000000246 R12: 00007ffda26f8ef0
R13: 00007fe2a2a36fb3 R14: 000000000001693d R15: 00007ffda26f8f30
 </TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:generic_shutdown_super+0x2ca/0x2d0 fs/super.c:731
Code: 03 42 80 3c 28 00 74 08 4c 89 f7 e8 70 c3 eb ff 49 8b 16 48 81 c3 c0 06 00 00 48 c7 c7 e0 4d 40 8c 48 89 de e8 67 08 d2 fe 90 <0f> 0b 0f 1f 40 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90
RSP: 0018:ffffc90004687d10 EFLAGS: 00010246
RAX: 0000000000000030 RBX: ffff888076bb06c0 RCX: b6550c5770e32b00
RDX: 0000000000000000 RSI: 8000000000000000 RDI: 0000000000000000
RBP: 1ffff1100ed760fa R08: ffffc90004687a87 R09: 1ffff920008d0f50
R10: dffffc0000000000 R11: fffff520008d0f51 R12: 0000000000000000
R13: dffffc0000000000 R14: ffffffff90728ae0 R15: ffff888076bb07d0
FS:  0000555589c0c500(0000) GS:ffff8881234f9000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fdc24b1b988 CR3: 000000005d99c000 CR4: 00000000003526f0

Crashes (1):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/05 03:22 linux-next 8b7179210b22 2ee8459f .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-linux-next-kasan-gce-root kernel BUG in generic_shutdown_super
* Struck through repros no longer work on HEAD.