do_split() allocates a new directory block with ext4_append() to hold the second half of the entries of the leaf block being split, then dx_move_dirents() copies entries from the old block (*bh) into the new one (bh2). Nothing verifies that the newly allocated block differs from the block being split. On a filesystem whose block bitmap is inconsistent with the directory tree, a block already referenced as a directory leaf can still be marked free, so ext4_append() can return that same block. With concurrent operations on such a filesystem (as exercised by the syzbot reproducer), the block can even be freed between allocations, resulting in bh2 aliasing *bh. dx_move_dirents() then copies the block onto itself, corrupting directory entries mid-copy: a rec_len field can be overwritten with name bytes, so the subsequent wipe of the stale entry memset()s a bogus length, causing the slab-use- after-free reported by syzbot. Fix this by checking for bh2 == *bh right after ext4_append() and bailing out with -EFSCORRUPTED before any directory block contents are modified, reporting the corruption via ext4_error_inode_block(). Reported-by: syzbot+09bec78ee77613a3efdd@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=09bec78ee77613a3efdd Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Shuai Liu <847807867@qq.com> --- fs/ext4/namei.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/ext4/namei.c b/fs/ext4/namei.c index 354fc2d22df1..4ef7983d46c6 100644 --- a/fs/ext4/namei.c +++ b/fs/ext4/namei.c @@ -1928,6 +1928,13 @@ static struct ext4_dir_entry_2 *do_split(handle_t *handle, struct inode *dir, *bh = NULL; return ERR_CAST(bh2); } + if (bh2 == *bh) { + ext4_error_inode_block(dir, newblock, -EFSCORRUPTED, + "cannot split aliased directory block"); + brelse(bh2); + *bh = NULL; + return ERR_PTR(-EFSCORRUPTED); + } BUFFER_TRACE(*bh, "get_write_access"); err = ext4_journal_get_write_access(handle, dir->i_sb, *bh, -- 2.43.0