* [PATCH] ocfs2: validate in-inode extent ordering on read
@ 2026-04-23 6:19 ZhengYuan Huang
0 siblings, 0 replies; only message in thread
From: ZhengYuan Huang @ 2026-04-23 6:19 UTC (permalink / raw)
To: mark, jlbec, joseph.qi
Cc: ocfs2-devel, linux-kernel, baijiaju1990, r33s3n6, zzzccc427,
ZhengYuan Huang
[BUG]
A corrupted image can make creat("./file0") truncate an inode
whose in-inode extent records overlap.
kernel BUG at fs/ocfs2/alloc.c:5581!
Oops: invalid opcode: 0000 [#1] SMP KASAN NOPTI
RIP: 0010:ocfs2_remove_extent+0xdbc/0x1600 fs/ocfs2/alloc.c:5581
Code: 0f85c7f9 ffffe873 a7cffe48 c7c76025 4e86c605 a0db9605
Call Trace:
ocfs2_remove_btree_range+0x98b/0x1520 fs/ocfs2/alloc.c:5778
ocfs2_commit_truncate+0x6b3/0x1aa0 fs/ocfs2/alloc.c:7372
ocfs2_truncate_file+0xcdd/0x13c0 fs/ocfs2/file.c:509
ocfs2_setattr+0xa6d/0x1fd0 fs/ocfs2/file.c:1212
notify_change+0x4b5/0x1030 fs/attr.c:546
do_truncate+0x1d2/0x230 fs/open.c:68
handle_truncate fs/namei.c:3596 [inline]
do_open fs/namei.c:3979 [inline]
path_openat+0x260f/0x2ce0 fs/namei.c:4134
do_filp_open+0x1f6/0x430 fs/namei.c:4161
do_sys_openat2+0x117/0x1c0 fs/open.c:1437
do_sys_open fs/open.c:1452 [inline]
__do_sys_creat fs/open.c:1530 [inline]
__se_sys_creat fs/open.c:1524 [inline]
...
[CAUSE]
ocfs2_validate_inode_block() checks dinode header fields, but it does not
validate the ordering invariants of used records in di->id2.i_list.
A corrupted inode can therefore carry overlapping leaf extents into memory.
Later truncate code derives the removal range from the rightmost record.
ocfs2_remove_extent() then re-finds the extent by cpos and can hit an
earlier, overlapping record instead. That violates the remove_extent()
assumption that the requested range is contained in the matched record.
[FIX]
Validate the in-inode extent list while reading the dinode.
Reject lists whose record count is inconsistent, whose
next_free index is out of range, or whose used records are
empty, out of order, or overlapping. This stops corrupted
extent layouts before they reach truncate and the other
paths that assume a sorted extent list.
Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
fs/ocfs2/inode.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 61 insertions(+), 1 deletion(-)
diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index a510a0eb1adc..7fcd1ee4fd46 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -215,6 +215,61 @@ static int ocfs2_dinode_has_extents(struct ocfs2_dinode *di)
return 1;
}
+static int ocfs2_validate_inode_extent_list(struct super_block *sb,
+ struct buffer_head *bh,
+ struct ocfs2_dinode *di)
+{
+ struct ocfs2_extent_list *el = &di->id2.i_list;
+ u16 count = le16_to_cpu(el->l_count);
+ u16 next_free = le16_to_cpu(el->l_next_free_rec);
+ u16 expected = ocfs2_extent_recs_per_inode_with_xattr(sb, di);
+ u32 prev_end = 0;
+ bool have_prev = false;
+ int i;
+
+ if (count != expected)
+ return ocfs2_error(sb,
+ "Invalid dinode #%llu: extent list count %u (expected %u)\n",
+ (unsigned long long)bh->b_blocknr, count,
+ expected);
+
+ if (next_free > count)
+ return ocfs2_error(sb,
+ "Invalid dinode #%llu: extent list index %u (count %u)\n",
+ (unsigned long long)bh->b_blocknr, next_free,
+ count);
+
+ for (i = 0; i < next_free; i++) {
+ struct ocfs2_extent_rec *rec = &el->l_recs[i];
+ u32 rec_cpos = le32_to_cpu(rec->e_cpos);
+ u32 rec_clusters = ocfs2_rec_clusters(el, rec);
+ u32 rec_end = rec_cpos + rec_clusters;
+
+ if (!rec_clusters) {
+ if (!el->l_tree_depth && i == 0)
+ continue;
+
+ return ocfs2_error(sb,
+ "Invalid dinode #%llu: empty extent record %d at depth %u\n",
+ (unsigned long long)bh->b_blocknr,
+ i, le16_to_cpu(el->l_tree_depth));
+ }
+
+ if (have_prev &&
+ ((el->l_tree_depth && rec_cpos != prev_end) ||
+ (!el->l_tree_depth && rec_cpos < prev_end)))
+ return ocfs2_error(sb,
+ "Invalid dinode #%llu: extent record %d starts at %u after previous end %u\n",
+ (unsigned long long)bh->b_blocknr, i,
+ rec_cpos, prev_end);
+
+ prev_end = rec_end;
+ have_prev = true;
+ }
+
+ return 0;
+}
+
/*
* here's how inodes get read from disk:
* iget5_locked -> find_actor -> OCFS2_FIND_ACTOR
@@ -1552,6 +1607,12 @@ int ocfs2_validate_inode_block(struct super_block *sb,
}
}
+ if (ocfs2_dinode_has_extents(di)) {
+ rc = ocfs2_validate_inode_extent_list(sb, bh, di);
+ if (rc)
+ goto bail;
+ }
+
if ((le16_to_cpu(di->i_dyn_features) & OCFS2_HAS_REFCOUNT_FL) &&
!di->i_refcount_loc) {
rc = ocfs2_error(sb, "Inode #%llu has refcount flag but no i_refcount_loc\n",
@@ -1812,4 +1873,3 @@ const struct ocfs2_caching_operations ocfs2_inode_caching_ops = {
.co_io_lock = ocfs2_inode_cache_io_lock,
.co_io_unlock = ocfs2_inode_cache_io_unlock,
};
-
--
2.43.0
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-04-23 6:19 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-23 6:19 [PATCH] ocfs2: validate in-inode extent ordering on read ZhengYuan Huang
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox