A crafted ext2 image can provide a target inode with i_links_count == 0 on disk. When rename() resolves to an existing target, ext2_rename() calls drop_nlink(new_inode) for the directory case and inode_dec_link_count(new_inode) unconditionally. Both reach drop_nlink(), which triggers WARN_ON: WARNING: CPU: 0 PID: 646 at fs/inode.c:336 drop_nlink+0xad/0xd0 fs/inode.c:336 CPU: 0 UID: 0 PID: 646 Comm: syz.0.17 Not tainted 6.12.77+ #1 Call Trace: inode_dec_link_count include/linux/fs.h:2518 [inline] ext2_rename+0x35e/0x850 fs/ext2/namei.c:374 vfs_rename+0xf2f/0x2060 fs/namei.c:5021 do_renameat2+0xbe2/0xd50 fs/namei.c:5178 __do_sys_rename fs/namei.c:5225 [inline] __se_sys_rename fs/namei.c:5223 [inline] __x64_sys_rename+0x7e/0xa0 fs/namei.c:5223 do_syscall_x64 arch/x86/entry/common.c:47 [inline] do_syscall_64+0xf5/0x220 arch/x86/entry/common.c:78 entry_SYSCALL_64_after_hwframe+0x77/0x7f No disk state has been modified at this point in the function, so return -EFSCORRUPTED after reporting the corruption via ext2_error(). Found by Linux Verification Center (linuxtesting.org) with Syzkaller. Cc: stable@vger.kernel.org Fixes: 9a53c3a783c2 ("[PATCH] r/o bind mounts: unlink: monitor i_nlink") Signed-off-by: Vasiliy Kovalev --- fs/ext2/namei.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index ea49e8f2b292..419e844f2e54 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -334,6 +334,13 @@ static int ext2_rename (struct mnt_idmap * idmap, bool old_is_dir = S_ISDIR(old_inode->i_mode); int err; + if (new_inode && new_inode->i_nlink == 0) { + ext2_error(old_dir->i_sb, __func__, + "target inode %lu has zero i_nlink, filesystem may be corrupt", + new_inode->i_ino); + return -EFSCORRUPTED; + } + if (flags & ~RENAME_NOREPLACE) return -EINVAL; -- 2.50.1