Linux XFS filesystem development
 help / color / mirror / Atom feed
From: Dave Chinner <dgc@kernel.org>
To: linux-xfs@vger.kernel.org
Cc: cem@kernel.org
Subject: [PATCH 45/48] xfs: validate the whole buffer item during recovery
Date: Tue, 13 Oct 2026 17:01:35 +1100	[thread overview]
Message-ID: <20261013060204.3138690-46-dgc@kernel.org> (raw)
In-Reply-To: <20261013060204.3138690-1-dgc@kernel.org>

Validate a buffer log item once all its regions are decoded from the journal, so
the rest of recovery can trust it. The item size must match the number of
regions attached, and the buffer must lie wholly within the data device.

A cancelled buffer is not replayed and logs only its format header, so it is
required to be a single region and nothing further is checked. Any other buffer
must record a valid buffer type that agrees with its type flags, and its dirty
regions must correspond to the dirty bitmap without describing more than a
buffer's worth of data.

The buffer block range is checked against the data device size held in the
buftarg rather than the superblock geometry. Deriving a shared daddr range
helper is left for later.

Assisted-by: LLM
Signed-off-by: Dave Chinner <dgc@kernel.org>
---
 fs/xfs/xfs_buf_item_recover.c | 153 ++++++++++++++++++++++++++++++++++
 1 file changed, 153 insertions(+)

diff --git a/fs/xfs/xfs_buf_item_recover.c b/fs/xfs/xfs_buf_item_recover.c
index 8cae5287e4fb..309260e65889 100644
--- a/fs/xfs/xfs_buf_item_recover.c
+++ b/fs/xfs/xfs_buf_item_recover.c
@@ -1233,11 +1233,164 @@ xlog_recover_buf_validate_region(
 	return 0;
 }
 
