* [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* Re: [PATCH] xfs: validate btree block level on read
2026-07-31 12:03 [PATCH] xfs: validate btree block level on read David Lee
@ 2026-07-31 23:28 ` Dave Chinner
0 siblings, 0 replies; 2+ messages in thread
From: Dave Chinner @ 2026-07-31 23:28 UTC (permalink / raw)
To: David Lee
Cc: cem, Kyle Zeng, Dominik 'Disconnect3d' Czarnota,
linux-xfs, linux-kernel
On Fri, Jul 31, 2026 at 12:03:42PM +0000, David Lee wrote:
> 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.
We already have a centralised function that does all this level
check (and more): xfs_btree_lookup_get_block(). It is already passed
the expected level, and it also checks various other per-usage
header information like owner, level specific numrecs bounds, etc.
If we have to verify per-block context state, then nothing should be
calling xfs_btree_read_buf_block() directly except
xfs_btree_lookup_get_block(), and all the read context checks should
be performed in xfs_btree_lookup_get_block() (as they already are).
> 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.
Great. How did you reproduce the issue and test the fix is correct?
Did you submit an fstests that reproduces the issue and verifies the
fix works?
> Assisted-by: Codex:gpt-5.6-sol gpt-5.5-cyber
> Signed-off-by: Kyle Zeng <kylebot@openai.com>
Yeah, nah. Patches have to be submitted by a real person, not a bot
account, and SOB should reflect that.
-Dave.
--
Dave Chinner
dgc@kernel.org
^ permalink raw reply [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