From: Dave Chinner <dgc@kernel.org>
To: linux-xfs@vger.kernel.org
Cc: cem@kernel.org
Subject: [PATCH 48/48] xfs: validate logged dquot buffers during recovery
Date: Tue, 13 Oct 2026 17:01:38 +1100 [thread overview]
Message-ID: <20261013060204.3138690-49-dgc@kernel.org> (raw)
In-Reply-To: <20261013060204.3138690-1-dgc@kernel.org>
A dquot buffer is only ever logged by xfs_qm_init_dquot_blk(), which initialises
and logs the whole buffer in a single region. So a logged dquot buffer has
exactly two regions, the format header and one data region covering the entire
buffer, and that region holds a full chunk of valid dquots.
Validate this in the buffer item validate_item callback. A buffer carrying a
dquot type flag must have that shape, and every dquot in the logged chunk is
verified so a corrupt dquot is never replayed to disk. The dquot content check
in the replay path only looked at the first dquot and relied on the replay loop
for the region bounds, so it is removed and the replay loop reduces to copying
the logged regions into the buffer.
To share the per-dquot verification, xfs_dquot_verify_chunk() is factored out of
xfs_dquot_buf_verify(). It walks a chunk of dquots and, on failure, updates its
dquot pointer to the offending dquot so the caller can report it. The buffer
read verifier is otherwise unchanged.
Assisted-by: LLM
Signed-off-by: Dave Chinner <dgc@kernel.org>
---
fs/xfs/libxfs/xfs_dquot_buf.c | 64 ++++++++++++++++++-------------
fs/xfs/libxfs/xfs_quota_defs.h | 2 +
fs/xfs/xfs_buf_item_recover.c | 70 +++++++++++++++++++---------------
3 files changed, 78 insertions(+), 58 deletions(-)
diff --git a/fs/xfs/libxfs/xfs_dquot_buf.c b/fs/xfs/libxfs/xfs_dquot_buf.c
index 6c8a9afdfb1c..d6837c840989 100644
--- a/fs/xfs/libxfs/xfs_dquot_buf.c
+++ b/fs/xfs/libxfs/xfs_dquot_buf.c
@@ -179,6 +179,38 @@ xfs_dquot_buf_verify_crc(
return true;
}
+/*
+ * Verify every dquot in a full chunk of dquots. We don't know what the id of
+ * the first dquot is supposed to be, just that they should be increasing
+ * monotonically within the chunk. If the first id is corrupt, then it will fail
+ * on the second dquot in the chunk so corruptions could point to the wrong
+ * dquot in this case. On failure @dqb is updated to point at the failing dquot
+ * so the caller can report it.
+ */
+xfs_failaddr_t
+xfs_dquot_verify_chunk(
+ struct xfs_mount *mp,
+ struct xfs_dqblk **dqb,
+ unsigned int ndquots)
+{
+ xfs_failaddr_t fa;
+ xfs_dqid_t id = 0;
+ unsigned int i;
+
+ for (i = 0; i < ndquots; i++) {
+ if (i == 0)
+ id = be32_to_cpu((*dqb)[i].dd_diskdq.d_id);
+
+ fa = xfs_dqblk_verify(mp, &(*dqb)[i], id + i);
+ if (fa) {
+ *dqb = &(*dqb)[i];
+ return fa;
+ }
+ }
+
+ return NULL;
+}
+
STATIC xfs_failaddr_t
xfs_dquot_buf_verify(
struct xfs_mount *mp,
@@ -187,9 +219,7 @@ xfs_dquot_buf_verify(
{
struct xfs_dqblk *dqb = bp->b_addr;
xfs_failaddr_t fa;
- xfs_dqid_t id = 0;
int ndquots;
- int i;
/*
* if we are in log recovery, the quota subsystem has not been
@@ -201,32 +231,12 @@ xfs_dquot_buf_verify(
else
ndquots = xfs_calc_dquots_per_chunk(bp->b_length);
- /*
- * On the first read of the buffer, verify that each dquot is valid.
- * We don't know what the id of the dquot is supposed to be, just that
- * they should be increasing monotonically within the buffer. If the
- * first id is corrupt, then it will fail on the second dquot in the
- * buffer so corruptions could point to the wrong dquot in this case.
- */
- for (i = 0; i < ndquots; i++) {
- struct xfs_disk_dquot *ddq;
+ fa = xfs_dquot_verify_chunk(mp, &dqb, ndquots);
+ if (fa && !readahead)
+ xfs_buf_verifier_error(bp, -EFSCORRUPTED, __func__, dqb,
+ sizeof(struct xfs_dqblk), fa);
- ddq = &dqb[i].dd_diskdq;
-
- if (i == 0)
- id = be32_to_cpu(ddq->d_id);
-
- fa = xfs_dqblk_verify(mp, &dqb[i], id + i);
- if (fa) {
- if (!readahead)
- xfs_buf_verifier_error(bp, -EFSCORRUPTED,
- __func__, &dqb[i],
- sizeof(struct xfs_dqblk), fa);
- return fa;
- }
- }
-
- return NULL;
+ return fa;
}
static xfs_failaddr_t
diff --git a/fs/xfs/libxfs/xfs_quota_defs.h b/fs/xfs/libxfs/xfs_quota_defs.h
index 551d7ae46c5c..73d82edf0969 100644
--- a/fs/xfs/libxfs/xfs_quota_defs.h
+++ b/fs/xfs/libxfs/xfs_quota_defs.h
@@ -132,6 +132,8 @@ extern xfs_failaddr_t xfs_dquot_verify(struct xfs_mount *mp,
struct xfs_disk_dquot *ddq, xfs_dqid_t id);
extern xfs_failaddr_t xfs_dqblk_verify(struct xfs_mount *mp,
struct xfs_dqblk *dqb, xfs_dqid_t id);
+xfs_failaddr_t xfs_dquot_verify_chunk(struct xfs_mount *mp,
+ struct xfs_dqblk **dqb, unsigned int ndquots);
extern int xfs_calc_dquots_per_chunk(unsigned int nbblks);
extern void xfs_dqblk_repair(struct xfs_mount *mp, struct xfs_dqblk *dqb,
xfs_dqid_t id, xfs_dqtype_t type);
diff --git a/fs/xfs/xfs_buf_item_recover.c b/fs/xfs/xfs_buf_item_recover.c
index 27cab5afc8ba..be1558d851db 100644
--- a/fs/xfs/xfs_buf_item_recover.c
+++ b/fs/xfs/xfs_buf_item_recover.c
@@ -481,8 +481,6 @@ xlog_recover_do_reg_buffer(
int i;
int bit;
int nbits;
- xfs_failaddr_t fa;
- const size_t size_disk_dquot = sizeof(struct xfs_disk_dquot);
trace_xfs_log_recover_buf_reg_buf(mp->m_log, buf_f);
@@ -496,39 +494,10 @@ xlog_recover_do_reg_buffer(
nbits = xfs_contig_bits(buf_f->blf_data_map,
buf_f->blf_map_size, bit);
- /*
- * Do a sanity check if this is a dquot buffer. Just checking
- * the first dquot in the buffer should do. XXXThis is
- * probably a good thing to do for other buf types also.
- */
- fa = NULL;
- if (buf_f->blf_flags &
- (XFS_BLF_UDQUOT_BUF|XFS_BLF_PDQUOT_BUF|XFS_BLF_GDQUOT_BUF)) {
- if (item->ri_buf[i].iov_base == NULL) {
- xfs_alert(mp,
- "XFS: NULL dquot in %s.", __func__);
- goto next;
- }
- if (item->ri_buf[i].iov_len < size_disk_dquot) {
- xfs_alert(mp,
- "XFS: dquot too small (%zd) in %s.",
- item->ri_buf[i].iov_len, __func__);
- goto next;
- }
- fa = xfs_dquot_verify(mp, item->ri_buf[i].iov_base, -1);
- if (fa) {
- xfs_alert(mp,
- "dquot corrupt at %pS trying to replay into block 0x%llx",
- fa, xfs_buf_daddr(bp));
- goto next;
- }
- }
-
memcpy(xfs_buf_offset(bp,
(uint)bit << XFS_BLF_SHIFT), /* dest */
item->ri_buf[i].iov_base, /* source */
nbits<<XFS_BLF_SHIFT); /* length */
- next:
i++;
bit += nbits;
}
@@ -1246,6 +1215,41 @@ xlog_recover_buf_verify_type(
return NULL;
}
+/*
+ * A dquot buffer is only ever logged by xfs_qm_init_dquot_blk(), which
+ * initialises and logs the entire buffer in one go. So a logged dquot buffer
+ * has exactly one data region covering the whole buffer, and that region is a
+ * full chunk of valid dquots. Reject anything else, then verify every dquot in
+ * the logged chunk so a corrupt dquot is not replayed onto disk.
+ */
+static int
+xlog_recover_buf_validate_dquot(
+ struct xlog *log,
+ struct xlog_recover_item *item,
+ struct xfs_buf_log_format *blf)
+{
+ struct xfs_dqblk *dqb = item->ri_buf[1].iov_base;
+ xfs_failaddr_t fa;
+ unsigned int ndquots;
+
+ if (item->ri_total != 2 ||
+ item->ri_buf[1].iov_len != BBTOB(blf->blf_len)) {
+ xfs_warn(log->l_mp,
+ "BUF: dquot buffer is not a single whole region recovered.");
+ return -EFSCORRUPTED;
+ }
+
+ ndquots = xfs_calc_dquots_per_chunk(blf->blf_len);
+ fa = xfs_dquot_verify_chunk(log->l_mp, &dqb, ndquots);
+ if (fa) {
+ xfs_warn(log->l_mp,
+ "BUF: dquot at %pS failed validation recovered.", fa);
+ return -EFSCORRUPTED;
+ }
+
+ return 0;
+}
+
/*
* Validate the whole buffer item once all regions are assembled. The item size
* must match the number of regions attached. A cancelled buffer is not
@@ -1346,6 +1350,10 @@ xlog_recover_buf_validate_item(
if (ri != item->ri_total || total > BBTOB(blf->blf_len))
goto bad_map;
+ if (blf->blf_flags &
+ (XFS_BLF_UDQUOT_BUF | XFS_BLF_PDQUOT_BUF | XFS_BLF_GDQUOT_BUF))
+ return xlog_recover_buf_validate_dquot(log, item, blf);
+
return 0;
bad_map:
--
2.55.0
prev parent reply other threads:[~2026-10-13 6:03 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 ` [PATCH 39/48] xfs: validate logged extent " Dave Chinner
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 ` Dave Chinner [this message]
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-49-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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.