mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] ocfs2: validate extent list in filecheck repair
@ 2026-09-26 13:18 Jiale Yao
  2026-09-28  1:33 ` Joseph Qi
  0 siblings, 1 reply; 8+ messages in thread
From: Jiale Yao @ 2026-09-26 13:18 UTC (permalink / raw)
  To: Mark Fasheh, Joel Becker, Joseph Qi, Gang He, Andrew Morton,
	ocfs2-devel, linux-kernel
  Cc: Jiale Yao

ocfs2_filecheck_validate_inode_block() does not validate the embedded
extent list, while ocfs2_filecheck_repair_inode_block() only clamps
l_next_free_rec to l_count.  The normal inode read path requires l_count
to be non-zero, limits it to the number of extent records that fit in the
inode, and requires l_next_free_rec not to exceed l_count.

Without the same checks, filecheck can report SUCCESS while leaving an
invalid extent list on disk.  A later read through the normal inode
validation path rejects the inode and makes the filesystem read-only.

Add a shared helper for the filecheck paths to check these invariants.
The filecheck validator now rejects all three cases.  The repair path
still clamps l_next_free_rec, but refuses to repair zero or oversized
l_count values.

Fixes: d56a8f32e4c6 ("ocfs2: check/fix inode block for online file check")
Signed-off-by: Jiale Yao <yaojiale02@163.com>
---
 fs/ocfs2/inode.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 71 insertions(+)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 180107a11046..57168ed02915 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -249,6 +249,41 @@ static int ocfs2_dinode_has_extents(struct ocfs2_dinode *di)
 	return 1;
 }
 
+enum ocfs2_extent_list_status {
+	OCFS2_EXTENT_LIST_OK,
+	OCFS2_EXTENT_LIST_ZERO_COUNT,
+	OCFS2_EXTENT_LIST_OVERSIZED,
+	OCFS2_EXTENT_LIST_BAD_NEXT_FREE,
+};
+
+static enum ocfs2_extent_list_status
+ocfs2_check_extent_list(struct super_block *sb, struct ocfs2_dinode *di)
+{
+	struct ocfs2_extent_list *el = &di->id2.i_list;
+	u16 count;
+	u16 next_free;
+
+	if (!ocfs2_dinode_has_extents(di))
+		return OCFS2_EXTENT_LIST_OK;
+
+	count = le16_to_cpu(el->l_count);
+	next_free = le16_to_cpu(el->l_next_free_rec);
+	if (count == 0)
+		return OCFS2_EXTENT_LIST_ZERO_COUNT;
+	/*
+	 * The exact capacity depends on i_xattr_inline_size, another
+	 * unvalidated on-disk field. Inline xattrs only shrink the
+	 * list, so the no-xattr maximum is a safe upper bound that a
+	 * valid l_count never exceeds.
+	 */
+	if (count > ocfs2_extent_recs_per_inode(sb))
+		return OCFS2_EXTENT_LIST_OVERSIZED;
+	if (next_free > count)
+		return OCFS2_EXTENT_LIST_BAD_NEXT_FREE;
+
+	return OCFS2_EXTENT_LIST_OK;
+}
+
 /*
  * here's how inodes get read from disk:
  * iget5_locked -> find_actor -> OCFS2_FIND_ACTOR
@@ -1835,6 +1870,33 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
+	switch (ocfs2_check_extent_list(sb, di)) {
+	case OCFS2_EXTENT_LIST_OK:
+		break;
+	case OCFS2_EXTENT_LIST_ZERO_COUNT:
+		mlog(ML_ERROR,
+		     "Filecheck: invalid dinode #%llu: extent list l_count is zero\n",
+		     (unsigned long long)bh->b_blocknr);
+		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+		goto bail;
+	case OCFS2_EXTENT_LIST_OVERSIZED:
+		mlog(ML_ERROR,
+		     "Filecheck: invalid dinode #%llu: extent list l_count %u exceeds max %u\n",
+		     (unsigned long long)bh->b_blocknr,
+		     le16_to_cpu(di->id2.i_list.l_count),
+		     ocfs2_extent_recs_per_inode(sb));
+		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+		goto bail;
+	case OCFS2_EXTENT_LIST_BAD_NEXT_FREE:
+		mlog(ML_ERROR,
+		     "Filecheck: invalid dinode #%llu: extent list l_next_free_rec %u exceeds l_count %u\n",
+		     (unsigned long long)bh->b_blocknr,
+		     le16_to_cpu(di->id2.i_list.l_next_free_rec),
+		     le16_to_cpu(di->id2.i_list.l_count));
+		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+		goto bail;
+	}
+
 	if (ocfs2_dinode_has_size_without_clusters(sb, di)) {
 		if (S_ISDIR(le16_to_cpu(di->i_mode)))
 			mlog(ML_ERROR,
@@ -1893,6 +1955,15 @@ static int ocfs2_filecheck_repair_inode_block(struct super_block *sb,
 		return -OCFS2_FILECHECK_ERR_VALIDFLAG;
 	}
 
+	switch (ocfs2_check_extent_list(sb, di)) {
+	case OCFS2_EXTENT_LIST_OK:
+	case OCFS2_EXTENT_LIST_BAD_NEXT_FREE:
+		break;
+	case OCFS2_EXTENT_LIST_ZERO_COUNT:
+	case OCFS2_EXTENT_LIST_OVERSIZED:
+		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.34.1


^ permalink raw reply	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2026-09-29 13:31 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-26 13:18 [PATCH] ocfs2: validate extent list in filecheck repair Jiale Yao
2026-09-28  1:33 ` Joseph Qi
2026-09-28  5:18   ` Heming Zhao
2026-09-28  9:56     ` Joseph Qi
2026-09-28 14:33       ` Heming Zhao
2026-09-28  7:39   ` Heming Zhao
2026-09-28  8:02     ` jiale yao
2026-09-29 13:30       ` jiale yao

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

all inboxes | Powered by JetHome®