From: Dave Chinner <dgc@kernel.org>
To: linux-xfs@vger.kernel.org
Cc: cem@kernel.org
Subject: [PATCH 39/48] xfs: validate logged extent fork contents during recovery
Date: Tue, 13 Oct 2026 17:01:29 +1100 [thread overview]
Message-ID: <20261013060204.3138690-40-dgc@kernel.org> (raw)
In-Reply-To: <20261013060204.3138690-1-dgc@kernel.org>
An extents format fork is logged as a packed array of on-disk extent records.
Recovery copied this array into the inode without inspecting it, so corrupt
extent records were replayed as they stood.
The existing verifiers open code the extent array walk for validation, so factor
that walk into xfs_bmap_validate_extent_array() and use it in all three places.
The on-disk reader now dumps the whole fork on a bad record rather than the
single record, so the failing record is still captured.
xfs_bmap_complain_bad_rec() is recast to take a mount and inode number rather
than an incore inode, so the array walk can report a bad record on behalf of any
caller. The walk reports against the inode number it is passed, and stays silent
when passed NULLFSINO.
Assisted-by: LLM
Signed-off-by: Dave Chinner <dgc@kernel.org>
---
fs/xfs/libxfs/xfs_bmap.c | 48 ++++++++++++++++++++++++++++-----
fs/xfs/libxfs/xfs_bmap.h | 8 ++++--
fs/xfs/libxfs/xfs_inode_fork.c | 23 ++++++++--------
fs/xfs/scrub/inode_repair.c | 14 +++-------
fs/xfs/xfs_inode_item_recover.c | 32 ++++++++++++++++++++--
5 files changed, 91 insertions(+), 34 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_bmap.c b/fs/xfs/libxfs/xfs_bmap.c
index 9bffde1484a7..a89c2076e7c9 100644
--- a/fs/xfs/libxfs/xfs_bmap.c
+++ b/fs/xfs/libxfs/xfs_bmap.c
@@ -1090,12 +1090,12 @@ struct xfs_iread_state {
int
xfs_bmap_complain_bad_rec(
- struct xfs_inode *ip,
+ struct xfs_mount *mp,
+ xfs_ino_t ino,
int whichfork,
xfs_failaddr_t fa,
const struct xfs_bmbt_irec *irec)
{
- struct xfs_mount *mp = ip->i_mount;
const char *forkname;
switch (whichfork) {
@@ -1107,7 +1107,7 @@ xfs_bmap_complain_bad_rec(
xfs_warn(mp,
"Bmap BTree record corruption in inode 0x%llx %s fork detected at %pS!",
- I_INO(ip), forkname, fa);
+ ino, forkname, fa);
xfs_warn(mp,
"Offset 0x%llx, start block 0x%llx, block count 0x%llx state 0x%x",
irec->br_startoff, irec->br_startblock, irec->br_blockcount,
@@ -1160,8 +1160,8 @@ xfs_iread_bmbt_block(
"xfs_iread_extents(2)", frp,
sizeof(*frp), fa);
xfs_bmap_mark_sick(ip, whichfork);
- return xfs_bmap_complain_bad_rec(ip, whichfork, fa,
- &new);
+ return xfs_bmap_complain_bad_rec(ip->i_mount,
+ I_INO(ip), whichfork, fa, &new);
}
xfs_iext_insert(ip, &ir->icur, &new,
xfs_bmap_fork_to_state(whichfork));
@@ -6135,6 +6135,39 @@ xfs_bmap_validate_extent(
XFS_IS_REALTIME_INODE(ip), whichfork, irec);
}
+/*
+ * Check a packed on-disk array of extent records. Returns the address of the
+ * first bad record, or NULL if they are all good. If ino is not NULLFSINO, a
+ * bad record is also reported against that inode.
+ */
+xfs_failaddr_t
+xfs_bmap_validate_extent_array(
+ struct xfs_mount *mp,
+ xfs_ino_t ino,
+ bool rtfile,
+ int whichfork,
+ struct xfs_bmbt_rec *recs,
+ xfs_extnum_t nrecs)
+{
+ xfs_extnum_t i;
+
+ for (i = 0; i < nrecs; i++) {
+ struct xfs_bmbt_irec irec;
+ xfs_failaddr_t fa;
+
+ xfs_bmbt_disk_get_all(&recs[i], &irec);
+ fa = xfs_bmap_validate_extent_raw(mp, rtfile, whichfork, &irec);
+ if (fa) {
+ if (ino != NULLFSINO)
+ xfs_bmap_complain_bad_rec(mp, ino, whichfork,
+ fa, &irec);
+ return fa;
+ }
+ }
+
+ return NULL;
+}
+
/*
* Used in xfs_itruncate_extents(). This is the maximum number of extents
* freed from a file in a single transaction.
@@ -6196,8 +6229,9 @@ xfs_bmap_query_range_helper(
&irec);
if (fa) {
xfs_btree_mark_sick(cur);
- return xfs_bmap_complain_bad_rec(cur->bc_ino.ip,
- cur->bc_ino.whichfork, fa, &irec);
+ return xfs_bmap_complain_bad_rec(cur->bc_mp,
+ I_INO(cur->bc_ino.ip), cur->bc_ino.whichfork,
+ fa, &irec);
}
return query->fn(cur, &irec, query->priv);
diff --git a/fs/xfs/libxfs/xfs_bmap.h b/fs/xfs/libxfs/xfs_bmap.h
index 60f6df4ce057..6d9c61994be8 100644
--- a/fs/xfs/libxfs/xfs_bmap.h
+++ b/fs/xfs/libxfs/xfs_bmap.h
@@ -275,8 +275,12 @@ xfs_failaddr_t xfs_bmap_validate_extent_raw(struct xfs_mount *mp, bool rtfile,
int whichfork, struct xfs_bmbt_irec *irec);
xfs_failaddr_t xfs_bmap_validate_extent(struct xfs_inode *ip, int whichfork,
struct xfs_bmbt_irec *irec);
-int xfs_bmap_complain_bad_rec(struct xfs_inode *ip, int whichfork,
- xfs_failaddr_t fa, const struct xfs_bmbt_irec *irec);
+xfs_failaddr_t xfs_bmap_validate_extent_array(struct xfs_mount *mp,
+ xfs_ino_t ino, bool rtfile, int whichfork,
+ struct xfs_bmbt_rec *recs, xfs_extnum_t nrecs);
+int xfs_bmap_complain_bad_rec(struct xfs_mount *mp, xfs_ino_t ino,
+ int whichfork, xfs_failaddr_t fa,
+ const struct xfs_bmbt_irec *irec);
int xfs_bmapi_remap(struct xfs_trans *tp, struct xfs_inode *ip,
xfs_fileoff_t bno, xfs_filblks_t len, xfs_fsblock_t startblock,
diff --git a/fs/xfs/libxfs/xfs_inode_fork.c b/fs/xfs/libxfs/xfs_inode_fork.c
index fac592e1852c..f4af3c18ddc2 100644
--- a/fs/xfs/libxfs/xfs_inode_fork.c
+++ b/fs/xfs/libxfs/xfs_inode_fork.c
@@ -138,23 +138,22 @@ xfs_iformat_extents(
ifp->if_data = NULL;
ifp->if_height = 0;
if (size) {
+ xfs_failaddr_t fa;
+
dp = (xfs_bmbt_rec_t *) XFS_DFORK_PTR(dip, whichfork);
+ fa = xfs_bmap_validate_extent_array(mp, I_INO(ip),
+ XFS_IS_REALTIME_INODE(ip), whichfork, dp, nex);
+ if (fa) {
+ xfs_inode_verifier_error(ip, -EFSCORRUPTED,
+ "xfs_iformat_extents(2)", dp, size, fa);
+ xfs_inode_mark_sick(ip, XFS_SICK_INO_CORE);
+ return -EFSCORRUPTED;
+ }
+
xfs_iext_first(ifp, &icur);
for (i = 0; i < nex; i++, dp++) {
- xfs_failaddr_t fa;
-
xfs_bmbt_disk_get_all(dp, &new);
- fa = xfs_bmap_validate_extent(ip, whichfork, &new);
- if (fa) {
- xfs_inode_verifier_error(ip, -EFSCORRUPTED,
- "xfs_iformat_extents(2)",
- dp, sizeof(*dp), fa);
- xfs_inode_mark_sick(ip, XFS_SICK_INO_CORE);
- return xfs_bmap_complain_bad_rec(ip, whichfork,
- fa, &new);
- }
-
xfs_iext_insert(ip, &icur, &new, state);
trace_xfs_read_extent(ip, &icur, state, _THIS_IP_);
xfs_iext_next(ifp, &icur);
diff --git a/fs/xfs/scrub/inode_repair.c b/fs/xfs/scrub/inode_repair.c
index e1a7a2729b47..191ef85055e0 100644
--- a/fs/xfs/scrub/inode_repair.c
+++ b/fs/xfs/scrub/inode_repair.c
@@ -869,11 +869,9 @@ xrep_dinode_bad_extents_fork(
unsigned int dfork_size,
int whichfork)
{
- struct xfs_bmbt_irec new;
struct xfs_bmbt_rec *dp;
xfs_extnum_t nex;
bool isrt;
- unsigned int i;
nex = xfs_dfork_nextents(dip, whichfork);
if (nex > dfork_size / sizeof(struct xfs_bmbt_rec))
@@ -882,15 +880,9 @@ xrep_dinode_bad_extents_fork(
dp = XFS_DFORK_PTR(dip, whichfork);
isrt = dip->di_flags & cpu_to_be16(XFS_DIFLAG_REALTIME);
- for (i = 0; i < nex; i++, dp++) {
- xfs_failaddr_t fa;
-
- xfs_bmbt_disk_get_all(dp, &new);
- fa = xfs_bmap_validate_extent_raw(sc->mp, isrt, whichfork,
- &new);
- if (fa)
- return true;
- }
+ if (xfs_bmap_validate_extent_array(sc->mp, NULLFSINO, isrt, whichfork,
+ dp, nex))
+ return true;
return false;
}
diff --git a/fs/xfs/xfs_inode_item_recover.c b/fs/xfs/xfs_inode_item_recover.c
index c987d4cd0285..3e0109833d21 100644
--- a/fs/xfs/xfs_inode_item_recover.c
+++ b/fs/xfs/xfs_inode_item_recover.c
@@ -27,6 +27,7 @@
#include "xfs_dir2_priv.h"
#include "xfs_attr_leaf.h"
#include "xfs_metafile.h"
+#include "xfs_bmap.h"
#include "xfs_bmap_btree.h"
#include "xfs_rtrmap_btree.h"
#include "xfs_rtrefcount_btree.h"
@@ -443,6 +444,31 @@ xlog_dinode_verify(
return NULL;
}
+/*
+ * Verify a logged extents fork. The region is a packed array of on-disk extent
+ * records, so its size fixes the number of records it holds.
+ *
+ * The region size has already been bounds checked against the inode fork size,
+ * so it is known to fit within the fork and does not need rechecking here.
+ */
+static xfs_failaddr_t
+xlog_dinode_verify_extents(
+ struct xfs_mount *mp,
+ struct xfs_log_dinode *ldip,
+ int whichfork,
+ void *src,
+ unsigned int size)
+{
+ bool isrt;
+
+ if (size == 0 || (size % sizeof(struct xfs_bmbt_rec)))
+ return __this_address;
+
+ isrt = ldip->di_flags & XFS_DIFLAG_REALTIME;
+ return xfs_bmap_validate_extent_array(mp, ldip->di_ino, isrt, whichfork,
+ src, size / sizeof(struct xfs_bmbt_rec));
+}
+
/*
* Verify a logged shortform symlink target. The logged target is the on-disk
* form, which is not null terminated, so this cannot use
@@ -538,7 +564,8 @@ xlog_dinode_verify_dfork(
return __this_address;
if (ilfp->ilf_dsize > fork_size)
return __this_address;
- break;
+ return xlog_dinode_verify_extents(mp, ldip, XFS_DATA_FORK,
+ item->ri_buf[2].iov_base, ilfp->ilf_dsize);
case XFS_DINODE_FMT_BTREE:
case XFS_DINODE_FMT_META_BTREE:
if (field != XFS_ILOG_DBROOT)
@@ -596,7 +623,8 @@ xlog_dinode_verify_afork(
return __this_address;
if (ilfp->ilf_asize > fork_size)
return __this_address;
- break;
+ return xlog_dinode_verify_extents(mp, ldip, XFS_ATTR_FORK,
+ item->ri_buf[region].iov_base, ilfp->ilf_asize);
case XFS_DINODE_FMT_BTREE:
if (field != XFS_ILOG_ABROOT)
return __this_address;
--
2.55.0
next prev parent reply other threads:[~2026-10-13 6:02 UTC|newest]
Thread overview: 49+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-13 6:00 [RFC] [PATCH 00/48] Validate journal log items during log recovery Dave Chinner
2026-10-13 6:00 ` [PATCH 01/48] xfs: set the unmount record operation header length Dave Chinner
2026-10-13 6:00 ` [PATCH 02/48] xfs: add log recovery item validation design document Dave Chinner
2026-10-13 6:00 ` [PATCH 03/48] xfs: lift transaction header parsing out of the region assembly Dave Chinner
2026-10-13 6:00 ` [PATCH 04/48] xfs: track transaction header decode state explicitly Dave Chinner
2026-10-13 6:00 ` [PATCH 05/48] xfs: track the log item being rebuilt with an explicit pointer Dave Chinner
2026-10-13 6:00 ` [PATCH 06/48] xfs: complete log items explicitly during recovery Dave Chinner
2026-10-13 6:00 ` [PATCH 07/48] xfs: add log item validation infrastructure to log recovery Dave Chinner
2026-10-13 6:00 ` [PATCH 08/48] xfs: recover the transaction header as a validated item type Dave Chinner
2026-10-13 6:00 ` [PATCH 09/48] xfs: factor out xlog_recover_validate_ophdr() Dave Chinner
2026-10-13 6:01 ` [PATCH 10/48] xfs: validate operation header length and alignment during recovery Dave Chinner
2026-10-13 6:01 ` [PATCH 11/48] xfs: validate the operation header padding " Dave Chinner
2026-10-13 6:01 ` [PATCH 12/48] xfs: validate the operation header flags " Dave Chinner
2026-10-13 6:01 ` [PATCH 13/48] xfs: enforce zero length control records " Dave Chinner
2026-10-13 6:01 ` [PATCH 14/48] xfs: validate the unmount record " Dave Chinner
2026-10-13 6:01 ` [PATCH 15/48] xfs: handle zero length continuation op headers " Dave Chinner
2026-10-13 6:01 ` [PATCH 16/48] xfs: bound the assembled region size " Dave Chinner
2026-10-13 6:01 ` [PATCH 17/48] xfs: dump the corrupt region when log item validation fails Dave Chinner
2026-10-13 6:01 ` [PATCH 18/48] xfs: validate fixed size log items during recovery Dave Chinner
2026-10-13 6:01 ` [PATCH 19/48] xfs: validate extent free " Dave Chinner
2026-10-13 6:01 ` [PATCH 20/48] xfs: validate and convert extent free intents " Dave Chinner
2026-10-13 6:01 ` [PATCH 21/48] xfs: validate and convert extent free done items " Dave Chinner
2026-10-13 6:01 ` [PATCH 22/48] xfs: validate rmap update intents " Dave Chinner
2026-10-13 6:01 ` [PATCH 23/48] xfs: validate rmap update done items " Dave Chinner
2026-10-13 6:01 ` [PATCH 24/48] xfs: validate refcount update intents " Dave Chinner
2026-10-13 6:01 ` [PATCH 25/48] xfs: validate refcount update done items " Dave Chinner
2026-10-13 6:01 ` [PATCH 26/48] xfs: validate bmap update intents " Dave Chinner
2026-10-13 6:01 ` [PATCH 27/48] xfs: validate bmap update done items " Dave Chinner
2026-10-13 6:01 ` [PATCH 28/48] xfs: validate mapping exchange intents " Dave Chinner
2026-10-13 6:01 ` [PATCH 29/48] xfs: validate mapping exchange done items " Dave Chinner
2026-10-13 6:01 ` [PATCH 30/48] xfs: validate dquot " Dave Chinner
2026-10-13 6:01 ` [PATCH 31/48] xfs: validate attr done " Dave Chinner
2026-10-13 6:01 ` [PATCH 32/48] xfs: validate attr intents " Dave Chinner
2026-10-13 6:01 ` [PATCH 33/48] xfs: convert inode items to host format " Dave Chinner
2026-10-13 6:01 ` [PATCH 34/48] xfs: allow xfs_imap() to map without an inode btree lookup Dave Chinner
2026-10-13 6:01 ` [PATCH 35/48] xfs: validate the inode item format header during recovery Dave Chinner
2026-10-13 6:01 ` [PATCH 36/48] xfs: validate the logged inode core " Dave Chinner
2026-10-13 6:01 ` [PATCH 37/48] xfs: validate the logged inode forks " Dave Chinner
2026-10-13 6:01 ` [PATCH 38/48] xfs: validate logged local fork contents " Dave Chinner
2026-10-13 6:01 ` Dave Chinner [this message]
2026-10-13 6:01 ` [PATCH 40/48] xfs: allow a long format btree root to be checked without a cursor Dave Chinner
2026-10-13 6:01 ` [PATCH 41/48] xfs: validate logged btree fork roots during recovery Dave Chinner
2026-10-13 6:01 ` [PATCH 42/48] xfs: validate logged metadata " Dave Chinner
2026-10-13 6:01 ` [PATCH 43/48] xfs: validate buffer item region count " Dave Chinner
2026-10-13 6:01 ` [PATCH 44/48] xfs: validate buffer item regions " Dave Chinner
2026-10-13 6:01 ` [PATCH 45/48] xfs: validate the whole buffer item " Dave Chinner
2026-10-13 6:01 ` [PATCH 46/48] xfs: remove redundant buffer checks from recovery replay Dave Chinner
2026-10-13 6:01 ` [PATCH 47/48] xfs: fail recovery on a buffer type mismatch Dave Chinner
2026-10-13 6:01 ` [PATCH 48/48] xfs: validate logged dquot buffers during recovery Dave Chinner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261013060204.3138690-40-dgc@kernel.org \
--to=dgc@kernel.org \
--cc=cem@kernel.org \
--cc=linux-xfs@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox