ext4_add_complete_io() queues the inode's i_rsv_conversion_work without holding an extra reference to the inode. If the inode is unlinked and evicted before the delayed work runs, the inode slab object gets freed. When ext4_do_flush_completed_IO() later accesses io_end->inode, it is accessing a freed/reallocated object -- a use-after-free detected by KASAN. Take igrab(inode) when first queueing the work (only when list is empty to avoid duplicate references), and drop the reference in ext4_do_flush_completed_IO() after processing all io_ends. A per-inode bit (i_rsv_need_iput) tracks whether the extra reference was taken. This is a defense-in-depth fix complementary to the upstream fix c678bdc99875 ("ext4: fix inode use after free in ext4_end_io_rsv_work()") which adds consistency checks in ext4_io_end_defer_completion(). Both fixes address the same root race but from different angles. Fixes: ce51afb8cc5e ("ext4: abort journal on data writeback failure if in data_err=abort mode") Reported-by: Longxing Li Signed-off-by: Longxing Li --- fs/ext4/ext4.h | 2 ++ fs/ext4/page-io.c | 15 +++++++++++++-- fs/ext4/super.c | 1 + 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h index 56112f201cac..239e6c0a3d3c 100644 --- a/fs/ext4/ext4.h +++ b/fs/ext4/ext4.h @@ -1179,6 +1179,8 @@ struct ext4_inode_info { /* Lock protecting lists below */ spinlock_t i_completed_io_lock; + /* Track if ext4_add_complete_io() took an extra inode reference. */ + unsigned short i_rsv_need_iput:1; /* * Completed IOs that need unwritten extents handling and have * transaction reserved diff --git a/fs/ext4/page-io.c b/fs/ext4/page-io.c index 39abfeec5f36..294c0bcbbb7f 100644 --- a/fs/ext4/page-io.c +++ b/fs/ext4/page-io.c @@ -262,8 +262,12 @@ static void ext4_add_complete_io(ext4_io_end_t *io_end) spin_lock_irqsave(&ei->i_completed_io_lock, flags); wq = sbi->rsv_conversion_wq; - if (list_empty(&ei->i_rsv_conversion_list)) - queue_work(wq, &ei->i_rsv_conversion_work); + if (list_empty(&ei->i_rsv_conversion_list)) { + if (igrab(io_end->inode)) { + ei->i_rsv_need_iput = 1; + queue_work(wq, &ei->i_rsv_conversion_work); + } + } list_add_tail(&io_end->list, &ei->i_rsv_conversion_list); spin_unlock_irqrestore(&ei->i_completed_io_lock, flags); } @@ -291,6 +295,13 @@ static int ext4_do_flush_completed_IO(struct inode *inode, if (unlikely(!ret && err)) ret = err; } + + /* Release inode reference from ext4_add_complete_io. */ + if (ei->i_rsv_need_iput) { + ei->i_rsv_need_iput = 0; + iput(&ei->vfs_inode); + } + return ret; } diff --git a/fs/ext4/super.c b/fs/ext4/super.c index 87205660c5d0..10a5d863d9b9 100644 --- a/fs/ext4/super.c +++ b/fs/ext4/super.c @@ -1418,6 +1418,7 @@ static struct inode *ext4_alloc_inode(struct super_block *sb) ei->jinode = NULL; INIT_LIST_HEAD(&ei->i_rsv_conversion_list); spin_lock_init(&ei->i_completed_io_lock); + ei->i_rsv_need_iput = 0; ei->i_sync_tid = 0; ei->i_datasync_tid = 0; INIT_WORK(&ei->i_rsv_conversion_work, ext4_end_io_rsv_work); -- 2.34.1