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 <stummala@codeaurora.org>
This commit is contained in:
Sahitya Tummala 2019-10-29 13:19:47 +05:30
parent 44b48b1070
commit 0f5052ff8d

View File

@ -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);