From: Weiming Shi <bestswngs@gmail.com>
To: linux-btrfs@vger.kernel.org
Cc: dsterba@suse.com, josef@toxicpanda.com, clm@fb.com,
xmei5@asu.edu, Weiming Shi <bestswngs@gmail.com>
Subject: [PATCH v2] btrfs: tree-checker: validate inode_ref and root_ref name lengths
Date: Mon, 8 Jun 2026 01:35:10 -0700 [thread overview]
Message-ID: <20260608083509.3907960-2-bestswngs@gmail.com> (raw)
The on-disk name length of an INODE_REF and a ROOT_REF/ROOT_BACKREF is a
__le16 and can be up to 65535, but a btrfs name is never longer than
BTRFS_NAME_LEN (255). The tree-checker validated only that a ref's name
stays within the item, not that name_len <= BTRFS_NAME_LEN, and it did
not validate ROOT_REF/ROOT_BACKREF items at all. A reader that copies
name_len bytes into a NAME_MAX-sized buffer therefore overflows it.
For example btrfs_get_name(), used by the NFS/exportfs ->get_name op,
copies the inode_ref/root_ref name into the caller's NAME_MAX + 1 byte
stack buffer with no bound of its own, which a crafted leaf with
name_len = 4096 overflows. It is reachable from a mounted untrusted
btrfs image via open_by_handle_at(), and on btrfs exported over NFS:
BUG: KASAN: stack-out-of-bounds in read_extent_buffer (fs/btrfs/extent_io.c:3742)
Write of size 633 at addr ffffc90006c9fc40 by task exploit/5192
read_extent_buffer (fs/btrfs/extent_io.c:3742)
btrfs_get_name (fs/btrfs/export.c:289)
reconnect_path (fs/exportfs/expfs.c:222)
exportfs_decode_fh_raw (fs/exportfs/expfs.c:473)
do_handle_open (fs/fhandle.c:230)
do_syscall_64 (arch/x86/entry/syscall_64.c:94)
Kernel panic - not syncing: stack-protector: Kernel stack is corrupted
Reject name_len > BTRFS_NAME_LEN for INODE_REF, and add a check_root_ref()
that validates ROOT_REF/ROOT_BACKREF item size and name length. Bounding
the length at read time protects every consumer instead of patching each
copy site, so the corrupted leaf is refused when the tree block is read.
Fixes: 2ede0daf0154 ("Btrfs: handle NFS lookups properly")
Reported-by: Xiang Mei <xmei5@asu.edu>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
---
v2:
- Check name_len in the tree-checker instead of btrfs_get_name().
- Add check_root_ref() for ROOT_REF/ROOT_BACKREF items.
fs/btrfs/tree-checker.c | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index 60bba7fbe..fedb0bd55 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -1760,6 +1760,12 @@ static int check_inode_ref(struct extent_buffer *leaf,
ptr, end, namelen);
return -EUCLEAN;
}
+ if (unlikely(namelen > BTRFS_NAME_LEN)) {
+ inode_ref_err(leaf, slot,
+ "inode ref name too long, namelen %u max %u",
+ namelen, BTRFS_NAME_LEN);
+ return -EUCLEAN;
+ }
/*
* NOTE: In theory we should record all found index numbers
@@ -1804,6 +1810,36 @@ static int check_inode_extref(struct extent_buffer *leaf,
return 0;
}
+static int check_root_ref(struct extent_buffer *leaf, int slot)
+{
+ struct btrfs_root_ref *ref;
+ u32 item_size = btrfs_item_size(leaf, slot);
+ u16 namelen;
+
+ if (unlikely(item_size < sizeof(*ref))) {
+ generic_err(leaf, slot,
+ "invalid root ref item size, have %u expect >= %zu",
+ item_size, sizeof(*ref));
+ return -EUCLEAN;
+ }
+
+ ref = btrfs_item_ptr(leaf, slot, struct btrfs_root_ref);
+ namelen = btrfs_root_ref_name_len(leaf, ref);
+ if (unlikely(sizeof(*ref) + namelen != item_size)) {
+ generic_err(leaf, slot,
+ "invalid root ref name len, have %u expect %zu",
+ namelen, item_size - sizeof(*ref));
+ return -EUCLEAN;
+ }
+ if (unlikely(namelen > BTRFS_NAME_LEN)) {
+ generic_err(leaf, slot,
+ "root ref name too long, namelen %u max %u",
+ namelen, BTRFS_NAME_LEN);
+ return -EUCLEAN;
+ }
+ return 0;
+}
+
static int check_raid_stripe_extent(const struct extent_buffer *leaf,
const struct btrfs_key *key, int slot)
{
@@ -1937,6 +1973,10 @@ static enum btrfs_tree_block_status check_leaf_item(struct extent_buffer *leaf,
case BTRFS_ROOT_ITEM_KEY:
ret = check_root_item(leaf, key, slot);
break;
+ case BTRFS_ROOT_REF_KEY:
+ case BTRFS_ROOT_BACKREF_KEY:
+ ret = check_root_ref(leaf, slot);
+ break;
case BTRFS_EXTENT_ITEM_KEY:
case BTRFS_METADATA_ITEM_KEY:
ret = check_extent_item(leaf, key, slot, prev_key);
--
2.43.0
next reply other threads:[~2026-06-08 8:35 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-08 8:35 Weiming Shi [this message]
2026-06-08 9:19 ` [PATCH v2] btrfs: tree-checker: validate inode_ref and root_ref name lengths Qu Wenruo
2026-06-10 10:45 ` Weiming Shi
2026-06-10 11:26 ` Qu Wenruo
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=20260608083509.3907960-2-bestswngs@gmail.com \
--to=bestswngs@gmail.com \
--cc=clm@fb.com \
--cc=dsterba@suse.com \
--cc=josef@toxicpanda.com \
--cc=linux-btrfs@vger.kernel.org \
--cc=xmei5@asu.edu \
/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