+/* The buffer type flags. A buffer has at most one of these set. */
+#define XFS_BLF_TYPE_MASK \
+	(XFS_BLF_INODE_BUF | XFS_BLF_UDQUOT_BUF | XFS_BLF_PDQUOT_BUF | \
+	 XFS_BLF_GDQUOT_BUF)
+
+/*
+ * The buffer type is recorded both in the top bits of the format flags and in
+ * the type flags. Check the two agree: a buffer carries at most one type flag,
+ * and it must be the one the type field describes.
+ */
+static xfs_failaddr_t
+xlog_recover_buf_verify_type(
+	uint16_t			blft,
+	unsigned int			type)
+{
+	if (blft <= XFS_BLFT_UNKNOWN_BUF || blft >= XFS_BLFT_MAX_BUF)
+		return __this_address;
+
+	switch (blft) {
+	case XFS_BLFT_DINO_BUF:
+		if (type != XFS_BLF_INODE_BUF)
+			return __this_address;
+		break;
+	case XFS_BLFT_UDQUOT_BUF:
+		if (type != XFS_BLF_UDQUOT_BUF)
+			return __this_address;
+		break;
+	case XFS_BLFT_PDQUOT_BUF:
+		if (type != XFS_BLF_PDQUOT_BUF)
+			return __this_address;
+		break;
+	case XFS_BLFT_GDQUOT_BUF:
+		if (type != XFS_BLF_GDQUOT_BUF)
+			return __this_address;
+		break;
+	default:
+		if (type)
+			return __this_address;
+		break;
+	}
+
+	return NULL;
+}
+
+/*
+ * 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
+ * replayed, so it logs only the format header. Any other buffer must have a
+ * valid type, and its dirty regions must match the dirty bitmap and not
+ * describe more than a buffer's worth of data.
+ */
+static int
+xlog_recover_buf_validate_item(
+	struct xlog			*log,
+	struct xlog_recover_item	*item)
+{
+	struct xfs_buf_log_format	*blf = item->ri_buf[0].iov_base;
+	xfs_daddr_t			eofs = log->l_mp->m_ddev_targp->bt_nr_sectors;
+	xfs_failaddr_t			fa;
+	size_t				total = 0;
+	unsigned int			ri = 1;
+	int				first_bit;
+
+	if (blf->blf_size != item->ri_total) {
+		xfs_warn(log->l_mp,
+			"BUF: item size %u does not match region count %u recovered.",
+			blf->blf_size, item->ri_total);
+		return -EFSCORRUPTED;
+	}
+
+	/*
+	 * The buffer length is an invariant: a buffer cannot be larger than the
+	 * most data that can be logged in a single region.
+	 */
+	if (blf->blf_len == 0 || BBTOB(blf->blf_len) > XLOG_MAX_REGION_SIZE) {
+		xfs_warn(log->l_mp,
+			"BUF: bad buffer length %u recovered.", blf->blf_len);
+		return -EFSCORRUPTED;
+	}
+
+	/*
+	 * The buffer must lie wholly within the data device. Guard against the
+	 * end wrapping so an out of range buffer cannot pass the bounds check.
+	 */
+	if (blf->blf_blkno < 0 ||
+	    blf->blf_blkno >= eofs ||
+	    blf->blf_blkno + blf->blf_len < blf->blf_blkno ||
+	    blf->blf_blkno + blf->blf_len > eofs) {
+		xfs_warn(log->l_mp,
+			"BUF: bad buffer block 0x%llx length %u recovered.",
+			blf->blf_blkno, blf->blf_len);
+		return -EFSCORRUPTED;
+	}
+
+	/*
+	 * A cancelled buffer overrides everything else. It is not replayed, so
+	 * it logs only the format header regardless of what the dirty bitmap
+	 * holds, and nothing else needs to be checked.
+	 */
+	if (blf->blf_flags & XFS_BLF_CANCEL) {
+		if (blf->blf_size != 1) {
+			xfs_warn(log->l_mp,
+				"BUF: cancelled buffer has %u regions recovered.",
+				blf->blf_size);
+			return -EFSCORRUPTED;
+		}
+		return 0;
+	}
+
+	fa = xlog_recover_buf_verify_type(xfs_blft_from_flags(blf),
+			blf->blf_flags & XFS_BLF_TYPE_MASK);
+	if (fa) {
+		xfs_warn(log->l_mp,
+			"BUF: type %u and flags 0x%x disagree recovered at %pS",
+			xfs_blft_from_flags(blf), blf->blf_flags, fa);
+		return -EFSCORRUPTED;
+	}
+
+	/*
+	 * Each dirty region covers one contiguous run of set bits in the dirty
+	 * bitmap. Walk the bitmap and the regions together: each region size
+	 * must match the length of the run it covers, each run must lie within
+	 * the buffer, and the regions must exactly cover the bitmap. The dirty
+	 * data across all regions cannot be larger than the buffer itself.
+	 */
+	first_bit = xfs_next_bit(blf->blf_data_map, blf->blf_map_size, 0);
+	while (first_bit != -1) {
+		int	nbits = xfs_contig_bits(blf->blf_data_map,
+				blf->blf_map_size, first_bit);
+
+		if (ri >= item->ri_total ||
+		    item->ri_buf[ri].iov_len != (nbits << XFS_BLF_SHIFT) ||
+		    ((first_bit + nbits) << XFS_BLF_SHIFT) > BBTOB(blf->blf_len))
+			goto bad_map;
+
+		total += item->ri_buf[ri].iov_len;
+		ri++;
+		first_bit = xfs_next_bit(blf->blf_data_map, blf->blf_map_size,
+				first_bit + nbits + 1);
+	}
+
+	if (ri != item->ri_total || total > BBTOB(blf->blf_len))
+		goto bad_map;
+
+	return 0;
+
+bad_map:
+	xfs_warn(log->l_mp,
+		"BUF: dirty bitmap does not match regions recovered.");
+	return -EFSCORRUPTED;
+}
+
 const struct xlog_recover_item_ops xlog_buf_item_ops = {
 	.item_type		= XFS_LI_BUF,
 	.min_hdr_len		= offsetof(struct xfs_buf_log_format, blf_data_map),
 	.validate_nregions	= xlog_recover_buf_nregions,
 	.validate_region	= xlog_recover_buf_validate_region,
+	.validate_item		= xlog_recover_buf_validate_item,
 	.reorder		= xlog_recover_buf_reorder,
 	.ra_pass2		= xlog_recover_buf_ra_pass2,
 	.commit_pass1		= xlog_recover_buf_commit_pass1,
-- 
2.55.0


  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 ` Dave Chinner [this message]
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-46-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