There is a race condition generic_shutdown_super() and __btrfs_run_defrag_inode(). Consider the following scenario:
umount thread: btrfs-cleaner thread: btrfs_run_delayed_iputs() ->run_delayed_iput_locked() ->iput(inode) // Here the inode (ie ino 261) will be cleared and freed btrfs_kill_super() ->generic_shutdown_super() btrfs_run_defrag_inodes() ->__btrfs_run_defrag_inode() ->btrfs_iget(ino) // The inode 261 was recreated with i_count=1 // and added to the sb list ->evict_inodes(sb) // After some work // inode 261 was added ->iput(inode) // to the dispose list ->iput_funal() ->evict(inode) ->evict(inode)
Now, we have two threads simultaneously evicting the same inode, which led to a bug.
The above behavior can be confirmed by the log I added for debugging and the log printed when BUG was triggered. Due to space limitations, I cannot paste the full diff and here is a brief describtion.
First, within __btrfs_run_defrag_inode(), set inode->i_state |= (1<<19) just before calling iput(). Within the dispose_list(), check the flag, if the flag was set, then pr_info("bug! double evict! crash will happen! state is 0x%lx\n", inode->i_state);
Here is the printed log when the BUG was triggered: [ 190.686726][ T2336] bug! double evict! crash will happen! state is 0x80020 [ 190.687647][ T2336] ------------[ cut here ]------------ [ 190.688294][ T2336] kernel BUG at fs/inode.c:626! [ 190.688939][ T2336] Oops: invalid opcode: 0000 [#1] PREEMPT SMP KASAN NOPTI [ 190.689792][ T2336] CPU: 1 PID: 2336 Comm: a.out Not tainted 6.10.0-rc2-00223-g0c529ab65ef8-dirty #109 [ 190.690894][ T2336] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.2-debian-1.16.2-1 04/01/2014 [ 190.692111][ T2336] RIP: 0010:clear_inode+0x15b/0x190 // some logs... [ 190.704501][ T2336] btrfs_evict_inode+0x529/0xe80 [ 190.706966][ T2336] evict+0x2ed/0x6c0 [ 190.707209][ T2336] dispose_list+0x62/0x260 [ 190.707490][ T2336] evict_inodes+0x34e/0x450
To prevent this behavior, we need to set BTRFS_FS_CLOSING_START before kill_anon_super() to ensure that btrfs_run_defrag_inodes() doesn't continue working after unmount.
Reported-and-tested-by: syzbot+67ba3c42bcbb4665d3ad@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=67ba3c42bcbb4665d3ad CC: stable@vger.kernel.org Fixes: c146afad2c7f ("Btrfs: mount ro and remount support") Signed-off-by: Julian Sun sunjunchao2870@gmail.com --- fs/btrfs/super.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c index f05cce7c8b8d..f7e87fe583ab 100644 --- a/fs/btrfs/super.c +++ b/fs/btrfs/super.c @@ -2093,6 +2093,7 @@ static int btrfs_get_tree(struct fs_context *fc) static void btrfs_kill_super(struct super_block *sb) { struct btrfs_fs_info *fs_info = btrfs_sb(sb); + set_bit(BTRFS_FS_CLOSING_START, &fs_info->flags); kill_anon_super(sb); btrfs_free_fs_info(fs_info); }