From 6d942c874f6fc8cea801981b6f2cfd9829a641d4 Mon Sep 17 00:00:00 2001 From: Milos Nikic Date: Fri, 6 Feb 2026 16:29:08 -0800 Subject: ext2: remove stale TODO about kmap The TODO comment in the file header asking to get rid of kmap() is outdated. The code has already been converted to use the folio API (specifically kmap_local_folio). Remove the stale comment to reflect the current state of the code. Signed-off-by: Milos Nikic Link: https://patch.msgid.link/20260207002908.176933-1-nikic.milos@gmail.com Signed-off-by: Jan Kara --- fs/ext2/namei.c | 2 -- 1 file changed, 2 deletions(-) (limited to 'fs/ext2') diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index bde617a66cec..3ab23de558fb 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -14,8 +14,6 @@ * * The only non-static object here is ext2_dir_inode_operations. * - * TODO: get rid of kmap() use, add readahead. - * * Copyright (C) 1992, 1993, 1994, 1995 * Remy Card (card@masi.ibp.fr) * Laboratoire MASI - Institut Blaise Pascal -- cgit v1.2.3 From 0cf9c58bf654d0f27abe18005281dbf9890de401 Mon Sep 17 00:00:00 2001 From: Milos Nikic Date: Fri, 6 Feb 2026 17:06:17 -0800 Subject: ext2: replace BUG_ON with WARN_ON_ONCE in ext2_get_blocks If ext2_get_blocks() is called with maxblocks == 0, it currently triggers a BUG_ON(), causing a kernel panic. While this condition implies a logic error in the caller, a filesystem should not crash the system due to invalid arguments. Replace the BUG_ON() with a WARN_ON_ONCE() to provide a stack trace for debugging, and return -EINVAL to handle the error gracefully. Signed-off-by: Milos Nikic Link: https://patch.msgid.link/20260207010617.216675-1-nikic.milos@gmail.com Signed-off-by: Jan Kara --- fs/ext2/inode.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'fs/ext2') diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c index dbfe9098a124..18bf1a91dbc2 100644 --- a/fs/ext2/inode.c +++ b/fs/ext2/inode.c @@ -638,7 +638,8 @@ static int ext2_get_blocks(struct inode *inode, int count = 0; ext2_fsblk_t first_block = 0; - BUG_ON(maxblocks == 0); + if (WARN_ON_ONCE(maxblocks == 0)) + return -EINVAL; depth = ext2_block_to_path(inode,iblock,offsets,&blocks_to_boundary); -- cgit v1.2.3 From ad0e9663f0f5b0ed8e27d3690c5ac9de72243fba Mon Sep 17 00:00:00 2001 From: Milos Nikic Date: Fri, 6 Feb 2026 18:29:20 -0800 Subject: ext2: guard reservation window dump with EXT2FS_DEBUG The function __rsv_window_dump() is a heavyweight debug tool that walks the reservation red-black tree. It is currently guarded by #if 1, forcing it to be compiled into all kernels, even production ones. Match the rest of the file by guarding it with #ifdef EXT2FS_DEBUG, so it is only included when explicit debugging is enabled. This removes the unused function code from standard builds. Signed-off-by: Milos Nikic Link: https://patch.msgid.link/20260207022920.258247-1-nikic.milos@gmail.com Signed-off-by: Jan Kara --- fs/ext2/balloc.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'fs/ext2') diff --git a/fs/ext2/balloc.c b/fs/ext2/balloc.c index 007eee794bd1..adf0f31fbddd 100644 --- a/fs/ext2/balloc.c +++ b/fs/ext2/balloc.c @@ -201,7 +201,7 @@ static void group_adjust_blocks(struct super_block *sb, int group_no, * windows(start, end). Otherwise, it will only print out the "bad" windows, * those windows that overlap with their immediate neighbors. */ -#if 1 +#ifdef EXT2FS_DEBUG static void __rsv_window_dump(struct rb_root *root, int verbose, const char *fn) { @@ -248,7 +248,7 @@ restart: __rsv_window_dump((root), (verbose), __func__) #else #define rsv_window_dump(root, verbose) do {} while (0) -#endif +#endif /* EXT2FS_DEBUG */ /** * goal_in_my_reservation() -- cgit v1.2.3 From 19134a133184fcc49c41cf42797cb2e7fef76065 Mon Sep 17 00:00:00 2001 From: Ziyi Guo Date: Wed, 11 Feb 2026 02:20:52 +0000 Subject: ext2: avoid drop_nlink() during unlink of zero-nlink inode in ext2_unlink() ext2_unlink() calls inode_dec_link_count() unconditionally, which invokes drop_nlink(). If the inode was loaded from a corrupted disk image with i_links_count == 0, drop_nlink() triggers WARN_ON(inode->i_nlink == 0) Follow the ext4 pattern from __ext4_unlink(): check i_nlink before decrementing. If already zero, skip the decrement. Signed-off-by: Ziyi Guo Link: https://patch.msgid.link/20260211022052.973114-1-n7l8m4@u.northwestern.edu Signed-off-by: Jan Kara --- fs/ext2/namei.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'fs/ext2') diff --git a/fs/ext2/namei.c b/fs/ext2/namei.c index 3ab23de558fb..0d09d22fe708 100644 --- a/fs/ext2/namei.c +++ b/fs/ext2/namei.c @@ -291,7 +291,10 @@ static int ext2_unlink(struct inode *dir, struct dentry *dentry) goto out; inode_set_ctime_to_ts(inode, inode_get_ctime(dir)); - inode_dec_link_count(inode); + + if (inode->i_nlink) + inode_dec_link_count(inode); + err = 0; out: return err; -- cgit v1.2.3 From 982999538269d5adbd7574098bd12e2c506bacfe Mon Sep 17 00:00:00 2001 From: David Carlier Date: Sun, 5 Apr 2026 16:47:17 +0100 Subject: ext2: use get_random_u32() where appropriate Use the typed random integer helpers instead of get_random_bytes() when filling a single integer variable. The helpers return the value directly, require no pointer or size argument, and better express intent. Signed-off-by: David Carlier Link: https://patch.msgid.link/20260405154717.4705-1-devnexen@gmail.com Signed-off-by: Jan Kara --- fs/ext2/super.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'fs/ext2') diff --git a/fs/ext2/super.c b/fs/ext2/super.c index 603f2641fe10..e4136490c883 100644 --- a/fs/ext2/super.c +++ b/fs/ext2/super.c @@ -1151,7 +1151,7 @@ static int ext2_fill_super(struct super_block *sb, struct fs_context *fc) goto failed_mount2; } sbi->s_gdb_count = db_count; - get_random_bytes(&sbi->s_next_generation, sizeof(u32)); + sbi->s_next_generation = get_random_u32(); spin_lock_init(&sbi->s_next_gen_lock); /* per filesystem reservation list head & lock */ -- cgit v1.2.3 From 25947cc5b2374cd5bf627fe3141496444260d04f Mon Sep 17 00:00:00 2001 From: Vasiliy Kovalev Date: Sat, 4 Apr 2026 18:20:11 +0300 Subject: ext2: reject inodes with zero i_nlink and valid mode in ext2_iget() ext2_iget() already rejects inodes with i_nlink == 0 when i_mode is zero or i_dtime is set, treating them as deleted. However, the case of i_nlink == 0 with a non-zero mode and zero dtime slips through. Since ext2 has no orphan list, such a combination can only result from filesystem corruption - a legitimate inode deletion always sets either i_dtime or clears i_mode before freeing the inode. A crafted image can exploit this gap to present such an inode to the VFS, which then triggers WARN_ON inside drop_nlink() (fs/inode.c) via ext2_unlink(), ext2_rename() and ext2_rmdir(): WARNING: CPU: 3 PID: 609 at fs/inode.c:336 drop_nlink+0xad/0xd0 fs/inode.c:336 CPU: 3 UID: 0 PID: 609 Comm: syz-executor Not tainted 6.12.77+ #1 Call Trace: inode_dec_link_count include/linux/fs.h:2518 [inline] ext2_unlink+0x26c/0x300 fs/ext2/namei.c:295 vfs_unlink+0x2fc/0x9b0 fs/namei.c:4477 do_unlinkat+0x53e/0x730 fs/namei.c:4541 __x64_sys_unlink+0xc6/0x110 fs/namei.c:4587 do_syscall_64+0xf5/0x220 arch/x86/entry/common.c:78 entry_SYSCALL_64_after_hwframe+0x77/0x7f 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 __x64_sys_rename+0x7e/0xa0 fs/namei.c:5223 do_syscall_64+0xf5/0x220 arch/x86/entry/common.c:78 entry_SYSCALL_64_after_hwframe+0x77/0x7f WARNING: CPU: 0 PID: 634 at fs/inode.c:336 drop_nlink+0xad/0xd0 fs/inode.c:336 CPU: 0 UID: 0 PID: 634 Comm: syz-executor Not tainted 6.12.77+ #1 Call Trace: inode_dec_link_count include/linux/fs.h:2518 [inline] ext2_rmdir+0xca/0x110 fs/ext2/namei.c:311 vfs_rmdir+0x204/0x690 fs/namei.c:4348 do_rmdir+0x372/0x3e0 fs/namei.c:4407 __x64_sys_unlinkat+0xf0/0x130 fs/namei.c:4577 do_syscall_64+0xf5/0x220 arch/x86/entry/common.c:78 entry_SYSCALL_64_after_hwframe+0x77/0x7f Extend the existing i_nlink == 0 check to also catch this case, reporting the corruption via ext2_error() and returning -EFSCORRUPTED. This rejects the inode at load time and prevents it from reaching any of the namei.c paths. Found by Linux Verification Center (linuxtesting.org) with Syzkaller. Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") Cc: stable@vger.kernel.org Signed-off-by: Vasiliy Kovalev Link: https://patch.msgid.link/20260404152011.2590197-1-kovalev@altlinux.org Signed-off-by: Jan Kara --- fs/ext2/inode.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'fs/ext2') diff --git a/fs/ext2/inode.c b/fs/ext2/inode.c index 18bf1a91dbc2..73efd3030743 100644 --- a/fs/ext2/inode.c +++ b/fs/ext2/inode.c @@ -1431,9 +1431,17 @@ struct inode *ext2_iget (struct super_block *sb, unsigned long ino) * the test is that same one that e2fsck uses * NeilBrown 1999oct15 */ - if (inode->i_nlink == 0 && (inode->i_mode == 0 || ei->i_dtime)) { - /* this inode is deleted */ - ret = -ESTALE; + if (inode->i_nlink == 0) { + if (inode->i_mode == 0 || ei->i_dtime) { + /* this inode is deleted */ + ret = -ESTALE; + } else { + ext2_error(sb, __func__, + "inode %lu has zero i_nlink with mode 0%o and no dtime, " + "filesystem may be corrupt", + ino, inode->i_mode); + ret = -EFSCORRUPTED; + } goto bad_inode; } inode->i_blocks = le32_to_cpu(raw_inode->i_blocks); -- cgit v1.2.3