* [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; 7+ 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] 7+ messages in thread* Re: [PATCH] ocfs2: validate extent list in filecheck repair 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 7:39 ` Heming Zhao 0 siblings, 2 replies; 7+ messages in thread From: Joseph Qi @ 2026-09-28 1:33 UTC (permalink / raw) To: Jiale Yao Cc: Andrew Morton, Mark Fasheh, Joel Becker, Gang He, Heming Zhao, ocfs2-devel, linux-kernel On 9/26/26 9:18 PM, Jiale Yao wrote: > 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") I'd like make this as a new ability rather than a bug fix. > 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; > +} This seems a duplicated check in ocfs2_validate_inode_block(). So I sugguest just extract the helper from it instead of duplicating a new one. BTW, seems we don't have to introduce enum ocfs2_extent_list_status, to make the checks simple. Thanks, Joseph > + > /* > * 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; ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ocfs2: validate extent list in filecheck repair 2026-09-28 1:33 ` Joseph Qi @ 2026-09-28 5:18 ` Heming Zhao 2026-09-28 9:56 ` Joseph Qi 2026-09-28 7:39 ` Heming Zhao 1 sibling, 1 reply; 7+ messages in thread From: Heming Zhao @ 2026-09-28 5:18 UTC (permalink / raw) To: Joseph Qi, yaojiale02 Cc: Andrew Morton, Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel On Mon, Sep 28, 2026 at 09:33:12AM +0800, Joseph Qi wrote: > > > On 9/26/26 9:18 PM, Jiale Yao wrote: > > 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") > > I'd like make this as a new ability rather than a bug fix. > > > 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; > > +} > > This seems a duplicated check in ocfs2_validate_inode_block(). > So I sugguest just extract the helper from it instead of duplicating > a new one. > > BTW, seems we don't have to introduce enum ocfs2_extent_list_status, > to make the checks simple. > > Thanks, > Joseph Hi Joseph, The code logic is identical for ocfs2_validate_inode_block() and ocfs2_filecheck_validate_inode_block(), except for their error handling style. It seems possible to merge them into ocfs2_validate_inode_block() by adding a bool check parameter to distinguish between the two behaviors, and then remove ocfs2_filecheck_validate_inode_block(). The new code logic: replace ocfs2_error() with mlog(), and then "goto bail". At the bail label, handle the two cases separately using "if (check)". i.e.: //adding a new parameter "check" int ocfs2_validate_inode_block(struct super_block *sb, struct buffer_head *bh, bool check) code change from: ``` int rc; ... ... if (!OCFS2_IS_VALID_DINODE(di)) rc = ocfs2_error(sb, "Invalid dinode #%llu: signature = %.*s\n", (unsigned long long)bh->b_blocknr, 7, di->i_signature); goto bail; } ``` to ``` int rc = 0; ... ... if (!OCFS2_IS_VALID_DINODE(di)) mlog(sb, "Invalid dinode #%llu: signature = %.*s\n", (unsigned long long)bh->b_blocknr, 7, di->i_signature); goto bail; } ... ... return rc; bail: if (check) { rc = -OCFS2_FILECHECK_ERR_INVALIDINO; } else { rc = ocfs2_error(sb, "invalid dinode\n"); } return rc; } ``` Thanks, Heming > > > + > > /* > > * 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; > ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ocfs2: validate extent list in filecheck repair 2026-09-28 5:18 ` Heming Zhao @ 2026-09-28 9:56 ` Joseph Qi 2026-09-28 14:33 ` Heming Zhao 0 siblings, 1 reply; 7+ messages in thread From: Joseph Qi @ 2026-09-28 9:56 UTC (permalink / raw) To: Heming Zhao, yaojiale02 Cc: Andrew Morton, Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel On 9/28/26 1:18 PM, Heming Zhao wrote: > On Mon, Sep 28, 2026 at 09:33:12AM +0800, Joseph Qi wrote: >> >> >> On 9/26/26 9:18 PM, Jiale Yao wrote: >>> 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") >> >> I'd like make this as a new ability rather than a bug fix. >> >>> 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; >>> +} >> >> This seems a duplicated check in ocfs2_validate_inode_block(). >> So I sugguest just extract the helper from it instead of duplicating >> a new one. >> >> BTW, seems we don't have to introduce enum ocfs2_extent_list_status, >> to make the checks simple. >> >> Thanks, >> Joseph > > Hi Joseph, > > The code logic is identical for ocfs2_validate_inode_block() and > ocfs2_filecheck_validate_inode_block(), except for their error handling style. > It seems possible to merge them into ocfs2_validate_inode_block() by adding > a bool check parameter to distinguish between the two behaviors, and then remove > ocfs2_filecheck_validate_inode_block(). > > The new code logic: replace ocfs2_error() with mlog(), and then "goto bail". > At the bail label, handle the two cases separately using "if (check)". > > i.e.: > //adding a new parameter "check" > int ocfs2_validate_inode_block(struct super_block *sb, > struct buffer_head *bh, bool check) > > > code change from: > ``` > int rc; > > ... ... > > if (!OCFS2_IS_VALID_DINODE(di)) > rc = ocfs2_error(sb, "Invalid dinode #%llu: signature = %.*s\n", > (unsigned long long)bh->b_blocknr, 7, > di->i_signature); > goto bail; > } > ``` > > to > ``` > int rc = 0; > > ... ... > > if (!OCFS2_IS_VALID_DINODE(di)) > mlog(sb, "Invalid dinode #%llu: signature = %.*s\n", > (unsigned long long)bh->b_blocknr, 7, > di->i_signature); > goto bail; > } > > ... ... > > return rc; > > bail: > if (check) { > rc = -OCFS2_FILECHECK_ERR_INVALIDINO; > } else { > rc = ocfs2_error(sb, "invalid dinode\n"); > } > > return rc; > } > ``` Ummm... This may make the code messy. I'd like only abtract the check condition, and let the return code and log message as in its own validation function. e.g. ocfs2_dinode_has_size_without_clusters(). Thanks, Joseph ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ocfs2: validate extent list in filecheck repair 2026-09-28 9:56 ` Joseph Qi @ 2026-09-28 14:33 ` Heming Zhao 0 siblings, 0 replies; 7+ messages in thread From: Heming Zhao @ 2026-09-28 14:33 UTC (permalink / raw) To: Joseph Qi Cc: yaojiale02, Andrew Morton, Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel On Mon, Sep 28, 2026 at 05:56:27PM +0800, Joseph Qi wrote: > > > On 9/28/26 1:18 PM, Heming Zhao wrote: > > On Mon, Sep 28, 2026 at 09:33:12AM +0800, Joseph Qi wrote: > >> > >> > >> On 9/26/26 9:18 PM, Jiale Yao wrote: > >>> 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") > >> > >> I'd like make this as a new ability rather than a bug fix. > >> > >>> 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; > >>> +} > >> > >> This seems a duplicated check in ocfs2_validate_inode_block(). > >> So I sugguest just extract the helper from it instead of duplicating > >> a new one. > >> > >> BTW, seems we don't have to introduce enum ocfs2_extent_list_status, > >> to make the checks simple. > >> > >> Thanks, > >> Joseph > > > > Hi Joseph, > > > > The code logic is identical for ocfs2_validate_inode_block() and > > ocfs2_filecheck_validate_inode_block(), except for their error handling style. > > It seems possible to merge them into ocfs2_validate_inode_block() by adding > > a bool check parameter to distinguish between the two behaviors, and then remove > > ocfs2_filecheck_validate_inode_block(). > > > > The new code logic: replace ocfs2_error() with mlog(), and then "goto bail". > > At the bail label, handle the two cases separately using "if (check)". > > > > i.e.: > > //adding a new parameter "check" > > int ocfs2_validate_inode_block(struct super_block *sb, > > struct buffer_head *bh, bool check) > > > > > > code change from: > > ``` > > int rc; > > > > ... ... > > > > if (!OCFS2_IS_VALID_DINODE(di)) > > rc = ocfs2_error(sb, "Invalid dinode #%llu: signature = %.*s\n", > > (unsigned long long)bh->b_blocknr, 7, > > di->i_signature); > > goto bail; > > } > > ``` > > > > to > > ``` > > int rc = 0; > > > > ... ... > > > > if (!OCFS2_IS_VALID_DINODE(di)) > > mlog(sb, "Invalid dinode #%llu: signature = %.*s\n", > > (unsigned long long)bh->b_blocknr, 7, > > di->i_signature); > > goto bail; > > } > > > > ... ... > > > > return rc; > > > > bail: > > if (check) { > > rc = -OCFS2_FILECHECK_ERR_INVALIDINO; > > } else { > > rc = ocfs2_error(sb, "invalid dinode\n"); > > } > > > > return rc; > > } > > ``` > > Ummm... This may make the code messy. > I'd like only abtract the check condition, and let the return code and > log message as in its own validation function. > e.g. ocfs2_dinode_has_size_without_clusters(). > > Thanks, > Joseph Got it and agreed. - Heming ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ocfs2: validate extent list in filecheck repair 2026-09-28 1:33 ` Joseph Qi 2026-09-28 5:18 ` Heming Zhao @ 2026-09-28 7:39 ` Heming Zhao 2026-09-28 8:02 ` jiale yao 1 sibling, 1 reply; 7+ messages in thread From: Heming Zhao @ 2026-09-28 7:39 UTC (permalink / raw) To: Joseph Qi, yaojiale02 Cc: Andrew Morton, Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel On Mon, Sep 28, 2026 at 09:33:12AM +0800, Joseph Qi wrote: > > > On 9/26/26 9:18 PM, Jiale Yao wrote: > > 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") > > I'd like make this as a new ability rather than a bug fix. Agree In my experience, users run online file checks only when inode data is corrupted, or when fsck/debugfs has already reported an inode issue prior to the check. The Fixes tag should only be used for the patch that fixes the unintended panic and read-only issue introduced by commit d56a8f32e4c6. - Heming > > > 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; > > +} > > This seems a duplicated check in ocfs2_validate_inode_block(). > So I sugguest just extract the helper from it instead of duplicating > a new one. > > BTW, seems we don't have to introduce enum ocfs2_extent_list_status, > to make the checks simple. > > Thanks, > Joseph > > > + > > /* > > * 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; > ^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ocfs2: validate extent list in filecheck repair 2026-09-28 7:39 ` Heming Zhao @ 2026-09-28 8:02 ` jiale yao 0 siblings, 0 replies; 7+ messages in thread From: jiale yao @ 2026-09-28 8:02 UTC (permalink / raw) To: Heming Zhao Cc: Joseph Qi, Andrew Morton, Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel Thanks for the review. I agree that this should be treated as an extension of online filecheck rather than a fix for d56a8f32e4c6. I will prepare a v2 with the following changes: - Drop the Fixes and stable tags and describe the change as additional filecheck validation. - Extract the existing extent-list checks from ocfs2_validate_inode_block() into a small shared helper instead of duplicating them. - Remove enum ocfs2_extent_list_status. - Keep the repair policy unchanged: reject an invalid l_count, while allowing the existing code to clamp l_next_free_rec to l_count. Thanks, Jiale At 2026-09-28 15:39:14, "Heming Zhao" <heming.zhao@suse.com> wrote: >On Mon, Sep 28, 2026 at 09:33:12AM +0800, Joseph Qi wrote: >> >> >> On 9/26/26 9:18 PM, Jiale Yao wrote: >> > 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") >> >> I'd like make this as a new ability rather than a bug fix. > >Agree > >In my experience, users run online file checks only when inode data is corrupted, >or when fsck/debugfs has already reported an inode issue prior to the check. > >The Fixes tag should only be used for the patch that fixes the unintended panic >and read-only issue introduced by commit d56a8f32e4c6. > >- Heming > >> >> > 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; >> > +} >> >> This seems a duplicated check in ocfs2_validate_inode_block(). >> So I sugguest just extract the helper from it instead of duplicating >> a new one. >> >> BTW, seems we don't have to introduce enum ocfs2_extent_list_status, >> to make the checks simple. >> >> Thanks, >> Joseph >> >> > + >> > /* >> > * 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; >> ^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-09-28 14:33 UTC | newest] Thread overview: 7+ 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
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®