The Linux Kernel Mailing List
 help / color / mirror / Atom feed
From: David Lee <david.lee@trailofbits.com>
To: cem@kernel.org
Cc: David Lee <david.lee@trailofbits.com>,
	Kyle Zeng <kylebot@openai.com>,
	Dominik 'Disconnect3d' Czarnota
	<dominik.czarnota@trailofbits.com>,
	linux-xfs@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH] xfs: validate btree block level on read
Date: Fri, 31 Jul 2026 12:03:42 +0000	[thread overview]
Message-ID: <20260731120344.558808-1-david.lee@trailofbits.com> (raw)

Btree buffer verifiers validate record counts against the level stored in
the block itself.  Callers of xfs_btree_read_buf_block(), however, use
the returned block according to the level they are traversing.  A
corrupted sibling pointer can therefore return a verifier-valid block
from another level and cause out-of-bounds accesses when layout-specific
entries are moved.

Pass the expected level into xfs_btree_read_buf_block() and reject
blocks whose on-disk level differs before returning them.  This
centralizes the check for traversal and sibling reads.

Fixes: 3d3e6f64e22c ("xfs: verify btree blocks as they are read from disk")
Bug found and triaged by OpenAI Security Research and 
validated by Trail of Bits.

Assisted-by: Codex:gpt-5.6-sol gpt-5.5-cyber
Signed-off-by: Kyle Zeng <kylebot@openai.com>
---
Trail of Bits has a reproducer for this bug that triggers ㅁ
KASAN use-after-free and can share if needed.

 fs/xfs/libxfs/xfs_btree.c         | 48 +++++++++++++++++++------------
 fs/xfs/libxfs/xfs_btree.h         |  2 +-
 fs/xfs/libxfs/xfs_btree_staging.c |  9 +++---
 3 files changed, 36 insertions(+), 23 deletions(-)

diff --git a/fs/xfs/libxfs/xfs_btree.c b/fs/xfs/libxfs/xfs_btree.c
index 60ef7f08b..b2db143bb 100644
--- a/fs/xfs/libxfs/xfs_btree.c
+++ b/fs/xfs/libxfs/xfs_btree.c
@@ -1378,13 +1378,14 @@ xfs_btree_get_buf_block(
 }
 
 /*
- * Read in the buffer at the given ptr and return the buffer and
- * the block pointer within the buffer.
+ * Read in the buffer at the given ptr and return the buffer and the block
+ * pointer within the buffer.  The block must be at the expected level.
  */
 int
 xfs_btree_read_buf_block(
 	struct xfs_btree_cur		*cur,
 	const union xfs_btree_ptr	*ptr,
+	int				level,
 	int				flags,
 	struct xfs_btree_block		**block,
 	struct xfs_buf			**bpp)
@@ -1409,6 +1410,14 @@ xfs_btree_read_buf_block(
 
 	xfs_btree_set_refs(cur, *bpp);
 	*block = XFS_BUF_TO_BLOCK(*bpp);
+	if (xfs_btree_get_level(*block) != level) {
+		xfs_buf_mark_corrupt(*bpp);
+		xfs_trans_brelse(cur->bc_tp, *bpp);
+		xfs_btree_mark_sick(cur);
+		*block = NULL;
+		*bpp = NULL;
+		return -EFSCORRUPTED;
+	}
 	return 0;
 }
 
@@ -1733,7 +1742,8 @@ xfs_btree_increment(
 
 		ptrp = xfs_btree_ptr_addr(cur, cur->bc_levels[lev].ptr, block);
 		--lev;
-		error = xfs_btree_read_buf_block(cur, ptrp, 0, &block, &bp);
+		error = xfs_btree_read_buf_block(cur, ptrp, lev, 0, &block,
+						 &bp);
 		if (error)
 			goto error0;
 
@@ -1827,7 +1837,8 @@ xfs_btree_decrement(
 
 		ptrp = xfs_btree_ptr_addr(cur, cur->bc_levels[lev].ptr, block);
 		--lev;
-		error = xfs_btree_read_buf_block(cur, ptrp, 0, &block, &bp);
+		error = xfs_btree_read_buf_block(cur, ptrp, lev, 0, &block,
+						 &bp);
 		if (error)
 			goto error0;
 		xfs_btree_setbuf(cur, lev, bp);
@@ -1904,7 +1915,7 @@ xfs_btree_lookup_get_block(
 		return 0;
 	}
 
-	error = xfs_btree_read_buf_block(cur, pp, 0, blkp, &bp);
+	error = xfs_btree_read_buf_block(cur, pp, level, 0, blkp, &bp);
 	if (error)
 		return error;
 
@@ -1912,10 +1923,6 @@ xfs_btree_lookup_get_block(
 	if (xfs_btree_check_block_owner(cur, *blkp) != NULL)
 		goto out_bad;
 
-	/* Did we get the level we were looking for? */
-	if (be16_to_cpu((*blkp)->bb_level) != level)
-		goto out_bad;
-
 	/* Check that internal nodes have at least one record. */
 	if (level != 0 && be16_to_cpu((*blkp)->bb_numrecs) == 0)
 		goto out_bad;
@@ -2457,7 +2464,7 @@ xfs_btree_lshift(
 		goto out0;
 
 	/* Set up the left neighbor as "left". */
-	error = xfs_btree_read_buf_block(cur, &lptr, 0, &left, &lbp);
+	error = xfs_btree_read_buf_block(cur, &lptr, level, 0, &left, &lbp);
 	if (error)
 		goto error0;
 
@@ -2653,7 +2660,7 @@ xfs_btree_rshift(
 		goto out0;
 
 	/* Set up the right neighbor as "right". */
-	error = xfs_btree_read_buf_block(cur, &rptr, 0, &right, &rbp);
+	error = xfs_btree_read_buf_block(cur, &rptr, level, 0, &right, &rbp);
 	if (error)
 		goto error0;
 
@@ -2940,8 +2947,8 @@ __xfs_btree_split(
 	 * point back to right instead of to left.
 	 */
 	if (!xfs_btree_ptr_is_null(cur, &rrptr)) {
-		error = xfs_btree_read_buf_block(cur, &rrptr,
-							0, &rrblock, &rrbp);
+		error = xfs_btree_read_buf_block(cur, &rrptr, level, 0,
+						 &rrblock, &rrbp);
 		if (error)
 			goto error0;
 		xfs_btree_set_sibling(cur, rrblock, &rptr, XFS_BB_LEFTSIB);
@@ -3365,7 +3372,8 @@ xfs_btree_new_root(
 		lbp = bp;
 		xfs_btree_buf_to_ptr(cur, lbp, &lptr);
 		left = block;
-		error = xfs_btree_read_buf_block(cur, &rptr, 0, &right, &rbp);
+		error = xfs_btree_read_buf_block(cur, &rptr,
+						 cur->bc_nlevels - 1, 0, &right, &rbp);
 		if (error)
 			goto error0;
 		bp = rbp;
@@ -3376,7 +3384,8 @@ xfs_btree_new_root(
 		xfs_btree_buf_to_ptr(cur, rbp, &rptr);
 		right = block;
 		xfs_btree_get_sibling(cur, right, &lptr, XFS_BB_LEFTSIB);
-		error = xfs_btree_read_buf_block(cur, &lptr, 0, &left, &lbp);
+		error = xfs_btree_read_buf_block(cur, &lptr,
+						 cur->bc_nlevels - 1, 0, &left, &lbp);
 		if (error)
 			goto error0;
 		bp = lbp;
@@ -4394,7 +4403,8 @@ xfs_btree_delrec(
 		rptr = cptr;
 		right = block;
 		rbp = bp;
-		error = xfs_btree_read_buf_block(cur, &lptr, 0, &left, &lbp);
+		error = xfs_btree_read_buf_block(cur, &lptr, level, 0, &left,
+						 &lbp);
 		if (error)
 			goto error0;
 
@@ -4411,7 +4421,8 @@ xfs_btree_delrec(
 		lptr = cptr;
 		left = block;
 		lbp = bp;
-		error = xfs_btree_read_buf_block(cur, &rptr, 0, &right, &rbp);
+		error = xfs_btree_read_buf_block(cur, &rptr, level, 0, &right,
+						 &rbp);
 		if (error)
 			goto error0;
 
@@ -4483,7 +4494,8 @@ xfs_btree_delrec(
 	/* If there is a right sibling, point it to the remaining block. */
 	xfs_btree_get_sibling(cur, left, &cptr, XFS_BB_RIGHTSIB);
 	if (!xfs_btree_ptr_is_null(cur, &cptr)) {
-		error = xfs_btree_read_buf_block(cur, &cptr, 0, &rrblock, &rrbp);
+		error = xfs_btree_read_buf_block(cur, &cptr, level, 0,
+						 &rrblock, &rrbp);
 		if (error)
 			goto error0;
 		xfs_btree_set_sibling(cur, rrblock, &lptr, XFS_BB_LEFTSIB);
diff --git a/fs/xfs/libxfs/xfs_btree.h b/fs/xfs/libxfs/xfs_btree.h
index 60e78572e..2153ee08d 100644
--- a/fs/xfs/libxfs/xfs_btree.h
+++ b/fs/xfs/libxfs/xfs_btree.h
@@ -650,7 +650,7 @@ int xfs_btree_get_buf_block(struct xfs_btree_cur *cur,
 		const union xfs_btree_ptr *ptr, struct xfs_btree_block **block,
 		struct xfs_buf **bpp);
 int xfs_btree_read_buf_block(struct xfs_btree_cur *cur,
-		const union xfs_btree_ptr *ptr, int flags,
+		const union xfs_btree_ptr *ptr, int level, int flags,
 		struct xfs_btree_block **block, struct xfs_buf **bpp);
 void xfs_btree_set_sibling(struct xfs_btree_cur *cur,
 		struct xfs_btree_block *block, const union xfs_btree_ptr *ptr,
diff --git a/fs/xfs/libxfs/xfs_btree_staging.c b/fs/xfs/libxfs/xfs_btree_staging.c
index c3c7ea548..a8af4f834 100644
--- a/fs/xfs/libxfs/xfs_btree_staging.c
+++ b/fs/xfs/libxfs/xfs_btree_staging.c
@@ -385,6 +385,7 @@ xfs_btree_bload_leaf(
 STATIC int
 xfs_btree_bload_node(
 	struct xfs_btree_cur	*cur,
+	unsigned int		level,
 	unsigned int		recs_this_block,
 	union xfs_btree_ptr	*child_ptr,
 	struct xfs_btree_block	*block)
@@ -407,8 +408,8 @@ xfs_btree_bload_node(
 		 * been reclaimed.  LRU refs will be set on the block, which is
 		 * desirable if the new btree commits.
 		 */
-		ret = xfs_btree_read_buf_block(cur, child_ptr, 0, &child_block,
-				&child_bp);
+		ret = xfs_btree_read_buf_block(cur, child_ptr, level - 1, 0,
+					       &child_block, &child_bp);
 		if (ret)
 			return ret;
 
@@ -767,8 +768,8 @@ xfs_btree_bload(
 			trace_xfs_btree_bload_block(cur, level, i, blocks,
 					&ptr, nr_this_block);
 
-			ret = xfs_btree_bload_node(cur, nr_this_block,
-					&child_ptr, block);
+			ret = xfs_btree_bload_node(cur, level, nr_this_block,
+						   &child_ptr, block);
 			if (ret)
 				goto out;
 

             reply	other threads:[~2026-07-31 12:03 UTC|newest]

Thread overview: 2+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-31 12:03 David Lee [this message]
2026-07-31 23:28 ` [PATCH] xfs: validate btree block level on read 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=20260731120344.558808-1-david.lee@trailofbits.com \
    --to=david.lee@trailofbits.com \
    --cc=cem@kernel.org \
    --cc=dominik.czarnota@trailofbits.com \
    --cc=kylebot@openai.com \
    --cc=linux-kernel@vger.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