[PATCH] ocfs2: reject superblock-flagged dinodes during inode reads

From: ZhengYuan Huang

Date: Thu Apr 02 2026 - 09:12:38 EST


[BUG]
kernel BUG at fs/ocfs2/inode.c:412!
Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
RIP: 0010:ocfs2_populate_inode+0x128d/0x16e0 fs/ocfs2/inode.c:412
Code: be040000 00e8f3da05 00e914fbff ffe8d94ec8
Call Trace:
ocfs2_read_locked_inode+0x79a/0x10c0 fs/ocfs2/inode.c:618
ocfs2_iget+0x7fa/0x9b0 fs/ocfs2/inode.c:157
_ocfs2_get_system_file_inode fs/ocfs2/sysfile.c:142 [inline]
ocfs2_get_system_file_inode+0x389/0x820 fs/ocfs2/sysfile.c:112
ocfs2_init_local_system_inodes fs/ocfs2/super.c:491 [inline]
ocfs2_mount_volume fs/ocfs2/super.c:1756 [inline]
ocfs2_fill_super+0x1330/0x3cd0 fs/ocfs2/super.c:1083
get_tree_bdev_flags+0x38b/0x640 fs/super.c:1698
get_tree_bdev+0x24/0x40 fs/super.c:1721
ocfs2_get_tree+0x21/0x30 fs/ocfs2/super.c:1184
vfs_get_tree+0x9a/0x370 fs/super.c:1758
fc_mount fs/namespace.c:1199 [inline]
do_new_mount_fc fs/namespace.c:3642 [inline]
do_new_mount fs/namespace.c:3718 [inline]
path_mount+0x5b8/0x1ea0 fs/namespace.c:4028
do_mount fs/namespace.c:4041 [inline]
__do_sys_mount fs/namespace.c:4229 [inline]
__se_sys_mount fs/namespace.c:4206 [inline]
__x64_sys_mount+0x282/0x320 fs/namespace.c:4206
...

[CAUSE]
A crafted OCFS2 image can place OCFS2_SUPER_BLOCK_FL on a block that
still looks like a dinode to the existing validator. That lets the block
reach ocfs2_populate_inode(), which still BUG()s when the superblock
flag is present.

[FIX]
Reject that flag in ocfs2_validate_inode_block() so normal inode
reads fail before they reach ocfs2_populate_inode(). Mirror the same
check in the filecheck validator and refuse to repair such dinodes,
so online checking reports the same corruption class instead of trying
to patch up other fields around it.

Add a second guard in ocfs2_read_locked_inode() for JBD2-managed buffers,
since those paths can bypass ocfs2_validate_inode_block() and would
otherwise still fall into the BUG() in ocfs2_populate_inode().

Signed-off-by: ZhengYuan Huang <gality369@xxxxxxxxx>
---
fs/ocfs2/inode.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index fcc89856ab95..5f34f3afa3b5 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -611,6 +611,18 @@ static int ocfs2_read_locked_inode(struct inode *inode,
"Inode %llu: system file state is ambiguous\n",
(unsigned long long)args->fi_blkno);

+ /*
+ * JBD2-managed buffers can skip ocfs2_validate_inode_block(). Keep
+ * SUPER_BLOCK_FL away from ocfs2_populate_inode(), which still BUG()s
+ * when that flag is present on a dinode.
+ */
+ if (fe->i_flags & cpu_to_le32(OCFS2_SUPER_BLOCK_FL)) {
+ status = ocfs2_error(inode->i_sb,
+ "Invalid dinode #%llu: superblock flag set\n",
+ (unsigned long long)args->fi_blkno);
+ goto bail;
+ }
+
if (S_ISCHR(le16_to_cpu(fe->i_mode)) ||
S_ISBLK(le16_to_cpu(fe->i_mode)))
inode->i_rdev = huge_decode_dev(le64_to_cpu(fe->id1.dev1.i_rdev));
@@ -1503,6 +1515,13 @@ int ocfs2_validate_inode_block(struct super_block *sb,
goto bail;
}

+ if (di->i_flags & cpu_to_le32(OCFS2_SUPER_BLOCK_FL)) {
+ rc = ocfs2_error(sb,
+ "Invalid dinode #%llu: superblock flag set\n",
+ (unsigned long long)bh->b_blocknr);
+ goto bail;
+ }
+
rc = 0;

bail:
@@ -1570,6 +1589,14 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
rc = -OCFS2_FILECHECK_ERR_GENERATION;
}

+ if (di->i_flags & cpu_to_le32(OCFS2_SUPER_BLOCK_FL)) {
+ mlog(ML_ERROR,
+ "Filecheck: invalid dinode #%llu: superblock flag set\n",
+ (unsigned long long)bh->b_blocknr);
+ rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+ goto bail;
+ }
+
bail:
return rc;
}
@@ -1615,6 +1642,11 @@ static int ocfs2_filecheck_repair_inode_block(struct super_block *sb,
return -OCFS2_FILECHECK_ERR_VALIDFLAG;
}

+ if (di->i_flags & cpu_to_le32(OCFS2_SUPER_BLOCK_FL)) {
+ /* Cannot fix a dinode with the superblock flag set. */
+ return -OCFS2_FILECHECK_ERR_INVALIDINO;
+ }
+
if (le64_to_cpu(di->i_blkno) != bh->b_blocknr) {
di->i_blkno = cpu_to_le64(bh->b_blocknr);
changed = 1;
--
2.43.0