public inbox for linux-kernel@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2] ocfs2: validate inline dir size during inode reads
@ 2026-04-10 11:32 ZhengYuan Huang
  0 siblings, 0 replies; only message in thread
From: ZhengYuan Huang @ 2026-04-10 11:32 UTC (permalink / raw)
  To: mark, jlbec, joseph.qi
  Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
	ZhengYuan Huang

[BUG]
A crafted inline-data directory can store i_size larger than id_count.
Once such a dinode is instantiated, readdir walks past data->id_data
and KASAN reports:

BUG: KASAN: use-after-free in ocfs2_check_dir_entry.isra.0+0x31f/0x370 fs/ocfs2/dir.c:305
Read of size 2 at addr ffff8880088f0008 by task syz.0.1936/4656
Call Trace:
 ...
 ocfs2_check_dir_entry.isra.0+0x31f/0x370 fs/ocfs2/dir.c:305
 ocfs2_dir_foreach_blk_id+0x203/0xa70 fs/ocfs2/dir.c:1805
 ocfs2_dir_foreach_blk fs/ocfs2/dir.c:1933 [inline]
 ocfs2_readdir+0x4ba/0x520 fs/ocfs2/dir.c:1977
 wrap_directory_iterator+0x9c/0xe0 fs/readdir.c:65
 shared_ocfs2_readdir+0x29/0x40 fs/ocfs2/file.c:2822
 iterate_dir+0x276/0x9e0 fs/readdir.c:108
 __do_sys_getdents64 fs/readdir.c:410 [inline]
 __se_sys_getdents64 fs/readdir.c:396 [inline]
 __x64_sys_getdents64+0x143/0x2a0 fs/readdir.c:396
 ...

[CAUSE]
The inline-dir invariant i_size <= id_count is never validated when a
dinode is read. ocfs2_validate_inode_block() accepts the corrupted
metadata, then ocfs2_populate_inode() or ocfs2_refresh_inode() copies
that unchecked on-disk i_size into inode->i_size.

JBD2-managed buffers can also bypass ocfs2_validate_inode_block(), so
the same unchecked size can still reach ocfs2_populate_inode() and
ocfs2_refresh_inode() through those read paths.

[FIX]
Introduce a shared helper that validates inline directory i_size
against id_count. Call it from ocfs2_validate_inode_block() so corrupt
inline-dir dinodes are rejected in the cold metadata-read path, and add
matching guards in ocfs2_read_locked_inode() and ocfs2_inode_lock_update()
for the JBD2-managed buffer paths that skip the validator.

This blocks the corrupted metadata before it reaches VFS inode state
and keeps the hot readdir path unchanged.

Fixes: 23193e513d1c ("ocfs2: Read support for directories with inline data")
Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
v2:
- Move the validation from ocfs2_dir_foreach_blk_id() to inode read paths
- Add JBD2-managed buffer guards in ocfs2_read_locked_inode() and ocfs2_inode_lock_update()
- Reword the changelog to describe unchecked on-disk i_size rather than corrupted in-memory state
---
 fs/ocfs2/dlmglue.c |  5 +++++
 fs/ocfs2/inode.c   | 29 +++++++++++++++++++++++++++++
 fs/ocfs2/inode.h   |  2 ++
 3 files changed, 36 insertions(+)

diff --git a/fs/ocfs2/dlmglue.c b/fs/ocfs2/dlmglue.c
index 92a6149da9c1..69aaceeb76bc 100644
--- a/fs/ocfs2/dlmglue.c
+++ b/fs/ocfs2/dlmglue.c
@@ -2363,6 +2363,11 @@ static int ocfs2_inode_lock_update(struct inode *inode,
 			goto bail_refresh;
 		}
 
+		/* JBD2-managed buffers can bypass ocfs2_validate_inode_block(). */
+		status = ocfs2_validate_inline_dir(inode->i_sb, oi->ip_blkno, fe);
+		if (status)
+			goto bail_refresh;
+
 		/* This is a good chance to make sure we're not
 		 * locking an invalid object.  ocfs2_read_inode_block()
 		 * already checked that the inode block is sane.
diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index fcc89856ab95..3c8a9b592d25 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -66,6 +66,26 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 static int ocfs2_filecheck_repair_inode_block(struct super_block *sb,
 					      struct buffer_head *bh);
 
+/* Inline directories must never advertise more data than id_count can hold. */
+int ocfs2_validate_inline_dir(struct super_block *sb, u64 blkno,
+			      struct ocfs2_dinode *di)
+{
+	struct ocfs2_inline_data *data = &di->id2.i_data;
+
+	if (!S_ISDIR(le16_to_cpu(di->i_mode)) ||
+	    !(le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL))
+		return 0;
+
+	if (le64_to_cpu(di->i_size) > le16_to_cpu(data->id_count))
+		return ocfs2_error(sb,
+				   "Invalid dinode #%llu: inline dir i_size %llu exceeds id_count %u\n",
+				   (unsigned long long)blkno,
+				   (unsigned long long)le64_to_cpu(di->i_size),
+				   le16_to_cpu(data->id_count));
+
+	return 0;
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -611,6 +631,11 @@ 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 bypass ocfs2_validate_inode_block(). */
+	status = ocfs2_validate_inline_dir(inode->i_sb, args->fi_blkno, fe);
+	if (status)
+		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 +1528,10 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
+	rc = ocfs2_validate_inline_dir(sb, bh->b_blocknr, di);
+	if (rc)
+		goto bail;
+
 	rc = 0;
 
 bail:
diff --git a/fs/ocfs2/inode.h b/fs/ocfs2/inode.h
index accf03d4765e..1d71648c1294 100644
--- a/fs/ocfs2/inode.h
+++ b/fs/ocfs2/inode.h
@@ -139,6 +139,8 @@ int ocfs2_mark_inode_dirty(handle_t *handle,
 
 void ocfs2_set_inode_flags(struct inode *inode);
 void ocfs2_get_inode_flags(struct ocfs2_inode_info *oi);
+int ocfs2_validate_inline_dir(struct super_block *sb, u64 blkno,
+			      struct ocfs2_dinode *di);
 
 static inline blkcnt_t ocfs2_inode_sector_count(struct inode *inode)
 {
-- 
2.49.0

^ permalink raw reply related	[flat|nested] only message in thread

only message in thread, other threads:[~2026-04-10 11:32 UTC | newest]

Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-10 11:32 [PATCH v2] ocfs2: validate inline dir size during inode reads ZhengYuan Huang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox