diff mbox series

[3/3] ext4: Avoid freeing inodes on dirty list

Message ID 20200421085445.5731-4-jack@suse.cz (mailing list archive)
State New, archived
Headers show
Series [1/3] fs: Avoid leaving freed inode on dirty list | expand

Commit Message

Jan Kara April 21, 2020, 8:54 a.m. UTC
When we are evicting inode with journalled data, we may race with
transaction commit in the following way:

CPU0					CPU1
jbd2_journal_commit_transaction()	evict(inode)
					  inode_io_list_del()
					  inode_wait_for_writeback()
  process BJ_Forget list
    __jbd2_journal_insert_checkpoint()
    __jbd2_journal_refile_buffer()
      __jbd2_journal_unfile_buffer()
        if (test_clear_buffer_jbddirty(bh))
          mark_buffer_dirty(bh)
	    __mark_inode_dirty(inode)
					  ext4_evict_inode(inode)
					    frees the inode

This results in use-after-free issues in the writeback code (or
the assertion added in the previous commit triggering).

Fix the problem by removing inode from writeback lists once all the page
cache is evicted and so inode cannot be added to writeback lists again.

Signed-off-by: Jan Kara <jack@suse.cz>
---
 fs/ext4/inode.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

Comments

Theodore Ts'o May 14, 2020, 2:57 p.m. UTC | #1
On Tue, Apr 21, 2020 at 10:54:45AM +0200, Jan Kara wrote:
> When we are evicting inode with journalled data, we may race with
> transaction commit in the following way:
> 
> CPU0					CPU1
> jbd2_journal_commit_transaction()	evict(inode)
> 					  inode_io_list_del()
> 					  inode_wait_for_writeback()
>   process BJ_Forget list
>     __jbd2_journal_insert_checkpoint()
>     __jbd2_journal_refile_buffer()
>       __jbd2_journal_unfile_buffer()
>         if (test_clear_buffer_jbddirty(bh))
>           mark_buffer_dirty(bh)
> 	    __mark_inode_dirty(inode)
> 					  ext4_evict_inode(inode)
> 					    frees the inode
> 
> This results in use-after-free issues in the writeback code (or
> the assertion added in the previous commit triggering).
> 
> Fix the problem by removing inode from writeback lists once all the page
> cache is evicted and so inode cannot be added to writeback lists again.
> 
> Signed-off-by: Jan Kara <jack@suse.cz>

Applied, thanks.

						- Ted
diff mbox series

Patch

diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c
index e416096fc081..d8a9d3da678c 100644
--- a/fs/ext4/inode.c
+++ b/fs/ext4/inode.c
@@ -220,6 +220,16 @@  void ext4_evict_inode(struct inode *inode)
 		ext4_begin_ordered_truncate(inode, 0);
 	truncate_inode_pages_final(&inode->i_data);
 
+	/*
+	 * For inodes with journalled data, transaction commit could have
+	 * dirtied the inode. Flush worker is ignoring it because of I_FREEING
+	 * flag but we still need to remove the inode from the writeback lists.
+	 */
+	if (!list_empty_careful(&inode->i_io_list)) {
+		WARN_ON_ONCE(!ext4_should_journal_data(inode));
+		inode_io_list_del(inode);
+	}
+
 	/*
 	 * Protect us against freezing - iput() caller didn't have to have any
 	 * protection against it