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;
next 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