From 0f5052ff8da48824fb7f6236f630095cf25ab43d Mon Sep 17 00:00:00 2001 From: Sahitya Tummala Date: Tue, 29 Oct 2019 13:19:47 +0530 Subject: [PATCH] writeback: Fix NULL pointer dereference issue with inode->i_wb There is a potential race between evict() and __mark_inode_dirty() observed with EXT4 FS. The ext4/jbd2 context is marking the inode as dirty while at the same time evict() is happening on another core. Due to this race, __mark_inode_dirty() is adding the inode to bdi_writeback IO dirty list, after evict() path has removed this inode from the writeback list. This results NULL pointer dereference issue when accessing inode->i_wb from writeback thread as below - locked_inode_to_wb_and_lock_list+0x2c/0x2a0 inode_to_wb_and_lock_list+0x24/0x30 writeback_sb_inodes+0x2cc/0x444 __writeback_inodes_wb+0x78/0xbc wb_writeback+0x1c8/0x3d0 wb_workfn+0x1a8/0x4d4 process_one_work+0x1c0/0x3d4 worker_thread+0x224/0x344 kthread+0x120/0x130 ret_from_fork+0x10/0x18 Fix this by checking for the inode->i_state I_WILL_FREE or I_FREEING before and after adding to the writeback list. If it is observed to be in freeing path before adding, then simply return. If it is after adding to the writeback list, then remove this inode from the writeback list in __mark_inode_dirty() context itself. Change-Id: I1e5f15d81dae01f0dd7a108b1861adc83105ed7b Signed-off-by: Sahitya Tummala --- fs/fs-writeback.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/fs-writeback.c b/fs/fs-writeback.c index ad6b8247d625..6336e3b61609 100644 --- a/fs/fs-writeback.c +++ b/fs/fs-writeback.c @@ -2187,7 +2187,7 @@ void __mark_inode_dirty(struct inode *inode, int flags) if (inode_unhashed(inode)) goto out_unlock_inode; } - if (inode->i_state & I_FREEING) + if (inode->i_state & (I_WILL_FREE | I_FREEING)) goto out_unlock_inode; /* @@ -2217,6 +2217,12 @@ void __mark_inode_dirty(struct inode *inode, int flags) wakeup_bdi = inode_io_list_move_locked(inode, wb, dirty_list); + if (inode->i_state & (I_WILL_FREE | I_FREEING)) { + inode_io_list_del_locked(inode, wb); + spin_unlock(&wb->list_lock); + return; + } + spin_unlock(&wb->list_lock); trace_writeback_dirty_inode_enqueue(inode);