* [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