Commit f8654743 authored by Ryusuke Konishi's avatar Ryusuke Konishi Committed by Andrew Morton

nilfs2: fix use-after-free of nilfs_root in dirtying inodes via iput

During unmount process of nilfs2, nothing holds nilfs_root structure after
nilfs2 detaches its writer in nilfs_detach_log_writer().  Previously,
nilfs_evict_inode() could cause use-after-free read for nilfs_root if
inodes are left in "garbage_list" and released by nilfs_dispose_list at
the end of nilfs_detach_log_writer(), and this bug was fixed by commit
9b5a04ac ("nilfs2: fix use-after-free bug of nilfs_root in
nilfs_evict_inode()").

However, it turned out that there is another possibility of UAF in the
call path where mark_inode_dirty_sync() is called from iput():

nilfs_detach_log_writer()
  nilfs_dispose_list()
    iput()
      mark_inode_dirty_sync()
        __mark_inode_dirty()
          nilfs_dirty_inode()
            __nilfs_mark_inode_dirty()
              nilfs_load_inode_block() --> causes UAF of nilfs_root struct

This can happen after commit 0ae45f63 ("vfs: add support for a
lazytime mount option"), which changed iput() to call
mark_inode_dirty_sync() on its final reference if i_state has I_DIRTY_TIME
flag and i_nlink is non-zero.

This issue appears after commit 28a65b49 ("nilfs2: do not write dirty
data after degenerating to read-only") when using the syzbot reproducer,
but the issue has potentially existed before.

Fix this issue by adding a "purging flag" to the nilfs structure, setting
that flag while disposing the "garbage_list" and checking it in
__nilfs_mark_inode_dirty().

Unlike commit 9b5a04ac ("nilfs2: fix use-after-free bug of nilfs_root
in nilfs_evict_inode()"), this patch does not rely on ns_writer to
determine whether to skip operations, so as not to break recovery on
mount.  The nilfs_salvage_orphan_logs routine dirties the buffer of
salvaged data before attaching the log writer, so changing
__nilfs_mark_inode_dirty() to skip the operation when ns_writer is NULL
will cause recovery write to fail.  The purpose of using the cleanup-only
flag is to allow for narrowing of such conditions.

Link: https://lkml.kernel.org/r/20230728191318.33047-1-konishi.ryusuke@gmail.comSigned-off-by: default avatarRyusuke Konishi <konishi.ryusuke@gmail.com>
Reported-by: syzbot+74db8b3087f293d3a13a@syzkaller.appspotmail.com
Closes: https://lkml.kernel.org/r/000000000000b4e906060113fd63@google.com
Fixes: 0ae45f63 ("vfs: add support for a lazytime mount option")
Tested-by: default avatarRyusuke Konishi <konishi.ryusuke@gmail.com>
Cc: <stable@vger.kernel.org> # 4.0+
Signed-off-by: default avatarAndrew Morton <akpm@linux-foundation.org>
parent fac26502
...@@ -1101,9 +1101,17 @@ int nilfs_set_file_dirty(struct inode *inode, unsigned int nr_dirty) ...@@ -1101,9 +1101,17 @@ int nilfs_set_file_dirty(struct inode *inode, unsigned int nr_dirty)
int __nilfs_mark_inode_dirty(struct inode *inode, int flags) int __nilfs_mark_inode_dirty(struct inode *inode, int flags)
{ {
struct the_nilfs *nilfs = inode->i_sb->s_fs_info;
struct buffer_head *ibh; struct buffer_head *ibh;
int err; int err;
/*
* Do not dirty inodes after the log writer has been detached
* and its nilfs_root struct has been freed.
*/
if (unlikely(nilfs_purging(nilfs)))
return 0;
err = nilfs_load_inode_block(inode, &ibh); err = nilfs_load_inode_block(inode, &ibh);
if (unlikely(err)) { if (unlikely(err)) {
nilfs_warn(inode->i_sb, nilfs_warn(inode->i_sb,
......
...@@ -2845,6 +2845,7 @@ void nilfs_detach_log_writer(struct super_block *sb) ...@@ -2845,6 +2845,7 @@ void nilfs_detach_log_writer(struct super_block *sb)
nilfs_segctor_destroy(nilfs->ns_writer); nilfs_segctor_destroy(nilfs->ns_writer);
nilfs->ns_writer = NULL; nilfs->ns_writer = NULL;
} }
set_nilfs_purging(nilfs);
/* Force to free the list of dirty files */ /* Force to free the list of dirty files */
spin_lock(&nilfs->ns_inode_lock); spin_lock(&nilfs->ns_inode_lock);
...@@ -2857,4 +2858,5 @@ void nilfs_detach_log_writer(struct super_block *sb) ...@@ -2857,4 +2858,5 @@ void nilfs_detach_log_writer(struct super_block *sb)
up_write(&nilfs->ns_segctor_sem); up_write(&nilfs->ns_segctor_sem);
nilfs_dispose_list(nilfs, &garbage_list, 1); nilfs_dispose_list(nilfs, &garbage_list, 1);
clear_nilfs_purging(nilfs);
} }
...@@ -29,6 +29,7 @@ enum { ...@@ -29,6 +29,7 @@ enum {
THE_NILFS_DISCONTINUED, /* 'next' pointer chain has broken */ THE_NILFS_DISCONTINUED, /* 'next' pointer chain has broken */
THE_NILFS_GC_RUNNING, /* gc process is running */ THE_NILFS_GC_RUNNING, /* gc process is running */
THE_NILFS_SB_DIRTY, /* super block is dirty */ THE_NILFS_SB_DIRTY, /* super block is dirty */
THE_NILFS_PURGING, /* disposing dirty files for cleanup */
}; };
/** /**
...@@ -208,6 +209,7 @@ THE_NILFS_FNS(INIT, init) ...@@ -208,6 +209,7 @@ THE_NILFS_FNS(INIT, init)
THE_NILFS_FNS(DISCONTINUED, discontinued) THE_NILFS_FNS(DISCONTINUED, discontinued)
THE_NILFS_FNS(GC_RUNNING, gc_running) THE_NILFS_FNS(GC_RUNNING, gc_running)
THE_NILFS_FNS(SB_DIRTY, sb_dirty) THE_NILFS_FNS(SB_DIRTY, sb_dirty)
THE_NILFS_FNS(PURGING, purging)
/* /*
* Mount option operations * Mount option operations
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment