Linux XFS filesystem development
 help / color / mirror / Atom feed
* [PATCH] xfs: validate btree block level on read
@ 2026-07-31 12:03 David Lee
  2026-07-31 23:28 ` Dave Chinner
  0 siblings, 1 reply; 2+ messages in thread
From: David Lee @ 2026-07-31 12:03 UTC (permalink / raw)
  To: cem
  Cc: David Lee, Kyle Zeng, Dominik 'Disconnect3d' Czarnota,
	linux-xfs, linux-kernel

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;
 

^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2026-07-31 23:28 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-31 12:03 [PATCH] xfs: validate btree block level on read David Lee
2026-07-31 23:28 ` Dave Chinner

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox