mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/5] btrfs: owner refs and tree blocks
@ 2026-11-04 10:23 Siddharth Mishra
  2026-11-04 10:23 ` [PATCH 1/5] btrfs: skip tree blocks in btrfs_get_extent_owner_root() Siddharth Mishra
                   ` (5 more replies)
  0 siblings, 6 replies; 16+ messages in thread
From: Siddharth Mishra @ 2026-11-04 10:23 UTC (permalink / raw)
  To: dsterba
  Cc: wqu, boris, josef, fdmanana, mason, linux-btrfs, linux-kernel,
	Siddharth Mishra

Simple quotas store the owner of a data extent in an inline owner ref.
Tree blocks never get one: their owner is in the tree block header.

Patch 1 fixes btrfs_get_extent_owner_root(), which also looks for an
owner ref on tree blocks. On a filesystem without skinny-metadata it
reads the btrfs_tree_block_info as an inline ref and warns on subvolume
deletion, snapshot writes and balance. The filesystem is valid.

Patches 2 and 3 are hardening. The tree-checker rejects refs whose type
does not match the extent type: inline refs in patch 2 (for example an
owner ref on a tree block), and keyed refs in the same leaf as their
extent item in patch 3.

Patches 4 and 5 are for btrfs-progs (devel 3737c260) and have the same
cause: a tree block without skinny-metadata is taken for data. mkfs and
btrfs check do not count such tree blocks in simple quotas, and
btrfstune --remove-simple-quota can corrupt their extent items.

Found by fuzzing. An AI tool helped with the analysis and the patches.

Tested on btrfs for-next df5b88a7914e, KASAN and non-debug kernels:
- Patch 1: no warning on subvolume deletion, snapshot writes and
  metadata balance without skinny-metadata; same qgroup numbers.
- Patches 2 and 3: crafted images with an owner ref on a tree block,
  and with a keyed tree block ref on a data extent, are refused at
  read time instead of aborting a transaction later. No valid
  filesystem was rejected: skinny and non-skinny, 4K nodes, mixed block
  groups, no quota, qgroups and simple quotas, with reflinks, snapshots,
  balance and scrub, and filesystems from btrfs-convert, mkfs --rootdir
  and btrfstune -x.
- fstests btrfs/quick, and the qgroup, balance, snapshot and subvol
  groups with -O ^skinny-metadata: no new failure.
- Patches 4 and 5: mkfs -O squota,^skinny-metadata writes the right
  qgroup counts; btrfstune --remove-simple-quota leaves the tree blocks
  intact and btrfs check is clean (before: "backpointer mismatch").

The crafted images are available on request.

Siddharth Mishra (5):
  btrfs: skip tree blocks in btrfs_get_extent_owner_root()
  btrfs: tree-checker: reject inline refs not matching the extent type
  btrfs: tree-checker: reject keyed refs not matching the extent type
  btrfs-progs: check: count non-skinny tree blocks in simple quotas
  btrfs-progs: tune: do not remove owner refs from tree blocks

 fs/btrfs/extent-tree.c  |  3 +++
 fs/btrfs/tree-checker.c | 56 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 57 insertions(+), 2 deletions(-)

 check/qgroup-verify.c | 5 +++--
 tune/quota.c          | 4 +++-
 2 files changed, 6 insertions(+), 3 deletions(-)


base-commit: df5b88a7914e18488908ddf13e14e31ab39f954c
-- 
2.54.0


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

* [PATCH 1/5] btrfs: skip tree blocks in btrfs_get_extent_owner_root()
  2026-11-04 10:23 [PATCH 0/5] btrfs: owner refs and tree blocks Siddharth Mishra
@ 2026-11-04 10:23 ` Siddharth Mishra
  2026-11-04 19:13   ` Boris Burkov
  2026-11-04 10:23 ` [PATCH 2/5] btrfs: tree-checker: reject inline refs not matching the extent type Siddharth Mishra
                   ` (4 subsequent siblings)
  5 siblings, 1 reply; 16+ messages in thread
From: Siddharth Mishra @ 2026-11-04 10:23 UTC (permalink / raw)
  To: dsterba
  Cc: wqu, boris, josef, fdmanana, mason, linux-btrfs, linux-kernel,
	Siddharth Mishra, stable

[BUG]
With simple quotas enabled on a filesystem without the skinny-metadata
feature, deleting a subvolume triggers the following warning:

 ------------[ cut here ]------------
 WARNING: fs/btrfs/extent-tree.c:393 at
   btrfs_get_extent_inline_ref_type+0x1b9/0x2d0, CPU#1: btrfs-cleaner/84
 RIP: 0010:btrfs_get_extent_inline_ref_type (fs/btrfs/extent-tree.c:393)
 Call Trace:
  <TASK>
  btrfs_get_extent_owner_root (fs/btrfs/extent-tree.c:3128)
  btrfs_lookup_extent_info (fs/btrfs/extent-tree.c:187)
  walk_down_proc (fs/btrfs/extent-tree.c:5647)
  walk_down_tree (fs/btrfs/extent-tree.c:6170)
  btrfs_drop_snapshot (fs/btrfs/extent-tree.c:6392)
  btrfs_clean_one_deleted_snapshot (fs/btrfs/transaction.c:2728)
  cleaner_kthread (fs/btrfs/disk-io.c:1553)
  kthread (kernel/kthread.c:436)
  ret_from_fork (arch/x86/kernel/process.c:158)
  ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
  </TASK>
 ---[ end trace 0000000000000000 ]---
 BTRFS error (device vda): eb 30720000 iref 0x3ee6 invalid extent
   inline ref type 0

Writing to a snapshot (update_ref_for_cow()) or a metadata balance
(relocate_block_group()) trigger the same warning.

[CAUSE]
Without skinny-metadata, the extent item of a tree block is an
EXTENT_ITEM with the TREE_BLOCK flag, and a btrfs_tree_block_info sits
between the btrfs_extent_item and the first inline ref:

	item 3 key (30441472 EXTENT_ITEM 16384) itemoff 16079 itemsize 51
		refs 1 gen 5 flags TREE_BLOCK
		tree block key (256 INODE_ITEM 0) level 0
		^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
		(176 0x5) tree block backref root FS_TREE

But btrfs_get_extent_owner_root() always takes the first inline ref
right after the btrfs_extent_item:

	ptr = (unsigned long)(ei + 1);

So for such a tree block it reads the lowest byte of the first key
objectid as the inline ref type. For the key above that byte is 0, and
btrfs_get_extent_inline_ref_type() warns. If the byte happens to be
BTRFS_EXTENT_OWNER_REF_KEY (172), the key bytes are returned as the
owner root.

The filesystem is valid. It can be reproduced with:

  mkfs.btrfs -f -O ^skinny-metadata $dev
  mount $dev $mnt
  btrfs quota enable --simple $mnt
  btrfs subvolume create $mnt/sv
  btrfs subvolume delete $mnt/sv
  btrfs subvolume sync $mnt

[FIX]
Owner refs are only added to data extents, never to tree blocks, so
return 0 for an extent item with the TREE_BLOCK flag before looking at
its inline refs. This covers both the EXTENT_ITEM and the METADATA_ITEM
layouts, and gives the same result as on a skinny-metadata filesystem,
where the first inline ref of a tree block is never an owner ref.

Fixes: 8d2990914073 ("btrfs: add helper for inline owner ref lookup")
Cc: stable@vger.kernel.org # 6.7+
Signed-off-by: Siddharth Mishra <smish149@asu.edu>
---
 fs/btrfs/extent-tree.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c
index 9b0a5c6b9..3ab4f2696 100644
--- a/fs/btrfs/extent-tree.c
+++ b/fs/btrfs/extent-tree.c
@@ -3117,6 +3117,9 @@ u64 btrfs_get_extent_owner_root(struct btrfs_fs_info *fs_info,
 		return 0;
 
 	ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item);
+	if (btrfs_extent_flags(leaf, ei) & BTRFS_EXTENT_FLAG_TREE_BLOCK)
+		return 0;
+
 	ptr = (unsigned long)(ei + 1);
 	end = (unsigned long)ei + btrfs_item_size(leaf, slot);
 
-- 
2.54.0


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

* [PATCH 2/5] btrfs: tree-checker: reject inline refs not matching the extent type
  2026-11-04 10:23 [PATCH 0/5] btrfs: owner refs and tree blocks Siddharth Mishra
  2026-11-04 10:23 ` [PATCH 1/5] btrfs: skip tree blocks in btrfs_get_extent_owner_root() Siddharth Mishra
@ 2026-11-04 10:23 ` Siddharth Mishra
  2026-11-04 19:24   ` Boris Burkov
  2026-11-04 10:23 ` [PATCH 3/5] btrfs: tree-checker: reject keyed " Siddharth Mishra
                   ` (3 subsequent siblings)
  5 siblings, 1 reply; 16+ messages in thread
From: Siddharth Mishra @ 2026-11-04 10:23 UTC (permalink / raw)
  To: dsterba
  Cc: wqu, boris, josef, fdmanana, mason, linux-btrfs, linux-kernel,
	Siddharth Mishra

[BACKGROUND]
An extent item is either for a tree block or for data, and each kind
only gets its own inline ref types:

  TREE_BLOCK: TREE_BLOCK_REF, SHARED_BLOCK_REF
  DATA:       EXTENT_OWNER_REF, EXTENT_DATA_REF, SHARED_DATA_REF

Readers rely on this.  btrfs_get_extent_owner_root() only looks for an
owner ref on data extents, and btrfs_get_extent_inline_ref_type() warns
when a caller that expects a tree block ref or a data ref gets the
other kind.

[CAUSE]
check_extent_item() verifies each inline ref on its own, but never
checks that its type matches the TREE_BLOCK or DATA flag of the extent
item.  So a data ref or an owner ref on a tree block, or a tree block
ref on a data extent, passes the tree-checker.  For example this tree
block has an owner ref instead of its tree block ref:

	item 9 key (30478336 METADATA_ITEM 0) itemoff 3636 itemsize 33
		refs 1 gen 13 flags TREE_BLOCK
		tree block skinny level 0
		(172 0x5) extent owner root 5
		^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

The leaf is accepted.  When the tree block is freed later,
lookup_inline_extent_backref() skips the owner ref, finds no tree block
ref, and the transaction aborts:

  WARNING: fs/btrfs/extent-tree.c:3418 at __btrfs_free_extent
  BTRFS error (device vda state A): Transaction 14 aborted (-EUCLEAN)
  BTRFS info (device vda state EA): forced readonly

[ENHANCEMENT]
Reject an inline ref whose type does not match the extent flags with
-EUCLEAN, so the leaf is refused when it is read:

  BTRFS critical (device vda): corrupt leaf: block=30441472 slot=9
  extent bytenr=30478336 len=4096 inline ref type 172 does not match
  extent flags 0x2

Signed-off-by: Siddharth Mishra <smish149@asu.edu>
---
 fs/btrfs/tree-checker.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index f021ac14b..db446c8af 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -1790,6 +1790,14 @@ static int check_extent_item(struct extent_buffer *leaf,
 				   inline_type);
 			return -EUCLEAN;
 		}
+		if (unlikely(is_tree_block !=
+			     (inline_type == BTRFS_TREE_BLOCK_REF_KEY ||
+			      inline_type == BTRFS_SHARED_BLOCK_REF_KEY))) {
+			extent_err(leaf, slot,
+			"inline ref type %u does not match extent flags 0x%llx",
+				   inline_type, flags);
+			return -EUCLEAN;
+		}
 		if (unlikely(inline_type < last_type)) {
 			extent_err(leaf, slot,
 				   "inline ref out-of-order: has type %u, prev type %u",
-- 
2.54.0


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

* [PATCH 3/5] btrfs: tree-checker: reject keyed refs not matching the extent type
  2026-11-04 10:23 [PATCH 0/5] btrfs: owner refs and tree blocks Siddharth Mishra
  2026-11-04 10:23 ` [PATCH 1/5] btrfs: skip tree blocks in btrfs_get_extent_owner_root() Siddharth Mishra
  2026-11-04 10:23 ` [PATCH 2/5] btrfs: tree-checker: reject inline refs not matching the extent type Siddharth Mishra
@ 2026-11-04 10:23 ` Siddharth Mishra
  2026-11-04 19:29   ` Boris Burkov
  2026-11-04 10:23 ` [PATCH 4/5] btrfs-progs: check: count non-skinny tree blocks in simple quotas Siddharth Mishra
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 16+ messages in thread
From: Siddharth Mishra @ 2026-11-04 10:23 UTC (permalink / raw)
  To: dsterba
  Cc: wqu, boris, josef, fdmanana, mason, linux-btrfs, linux-kernel,
	Siddharth Mishra

[BACKGROUND]
When the refs of an extent do not fit into its extent item, the others
are stored as keyed ref items with the same objectid, right after the
extent item:

  TREE_BLOCK:  TREE_BLOCK_REF and SHARED_BLOCK_REF items
  DATA:        EXTENT_DATA_REF and SHARED_DATA_REF items

[CAUSE]
check_simple_keyed_refs() and check_extent_data_ref() only verify the
keyed ref itself.  Nothing checks that its type matches the extent it
belongs to, so a tree block ref for a data extent, or a data ref for a
tree block, passes the tree-checker.  For example this data extent is
followed by a keyed tree block ref:

	item 0 key (13631488 EXTENT_ITEM 32768) itemoff 3797 itemsize 198
		refs 41 gen 9 flags DATA
		(178 0xdea30debbf5f519) extent data backref root 256
		objectid 258 offset 0 count 1
		...
	item 1 key (13631488 TREE_BLOCK_REF 5) itemoff 3797 itemsize 0
	           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
		tree block backref

The leaf is accepted.  When the extent is freed later, the refs do not
add up and the transaction aborts:

  WARNING: fs/btrfs/extent-tree.c:3418 at __btrfs_free_extent
  BTRFS error (device vda state A): Transaction 35 aborted (-EUCLEAN)
  BTRFS info (device vda state EA): forced readonly

[ENHANCEMENT]
Check each keyed ref against the item right before it.  Keyed refs sort
right after their extent item, so that item is either the extent item
or another keyed ref of the same extent, and both must be of the same
kind.  Reject a mismatch with -EUCLEAN, so the leaf is refused when it
is read:

  BTRFS critical (device vda): corrupt leaf: block=31678464 slot=1
  extent bytenr=13631488 len=4096 keyed ref type 176 does not match the
  extent type

This is the keyed counterpart of the inline ref check.

If the item before a keyed ref belongs to another extent, its extent
item is in another leaf and the keyed ref cannot be checked by the
tree-checker.

Signed-off-by: Siddharth Mishra <smish149@asu.edu>
---
 fs/btrfs/tree-checker.c | 48 +++++++++++++++++++++++++++++++++++++++--
 1 file changed, 46 insertions(+), 2 deletions(-)

diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index db446c8af..d966243c9 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -1854,6 +1854,50 @@ static int check_extent_item(struct extent_buffer *leaf,
 	return 0;
 }
 
+static int check_keyed_ref_extent_type(const struct extent_buffer *leaf,
+				       const struct btrfs_key *key, int slot)
+{
+	const bool is_tree_block_ref = (key->type == BTRFS_TREE_BLOCK_REF_KEY ||
+					key->type == BTRFS_SHARED_BLOCK_REF_KEY);
+	struct btrfs_key prev_key;
+	bool prev_is_tree_block;
+
+	if (slot == 0)
+		return 0;
+	btrfs_item_key_to_cpu(leaf, &prev_key, slot - 1);
+	if (prev_key.objectid != key->objectid)
+		return 0;
+
+	switch (prev_key.type) {
+	case BTRFS_EXTENT_ITEM_KEY:
+	case BTRFS_METADATA_ITEM_KEY: {
+		struct btrfs_extent_item *ei;
+
+		ei = btrfs_item_ptr(leaf, slot - 1, struct btrfs_extent_item);
+		prev_is_tree_block = (btrfs_extent_flags(leaf, ei) &
+				      BTRFS_EXTENT_FLAG_TREE_BLOCK);
+		break;
+	}
+	case BTRFS_TREE_BLOCK_REF_KEY:
+	case BTRFS_SHARED_BLOCK_REF_KEY:
+		prev_is_tree_block = true;
+		break;
+	case BTRFS_EXTENT_DATA_REF_KEY:
+	case BTRFS_SHARED_DATA_REF_KEY:
+		prev_is_tree_block = false;
+		break;
+	default:
+		return 0;
+	}
+	if (unlikely(prev_is_tree_block != is_tree_block_ref)) {
+		extent_err(leaf, slot,
+			   "keyed ref type %u does not match the extent type",
+			   key->type);
+		return -EUCLEAN;
+	}
+	return 0;
+}
+
 static int check_simple_keyed_refs(struct extent_buffer *leaf,
 				   struct btrfs_key *key, int slot)
 {
@@ -1892,7 +1936,7 @@ static int check_simple_keyed_refs(struct extent_buffer *leaf,
 			   key->offset, leaf->fs_info->sectorsize);
 		return -EUCLEAN;
 	}
-	return 0;
+	return check_keyed_ref_extent_type(leaf, key, slot);
 }
 
 static int check_extent_data_ref(struct extent_buffer *leaf,
@@ -1953,7 +1997,7 @@ static int check_extent_data_ref(struct extent_buffer *leaf,
 			return -EUCLEAN;
 		}
 	}
-	return 0;
+	return check_keyed_ref_extent_type(leaf, key, slot);
 }
 
 #define inode_ref_err(eb, slot, fmt, args...)			\
-- 
2.54.0


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

* [PATCH 4/5] btrfs-progs: check: count non-skinny tree blocks in simple quotas
  2026-11-04 10:23 [PATCH 0/5] btrfs: owner refs and tree blocks Siddharth Mishra
                   ` (2 preceding siblings ...)
  2026-11-04 10:23 ` [PATCH 3/5] btrfs: tree-checker: reject keyed " Siddharth Mishra
@ 2026-11-04 10:23 ` Siddharth Mishra
  2026-11-04 19:21   ` Boris Burkov
  2026-11-04 10:23 ` [PATCH 5/5] btrfs-progs: tune: do not remove owner refs from tree blocks Siddharth Mishra
  2026-11-04 22:50 ` [PATCH 0/5] btrfs: owner refs and " Qu Wenruo
  5 siblings, 1 reply; 16+ messages in thread
From: Siddharth Mishra @ 2026-11-04 10:23 UTC (permalink / raw)
  To: dsterba
  Cc: wqu, boris, josef, fdmanana, mason, linux-btrfs, linux-kernel,
	Siddharth Mishra

[BUG]
On a filesystem without skinny-metadata, mkfs with simple quotas writes
a qgroup that does not count the tree block of the fs tree:

  $ mkfs.btrfs -O squota,^skinny-metadata $dev
  $ btrfs inspect-internal dump-tree -t quota $dev
	item 1 key (0 QGROUP_INFO 0/5) itemoff 16203 itemsize 40
		generation 11
		referenced 0 referenced_compressed 0
		^^^^^^^^^^^^
		exclusive 0 exclusive_compressed 0

The kernel then warns about a squota underflow when the block is freed,
and "btrfs check" reports no error, because it uses the same code.

[CAUSE]
simple_quota_account_extent() treats an extent as a tree block only if
its key is METADATA_ITEM. Without skinny-metadata a tree block is an
EXTENT_ITEM with the TREE_BLOCK flag, so it is handled like data: its
first inline ref is a TREE_BLOCK_REF, not an owner ref, and the block is
not counted. mkfs fills the qgroups with the same code.

[FIX]
Also treat an extent item with the TREE_BLOCK flag as a tree block, and
take its owner from the tree block header.

Fixes: 5bd97022f384 ("btrfs-progs: check: add support for squota")
Signed-off-by: Siddharth Mishra <smish149@asu.edu>
---
 check/qgroup-verify.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/check/qgroup-verify.c b/check/qgroup-verify.c
index b58ef366..d6681653 100644
--- a/check/qgroup-verify.c
+++ b/check/qgroup-verify.c
@@ -1046,8 +1046,9 @@ static int simple_quota_account_extent(struct btrfs_fs_info *info,
 	if (generation < counts.enable_gen)
 		return 0;
 
-	type = btrfs_extent_inline_ref_type(leaf, iref);
-	if (!meta_item) {
+	if (!meta_item &&
+	    !(btrfs_extent_flags(leaf, ei) & BTRFS_EXTENT_FLAG_TREE_BLOCK)) {
+		type = btrfs_extent_inline_ref_type(leaf, iref);
 		if (type == BTRFS_EXTENT_OWNER_REF_KEY) {
 			struct btrfs_extent_owner_ref *oref;
 
-- 
2.54.0


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

* [PATCH 5/5] btrfs-progs: tune: do not remove owner refs from tree blocks
  2026-11-04 10:23 [PATCH 0/5] btrfs: owner refs and tree blocks Siddharth Mishra
                   ` (3 preceding siblings ...)
  2026-11-04 10:23 ` [PATCH 4/5] btrfs-progs: check: count non-skinny tree blocks in simple quotas Siddharth Mishra
@ 2026-11-04 10:23 ` Siddharth Mishra
  2026-11-04 19:18   ` Boris Burkov
  2026-11-04 22:50 ` [PATCH 0/5] btrfs: owner refs and " Qu Wenruo
  5 siblings, 1 reply; 16+ messages in thread
From: Siddharth Mishra @ 2026-11-04 10:23 UTC (permalink / raw)
  To: dsterba
  Cc: wqu, boris, josef, fdmanana, mason, linux-btrfs, linux-kernel,
	Siddharth Mishra

[BUG]
On a filesystem without skinny-metadata, "btrfstune --remove-simple-quota"
can corrupt the extent items of tree blocks:

  before:
	item 31 key (30756864 EXTENT_ITEM 4096) itemoff 2363 itemsize 51
		refs 1 gen 6 flags TREE_BLOCK
		tree block key (940 INODE_ITEM 0) level 0
		(176 0x5) tree block backref root FS_TREE
  after:
	item 31 key (30756864 EXTENT_ITEM 4096) itemoff 2372 itemsize 42
		refs 1 gen 6 flags TREE_BLOCK
		tree block key (0 UNKNOWN.0 1456) level 0
		^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

and "btrfs check" then reports "backpointer mismatch" for each of them.

[CAUSE]
remove_owner_refs() takes the first inline ref right after the
btrfs_extent_item of every EXTENT_ITEM. For a tree block without
skinny-metadata that is the btrfs_tree_block_info. If the lowest byte of
its first key objectid is BTRFS_EXTENT_OWNER_REF_KEY (172, here inode
940 = 0x3ac), the tree block info is taken as an owner ref and 9 bytes of
it are removed.

The check for an extent item without inline refs is also off by one: it
reads the type byte when ptr == item_end.

[FIX]
Owner refs are only added to data extents, so skip extent items with the
TREE_BLOCK flag, and skip the item when ptr >= item_end.

Fixes: 2336a7b921ff ("btrfs-progs: btrfstune: add ability to remove squotas")
Signed-off-by: Siddharth Mishra <smish149@asu.edu>
---
 tune/quota.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/tune/quota.c b/tune/quota.c
index a5f7fb40..187ba4f6 100644
--- a/tune/quota.c
+++ b/tune/quota.c
@@ -132,10 +132,12 @@ search_slot:
 		if (found_key.type != BTRFS_EXTENT_ITEM_KEY)
 			goto next;
 		ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item);
+		if (btrfs_extent_flags(leaf, ei) & BTRFS_EXTENT_FLAG_TREE_BLOCK)
+			goto next;
 		ptr = (unsigned long)(ei + 1);
 		item_end = (unsigned long)ei + btrfs_item_size(leaf, slot);
 		/* No inline extent references; accessing type is invalid. */
-		if (ptr > item_end)
+		if (ptr >= item_end)
 			goto next;
 		iref = (struct btrfs_extent_inline_ref *)ptr;
 		type = btrfs_extent_inline_ref_type(leaf, iref);
-- 
2.54.0


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

* Re: [PATCH 1/5] btrfs: skip tree blocks in btrfs_get_extent_owner_root()
  2026-11-04 10:23 ` [PATCH 1/5] btrfs: skip tree blocks in btrfs_get_extent_owner_root() Siddharth Mishra
@ 2026-11-04 19:13   ` Boris Burkov
  0 siblings, 0 replies; 16+ messages in thread
From: Boris Burkov @ 2026-11-04 19:13 UTC (permalink / raw)
  To: Siddharth Mishra
  Cc: dsterba, wqu, josef, fdmanana, mason, linux-btrfs, linux-kernel, stable

On Wed, Nov 04, 2026 at 03:23:19AM -0700, Siddharth Mishra wrote:
> [BUG]
> With simple quotas enabled on a filesystem without the skinny-metadata
> feature, deleting a subvolume triggers the following warning:
> 
>  ------------[ cut here ]------------
>  WARNING: fs/btrfs/extent-tree.c:393 at
>    btrfs_get_extent_inline_ref_type+0x1b9/0x2d0, CPU#1: btrfs-cleaner/84
>  RIP: 0010:btrfs_get_extent_inline_ref_type (fs/btrfs/extent-tree.c:393)
>  Call Trace:
>   <TASK>
>   btrfs_get_extent_owner_root (fs/btrfs/extent-tree.c:3128)
>   btrfs_lookup_extent_info (fs/btrfs/extent-tree.c:187)
>   walk_down_proc (fs/btrfs/extent-tree.c:5647)
>   walk_down_tree (fs/btrfs/extent-tree.c:6170)
>   btrfs_drop_snapshot (fs/btrfs/extent-tree.c:6392)
>   btrfs_clean_one_deleted_snapshot (fs/btrfs/transaction.c:2728)
>   cleaner_kthread (fs/btrfs/disk-io.c:1553)
>   kthread (kernel/kthread.c:436)
>   ret_from_fork (arch/x86/kernel/process.c:158)
>   ret_from_fork_asm (arch/x86/entry/entry_64.S:245)
>   </TASK>
>  ---[ end trace 0000000000000000 ]---
>  BTRFS error (device vda): eb 30720000 iref 0x3ee6 invalid extent
>    inline ref type 0
> 
> Writing to a snapshot (update_ref_for_cow()) or a metadata balance
> (relocate_block_group()) trigger the same warning.
> 
> [CAUSE]
> Without skinny-metadata, the extent item of a tree block is an
> EXTENT_ITEM with the TREE_BLOCK flag, and a btrfs_tree_block_info sits
> between the btrfs_extent_item and the first inline ref:
> 
> 	item 3 key (30441472 EXTENT_ITEM 16384) itemoff 16079 itemsize 51
> 		refs 1 gen 5 flags TREE_BLOCK
> 		tree block key (256 INODE_ITEM 0) level 0
> 		^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 		(176 0x5) tree block backref root FS_TREE
> 
> But btrfs_get_extent_owner_root() always takes the first inline ref
> right after the btrfs_extent_item:
> 
> 	ptr = (unsigned long)(ei + 1);
> 
> So for such a tree block it reads the lowest byte of the first key
> objectid as the inline ref type. For the key above that byte is 0, and
> btrfs_get_extent_inline_ref_type() warns. If the byte happens to be
> BTRFS_EXTENT_OWNER_REF_KEY (172), the key bytes are returned as the
> owner root.
> 
> The filesystem is valid. It can be reproduced with:
> 
>   mkfs.btrfs -f -O ^skinny-metadata $dev
>   mount $dev $mnt
>   btrfs quota enable --simple $mnt
>   btrfs subvolume create $mnt/sv
>   btrfs subvolume delete $mnt/sv
>   btrfs subvolume sync $mnt
> 
> [FIX]
> Owner refs are only added to data extents, never to tree blocks, so
> return 0 for an extent item with the TREE_BLOCK flag before looking at
> its inline refs. This covers both the EXTENT_ITEM and the METADATA_ITEM
> layouts, and gives the same result as on a skinny-metadata filesystem,
> where the first inline ref of a tree block is never an owner ref.
> 

Nice catch, LGTM.
Reviewed-by: Boris Burkov <boris@bur.io>

> Fixes: 8d2990914073 ("btrfs: add helper for inline owner ref lookup")
> Cc: stable@vger.kernel.org # 6.7+
> Signed-off-by: Siddharth Mishra <smish149@asu.edu>
> ---
>  fs/btrfs/extent-tree.c | 3 +++
>  1 file changed, 3 insertions(+)
> 
> diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c
> index 9b0a5c6b9..3ab4f2696 100644
> --- a/fs/btrfs/extent-tree.c
> +++ b/fs/btrfs/extent-tree.c
> @@ -3117,6 +3117,9 @@ u64 btrfs_get_extent_owner_root(struct btrfs_fs_info *fs_info,
>  		return 0;
>  
>  	ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item);
> +	if (btrfs_extent_flags(leaf, ei) & BTRFS_EXTENT_FLAG_TREE_BLOCK)
> +		return 0;
> +
>  	ptr = (unsigned long)(ei + 1);
>  	end = (unsigned long)ei + btrfs_item_size(leaf, slot);
>  
> -- 
> 2.54.0
> 

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

* Re: [PATCH 5/5] btrfs-progs: tune: do not remove owner refs from tree blocks
  2026-11-04 10:23 ` [PATCH 5/5] btrfs-progs: tune: do not remove owner refs from tree blocks Siddharth Mishra
@ 2026-11-04 19:18   ` Boris Burkov
  0 siblings, 0 replies; 16+ messages in thread
From: Boris Burkov @ 2026-11-04 19:18 UTC (permalink / raw)
  To: Siddharth Mishra
  Cc: dsterba, wqu, josef, fdmanana, mason, linux-btrfs, linux-kernel

On Wed, Nov 04, 2026 at 03:23:23AM -0700, Siddharth Mishra wrote:
> [BUG]
> On a filesystem without skinny-metadata, "btrfstune --remove-simple-quota"
> can corrupt the extent items of tree blocks:
> 
>   before:
> 	item 31 key (30756864 EXTENT_ITEM 4096) itemoff 2363 itemsize 51
> 		refs 1 gen 6 flags TREE_BLOCK
> 		tree block key (940 INODE_ITEM 0) level 0
> 		(176 0x5) tree block backref root FS_TREE
>   after:
> 	item 31 key (30756864 EXTENT_ITEM 4096) itemoff 2372 itemsize 42
> 		refs 1 gen 6 flags TREE_BLOCK
> 		tree block key (0 UNKNOWN.0 1456) level 0
> 		^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 
> and "btrfs check" then reports "backpointer mismatch" for each of them.
> 
> [CAUSE]
> remove_owner_refs() takes the first inline ref right after the
> btrfs_extent_item of every EXTENT_ITEM. For a tree block without
> skinny-metadata that is the btrfs_tree_block_info. If the lowest byte of
> its first key objectid is BTRFS_EXTENT_OWNER_REF_KEY (172, here inode
> 940 = 0x3ac), the tree block info is taken as an owner ref and 9 bytes of
> it are removed.
> 
> The check for an extent item without inline refs is also off by one: it
> reads the type byte when ptr == item_end.
> 
> [FIX]
> Owner refs are only added to data extents, so skip extent items with the
> TREE_BLOCK flag, and skip the item when ptr >= item_end.
> 
Reviewed-by: Boris Burkov <boris@bur.io>
> Fixes: 2336a7b921ff ("btrfs-progs: btrfstune: add ability to remove squotas")
> Signed-off-by: Siddharth Mishra <smish149@asu.edu>
> ---
>  tune/quota.c | 4 +++-
>  1 file changed, 3 insertions(+), 1 deletion(-)
> 
> diff --git a/tune/quota.c b/tune/quota.c
> index a5f7fb40..187ba4f6 100644
> --- a/tune/quota.c
> +++ b/tune/quota.c
> @@ -132,10 +132,12 @@ search_slot:
>  		if (found_key.type != BTRFS_EXTENT_ITEM_KEY)
>  			goto next;
>  		ei = btrfs_item_ptr(leaf, slot, struct btrfs_extent_item);
> +		if (btrfs_extent_flags(leaf, ei) & BTRFS_EXTENT_FLAG_TREE_BLOCK)
> +			goto next;
>  		ptr = (unsigned long)(ei + 1);
>  		item_end = (unsigned long)ei + btrfs_item_size(leaf, slot);
>  		/* No inline extent references; accessing type is invalid. */
> -		if (ptr > item_end)
> +		if (ptr >= item_end)
>  			goto next;
>  		iref = (struct btrfs_extent_inline_ref *)ptr;
>  		type = btrfs_extent_inline_ref_type(leaf, iref);
> -- 
> 2.54.0
> 

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

* Re: [PATCH 4/5] btrfs-progs: check: count non-skinny tree blocks in simple quotas
  2026-11-04 10:23 ` [PATCH 4/5] btrfs-progs: check: count non-skinny tree blocks in simple quotas Siddharth Mishra
@ 2026-11-04 19:21   ` Boris Burkov
  2026-11-04 21:30     ` [PATCH v2] " Siddharth Mishra
  0 siblings, 1 reply; 16+ messages in thread
From: Boris Burkov @ 2026-11-04 19:21 UTC (permalink / raw)
  To: Siddharth Mishra
  Cc: dsterba, wqu, josef, fdmanana, mason, linux-btrfs, linux-kernel

On Wed, Nov 04, 2026 at 03:23:22AM -0700, Siddharth Mishra wrote:
> [BUG]
> On a filesystem without skinny-metadata, mkfs with simple quotas writes
> a qgroup that does not count the tree block of the fs tree:
> 
>   $ mkfs.btrfs -O squota,^skinny-metadata $dev
>   $ btrfs inspect-internal dump-tree -t quota $dev
> 	item 1 key (0 QGROUP_INFO 0/5) itemoff 16203 itemsize 40
> 		generation 11
> 		referenced 0 referenced_compressed 0
> 		^^^^^^^^^^^^
> 		exclusive 0 exclusive_compressed 0
> 
> The kernel then warns about a squota underflow when the block is freed,
> and "btrfs check" reports no error, because it uses the same code.
> 
> [CAUSE]
> simple_quota_account_extent() treats an extent as a tree block only if
> its key is METADATA_ITEM. Without skinny-metadata a tree block is an
> EXTENT_ITEM with the TREE_BLOCK flag, so it is handled like data: its
> first inline ref is a TREE_BLOCK_REF, not an owner ref, and the block is
> not counted. mkfs fills the qgroups with the same code.
> 
> [FIX]
> Also treat an extent item with the TREE_BLOCK flag as a tree block, and
> take its owner from the tree block header.
> 
> Fixes: 5bd97022f384 ("btrfs-progs: check: add support for squota")
> Signed-off-by: Siddharth Mishra <smish149@asu.edu>
> ---
>  check/qgroup-verify.c | 5 +++--
>  1 file changed, 3 insertions(+), 2 deletions(-)
> 
> diff --git a/check/qgroup-verify.c b/check/qgroup-verify.c
> index b58ef366..d6681653 100644
> --- a/check/qgroup-verify.c
> +++ b/check/qgroup-verify.c
> @@ -1046,8 +1046,9 @@ static int simple_quota_account_extent(struct btrfs_fs_info *info,
>  	if (generation < counts.enable_gen)
>  		return 0;
>  
> -	type = btrfs_extent_inline_ref_type(leaf, iref);
> -	if (!meta_item) {
> +	if (!meta_item &&
> +	    !(btrfs_extent_flags(leaf, ei) & BTRFS_EXTENT_FLAG_TREE_BLOCK)) {

I sort of think lifting this higher to make simple_quota_account_extent
check for "is metadata" vs "is metadata item" makes a bit more sense.

Either way,
Reviewed-by: Boris Burkov <boris@bur.io>

> +		type = btrfs_extent_inline_ref_type(leaf, iref);
>  		if (type == BTRFS_EXTENT_OWNER_REF_KEY) {
>  			struct btrfs_extent_owner_ref *oref;
>  
> -- 
> 2.54.0
> 

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

* Re: [PATCH 2/5] btrfs: tree-checker: reject inline refs not matching the extent type
  2026-11-04 10:23 ` [PATCH 2/5] btrfs: tree-checker: reject inline refs not matching the extent type Siddharth Mishra
@ 2026-11-04 19:24   ` Boris Burkov
  2026-11-04 19:56     ` Siddharth Mishra
  0 siblings, 1 reply; 16+ messages in thread
From: Boris Burkov @ 2026-11-04 19:24 UTC (permalink / raw)
  To: Siddharth Mishra
  Cc: dsterba, wqu, josef, fdmanana, mason, linux-btrfs, linux-kernel

On Wed, Nov 04, 2026 at 03:23:20AM -0700, Siddharth Mishra wrote:
> [BACKGROUND]
> An extent item is either for a tree block or for data, and each kind
> only gets its own inline ref types:
> 
>   TREE_BLOCK: TREE_BLOCK_REF, SHARED_BLOCK_REF
>   DATA:       EXTENT_OWNER_REF, EXTENT_DATA_REF, SHARED_DATA_REF
> 
> Readers rely on this.  btrfs_get_extent_owner_root() only looks for an
> owner ref on data extents, and btrfs_get_extent_inline_ref_type() warns
> when a caller that expects a tree block ref or a data ref gets the
> other kind.
> 
> [CAUSE]
> check_extent_item() verifies each inline ref on its own, but never
> checks that its type matches the TREE_BLOCK or DATA flag of the extent
> item.  So a data ref or an owner ref on a tree block, or a tree block
> ref on a data extent, passes the tree-checker.  For example this tree
> block has an owner ref instead of its tree block ref:
> 
> 	item 9 key (30478336 METADATA_ITEM 0) itemoff 3636 itemsize 33
> 		refs 1 gen 13 flags TREE_BLOCK
> 		tree block skinny level 0
> 		(172 0x5) extent owner root 5
> 		^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 

I assume this was crafted manually, not via btrfs operations with
squotas enabled, right?

> The leaf is accepted.  When the tree block is freed later,
> lookup_inline_extent_backref() skips the owner ref, finds no tree block
> ref, and the transaction aborts:
> 
>   WARNING: fs/btrfs/extent-tree.c:3418 at __btrfs_free_extent
>   BTRFS error (device vda state A): Transaction 14 aborted (-EUCLEAN)
>   BTRFS info (device vda state EA): forced readonly

This part is not that interesting, kinda garbage in garbage out

> 
> [ENHANCEMENT]
> Reject an inline ref whose type does not match the extent flags with
> -EUCLEAN, so the leaf is refused when it is read:
> 
>   BTRFS critical (device vda): corrupt leaf: block=30441472 slot=9
>   extent bytenr=30478336 len=4096 inline ref type 172 does not match
>   extent flags 0x2

but of course nice to catch more corrupt leaves

> 
> Signed-off-by: Siddharth Mishra <smish149@asu.edu>
> ---
>  fs/btrfs/tree-checker.c | 8 ++++++++
>  1 file changed, 8 insertions(+)
> 
> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
> index f021ac14b..db446c8af 100644
> --- a/fs/btrfs/tree-checker.c
> +++ b/fs/btrfs/tree-checker.c
> @@ -1790,6 +1790,14 @@ static int check_extent_item(struct extent_buffer *leaf,
>  				   inline_type);
>  			return -EUCLEAN;
>  		}

Logic itself is maybe too clever but at least quite compact. LGTM
Reviewed-by: Boris Burkov <boris@bur.io>

> +		if (unlikely(is_tree_block !=
> +			     (inline_type == BTRFS_TREE_BLOCK_REF_KEY ||
> +			      inline_type == BTRFS_SHARED_BLOCK_REF_KEY))) {
> +			extent_err(leaf, slot,
> +			"inline ref type %u does not match extent flags 0x%llx",
> +				   inline_type, flags);
> +			return -EUCLEAN;
> +		}
>  		if (unlikely(inline_type < last_type)) {
>  			extent_err(leaf, slot,
>  				   "inline ref out-of-order: has type %u, prev type %u",
> -- 
> 2.54.0
> 

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

* Re: [PATCH 3/5] btrfs: tree-checker: reject keyed refs not matching the extent type
  2026-11-04 10:23 ` [PATCH 3/5] btrfs: tree-checker: reject keyed " Siddharth Mishra
@ 2026-11-04 19:29   ` Boris Burkov
  0 siblings, 0 replies; 16+ messages in thread
From: Boris Burkov @ 2026-11-04 19:29 UTC (permalink / raw)
  To: Siddharth Mishra
  Cc: dsterba, wqu, josef, fdmanana, mason, linux-btrfs, linux-kernel

On Wed, Nov 04, 2026 at 03:23:21AM -0700, Siddharth Mishra wrote:
> [BACKGROUND]
> When the refs of an extent do not fit into its extent item, the others
> are stored as keyed ref items with the same objectid, right after the
> extent item:
> 
>   TREE_BLOCK:  TREE_BLOCK_REF and SHARED_BLOCK_REF items
>   DATA:        EXTENT_DATA_REF and SHARED_DATA_REF items
> 
> [CAUSE]
> check_simple_keyed_refs() and check_extent_data_ref() only verify the
> keyed ref itself.  Nothing checks that its type matches the extent it
> belongs to, so a tree block ref for a data extent, or a data ref for a
> tree block, passes the tree-checker.  For example this data extent is
> followed by a keyed tree block ref:
> 
> 	item 0 key (13631488 EXTENT_ITEM 32768) itemoff 3797 itemsize 198
> 		refs 41 gen 9 flags DATA
> 		(178 0xdea30debbf5f519) extent data backref root 256
> 		objectid 258 offset 0 count 1
> 		...
> 	item 1 key (13631488 TREE_BLOCK_REF 5) itemoff 3797 itemsize 0
> 	           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 		tree block backref
> 
> The leaf is accepted.  When the extent is freed later, the refs do not
> add up and the transaction aborts:
> 
>   WARNING: fs/btrfs/extent-tree.c:3418 at __btrfs_free_extent
>   BTRFS error (device vda state A): Transaction 35 aborted (-EUCLEAN)
>   BTRFS info (device vda state EA): forced readonly
> 
> [ENHANCEMENT]
> Check each keyed ref against the item right before it.  Keyed refs sort
> right after their extent item, so that item is either the extent item
> or another keyed ref of the same extent, and both must be of the same
> kind.  Reject a mismatch with -EUCLEAN, so the leaf is refused when it
> is read:
> 
>   BTRFS critical (device vda): corrupt leaf: block=31678464 slot=1
>   extent bytenr=13631488 len=4096 keyed ref type 176 does not match the
>   extent type
> 
> This is the keyed counterpart of the inline ref check.
> 
> If the item before a keyed ref belongs to another extent, its extent
> item is in another leaf and the keyed ref cannot be checked by the
> tree-checker.
> 

This one seems the most "juice not worth the squeeze" out of these
patches, but I think the analogy to inline refs is good enough for me to
justify it.

Reviewed-by: Boris Burkov <boris@bur.io>

> Signed-off-by: Siddharth Mishra <smish149@asu.edu>
> ---
>  fs/btrfs/tree-checker.c | 48 +++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 46 insertions(+), 2 deletions(-)
> 
> diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
> index db446c8af..d966243c9 100644
> --- a/fs/btrfs/tree-checker.c
> +++ b/fs/btrfs/tree-checker.c
> @@ -1854,6 +1854,50 @@ static int check_extent_item(struct extent_buffer *leaf,
>  	return 0;
>  }
>  
> +static int check_keyed_ref_extent_type(const struct extent_buffer *leaf,
> +				       const struct btrfs_key *key, int slot)
> +{
> +	const bool is_tree_block_ref = (key->type == BTRFS_TREE_BLOCK_REF_KEY ||
> +					key->type == BTRFS_SHARED_BLOCK_REF_KEY);
> +	struct btrfs_key prev_key;
> +	bool prev_is_tree_block;
> +
> +	if (slot == 0)
> +		return 0;
> +	btrfs_item_key_to_cpu(leaf, &prev_key, slot - 1);
> +	if (prev_key.objectid != key->objectid)
> +		return 0;
> +
> +	switch (prev_key.type) {
> +	case BTRFS_EXTENT_ITEM_KEY:
> +	case BTRFS_METADATA_ITEM_KEY: {
> +		struct btrfs_extent_item *ei;
> +
> +		ei = btrfs_item_ptr(leaf, slot - 1, struct btrfs_extent_item);
> +		prev_is_tree_block = (btrfs_extent_flags(leaf, ei) &
> +				      BTRFS_EXTENT_FLAG_TREE_BLOCK);
> +		break;
> +	}
> +	case BTRFS_TREE_BLOCK_REF_KEY:
> +	case BTRFS_SHARED_BLOCK_REF_KEY:
> +		prev_is_tree_block = true;
> +		break;
> +	case BTRFS_EXTENT_DATA_REF_KEY:
> +	case BTRFS_SHARED_DATA_REF_KEY:
> +		prev_is_tree_block = false;
> +		break;
> +	default:
> +		return 0;
> +	}
> +	if (unlikely(prev_is_tree_block != is_tree_block_ref)) {
> +		extent_err(leaf, slot,
> +			   "keyed ref type %u does not match the extent type",
> +			   key->type);
> +		return -EUCLEAN;
> +	}
> +	return 0;
> +}
> +
>  static int check_simple_keyed_refs(struct extent_buffer *leaf,
>  				   struct btrfs_key *key, int slot)
>  {
> @@ -1892,7 +1936,7 @@ static int check_simple_keyed_refs(struct extent_buffer *leaf,
>  			   key->offset, leaf->fs_info->sectorsize);
>  		return -EUCLEAN;
>  	}
> -	return 0;
> +	return check_keyed_ref_extent_type(leaf, key, slot);
>  }
>  
>  static int check_extent_data_ref(struct extent_buffer *leaf,
> @@ -1953,7 +1997,7 @@ static int check_extent_data_ref(struct extent_buffer *leaf,
>  			return -EUCLEAN;
>  		}
>  	}
> -	return 0;
> +	return check_keyed_ref_extent_type(leaf, key, slot);
>  }
>  
>  #define inode_ref_err(eb, slot, fmt, args...)			\
> -- 
> 2.54.0
> 

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

* Re: [PATCH 2/5] btrfs: tree-checker: reject inline refs not matching the extent type
  2026-11-04 19:24   ` Boris Burkov
@ 2026-11-04 19:56     ` Siddharth Mishra
  0 siblings, 0 replies; 16+ messages in thread
From: Siddharth Mishra @ 2026-11-04 19:56 UTC (permalink / raw)
  To: Boris Burkov
  Cc: dsterba, wqu, josef, fdmanana, mason, linux-btrfs, linux-kernel

On Wed, Nov 04, 2026 at 11:24:15AM -0800, Boris Burkov wrote:
> > 	item 9 key (30478336 METADATA_ITEM 0) itemoff 3636 itemsize 33
> > 		refs 1 gen 13 flags TREE_BLOCK
> > 		tree block skinny level 0
> > 		(172 0x5) extent owner root 5
> > 		^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> >
>
> I assume this was crafted manually, not via btrfs operations with
> squotas enabled, right?

Right, it is a corrupted image found by fuzzing. I did not find a way
to get an owner ref on a tree block with normal operations, the kernel
only adds owner refs to data extents.

Thanks for the review.

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

* [PATCH v2] btrfs-progs: check: count non-skinny tree blocks in simple quotas
  2026-11-04 19:21   ` Boris Burkov
@ 2026-11-04 21:30     ` Siddharth Mishra
  0 siblings, 0 replies; 16+ messages in thread
From: Siddharth Mishra @ 2026-11-04 21:30 UTC (permalink / raw)
  To: David Sterba
  Cc: Boris Burkov, wqu, josef, fdmanana, mason, linux-btrfs, linux-kernel

[BUG]
On a filesystem without skinny-metadata, mkfs with simple quotas writes
a qgroup that does not count the tree block of the fs tree:

  $ mkfs.btrfs -O squota,^skinny-metadata $dev
  $ btrfs inspect-internal dump-tree -t quota $dev
	item 1 key (0 QGROUP_INFO 0/5) itemoff 16203 itemsize 40
		generation 11
		referenced 0 referenced_compressed 0
		^^^^^^^^^^^^
		exclusive 0 exclusive_compressed 0

The kernel then warns about a squota underflow when the block is freed,
and "btrfs check" reports no error, because it uses the same code.

[CAUSE]
simple_quota_account_extent() treats an extent as a tree block only if
its key is METADATA_ITEM. Without skinny-metadata a tree block is an
EXTENT_ITEM with the TREE_BLOCK flag, so it is handled like data: its
first inline ref is a TREE_BLOCK_REF, not an owner ref, and the block is
not counted. mkfs fills the qgroups with the same code.

[FIX]
Pass whether the extent is a tree block, from its TREE_BLOCK flag,
instead of whether its key is METADATA_ITEM, so a tree block without
skinny-metadata also takes its owner from the tree block header.

Fixes: 5bd97022f384 ("btrfs-progs: check: add support for squota")
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Siddharth Mishra <smish149@asu.edu>
---
v2:
- Pass the TREE_BLOCK flag from add_inline_refs() instead of checking it
  in simple_quota_account_extent() (Boris)

An AI tool helped with this version.
Tested as v1 (mkfs and check on skinny and non-skinny images, a
kernel-written filesystem, the btrfs-progs test suites), with no change
in results.

 check/qgroup-verify.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/check/qgroup-verify.c b/check/qgroup-verify.c
index b58ef366..30f258a2 100644
--- a/check/qgroup-verify.c
+++ b/check/qgroup-verify.c
@@ -1033,7 +1033,7 @@ static int simple_quota_account_extent(struct btrfs_fs_info *info,
 				       struct btrfs_key *key,
 				       struct btrfs_extent_item *ei,
 				       struct btrfs_extent_inline_ref *iref,
-				       u64 bytenr, u64 num_bytes, int meta_item)
+				       u64 bytenr, u64 num_bytes, bool is_metadata)
 {
 	u64 generation;
 	int type;
@@ -1046,8 +1046,8 @@ static int simple_quota_account_extent(struct btrfs_fs_info *info,
 	if (generation < counts.enable_gen)
 		return 0;
 
-	type = btrfs_extent_inline_ref_type(leaf, iref);
-	if (!meta_item) {
+	if (!is_metadata) {
+		type = btrfs_extent_inline_ref_type(leaf, iref);
 		if (type == BTRFS_EXTENT_OWNER_REF_KEY) {
 			struct btrfs_extent_owner_ref *oref;
 
@@ -1105,7 +1105,8 @@ static int add_inline_refs(struct btrfs_fs_info *info,
 
 	if (counts.simple) {
 		int ret = simple_quota_account_extent(info, ei_leaf, &key, ei, iref,
-						      bytenr, num_bytes, meta_item);
+				bytenr, num_bytes,
+				flags & BTRFS_EXTENT_FLAG_TREE_BLOCK);
 
 		if (ret)
 			error("squota account extent error: %d", ret);

base-commit: 3737c2603af86f742986c51cf0e5c4898a126d6f
-- 
2.54.0


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

* Re: [PATCH 0/5] btrfs: owner refs and tree blocks
  2026-11-04 10:23 [PATCH 0/5] btrfs: owner refs and tree blocks Siddharth Mishra
                   ` (4 preceding siblings ...)
  2026-11-04 10:23 ` [PATCH 5/5] btrfs-progs: tune: do not remove owner refs from tree blocks Siddharth Mishra
@ 2026-11-04 22:50 ` Qu Wenruo
  2026-11-04 23:16   ` Siddharth Mishra
  5 siblings, 1 reply; 16+ messages in thread
From: Qu Wenruo @ 2026-11-04 22:50 UTC (permalink / raw)
  To: Siddharth Mishra, dsterba
  Cc: wqu, boris, josef, fdmanana, mason, linux-btrfs, linux-kernel



在 2026/11/4 20:53, Siddharth Mishra 写道:
> Simple quotas store the owner of a data extent in an inline owner ref.
> Tree blocks never get one: their owner is in the tree block header.
> 
> Patch 1 fixes btrfs_get_extent_owner_root(), which also looks for an
> owner ref on tree blocks. On a filesystem without skinny-metadata it
> reads the btrfs_tree_block_info as an inline ref and warns on subvolume
> deletion, snapshot writes and balance. The filesystem is valid.
> 
> Patches 2 and 3 are hardening. The tree-checker rejects refs whose type
> does not match the extent type: inline refs in patch 2 (for example an
> owner ref on a tree block), and keyed refs in the same leaf as their
> extent item in patch 3.
> 
> Patches 4 and 5 are for btrfs-progs (devel 3737c260) and have the same
> cause: a tree block without skinny-metadata is taken for data. mkfs and
> btrfs check do not count such tree blocks in simple quotas, and
> btrfstune --remove-simple-quota can corrupt their extent items.
> 
> Found by fuzzing. An AI tool helped with the analysis and the patches.
> 
> Tested on btrfs for-next df5b88a7914e, KASAN and non-debug kernels:
> - Patch 1: no warning on subvolume deletion, snapshot writes and
>    metadata balance without skinny-metadata; same qgroup numbers.
> - Patches 2 and 3: crafted images with an owner ref on a tree block,
>    and with a keyed tree block ref on a data extent, are refused at
>    read time instead of aborting a transaction later. No valid
>    filesystem was rejected: skinny and non-skinny, 4K nodes, mixed block
>    groups, no quota, qgroups and simple quotas, with reflinks, snapshots,
>    balance and scrub, and filesystems from btrfs-convert, mkfs --rootdir
>    and btrfstune -x.
> - fstests btrfs/quick, and the qgroup, balance, snapshot and subvol
>    groups with -O ^skinny-metadata: no new failure.
> - Patches 4 and 5: mkfs -O squota,^skinny-metadata writes the right
>    qgroup counts; btrfstune --remove-simple-quota leaves the tree blocks
>    intact and btrfs check is clean (before: "backpointer mismatch").
> 
> The crafted images are available on request.
> 
> Siddharth Mishra (5):
>    btrfs: skip tree blocks in btrfs_get_extent_owner_root()
>    btrfs: tree-checker: reject inline refs not matching the extent type
>    btrfs: tree-checker: reject keyed refs not matching the extent type
>    btrfs-progs: check: count non-skinny tree blocks in simple quotas
>    btrfs-progs: tune: do not remove owner refs from tree blocks

Please, never mixed progs and kernel patches in one series.

Send them out separately.

> 
>   fs/btrfs/extent-tree.c  |  3 +++
>   fs/btrfs/tree-checker.c | 56 +++++++++++++++++++++++++++++++++++++++--
>   2 files changed, 57 insertions(+), 2 deletions(-)
> 
>   check/qgroup-verify.c | 5 +++--
>   tune/quota.c          | 4 +++-
>   2 files changed, 6 insertions(+), 3 deletions(-)
> 
> 
> base-commit: df5b88a7914e18488908ddf13e14e31ab39f954c


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

* Re: [PATCH 0/5] btrfs: owner refs and tree blocks
  2026-11-04 22:50 ` [PATCH 0/5] btrfs: owner refs and " Qu Wenruo
@ 2026-11-04 23:16   ` Siddharth Mishra
  2026-11-04 23:17     ` Qu Wenruo
  0 siblings, 1 reply; 16+ messages in thread
From: Siddharth Mishra @ 2026-11-04 23:16 UTC (permalink / raw)
  To: Qu Wenruo
  Cc: dsterba, wqu, boris, josef, fdmanana, mason, linux-btrfs, linux-kernel

On Thu, Nov 05, 2026 at 09:20:51AM +1030, Qu Wenruo wrote:
> > Siddharth Mishra (5):
> >    btrfs: skip tree blocks in btrfs_get_extent_owner_root()
> >    btrfs: tree-checker: reject inline refs not matching the extent type
> >    btrfs: tree-checker: reject keyed refs not matching the extent type
> >    btrfs-progs: check: count non-skinny tree blocks in simple quotas
> >    btrfs-progs: tune: do not remove owner refs from tree blocks
>
> Please, never mixed progs and kernel patches in one series.
>
> Send them out separately.

Noted, I will send kernel and btrfs-progs patches in separate series
from next time.

Should I resend the two btrfs-progs patches as a separate series now?

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

* Re: [PATCH 0/5] btrfs: owner refs and tree blocks
  2026-11-04 23:16   ` Siddharth Mishra
@ 2026-11-04 23:17     ` Qu Wenruo
  0 siblings, 0 replies; 16+ messages in thread
From: Qu Wenruo @ 2026-11-04 23:17 UTC (permalink / raw)
  To: Siddharth Mishra, Qu Wenruo
  Cc: dsterba, boris, josef, fdmanana, mason, linux-btrfs, linux-kernel



在 2026/11/5 09:46, Siddharth Mishra 写道:
> On Thu, Nov 05, 2026 at 09:20:51AM +1030, Qu Wenruo wrote:
>>> Siddharth Mishra (5):
>>>     btrfs: skip tree blocks in btrfs_get_extent_owner_root()
>>>     btrfs: tree-checker: reject inline refs not matching the extent type
>>>     btrfs: tree-checker: reject keyed refs not matching the extent type
>>>     btrfs-progs: check: count non-skinny tree blocks in simple quotas
>>>     btrfs-progs: tune: do not remove owner refs from tree blocks
>>
>> Please, never mixed progs and kernel patches in one series.
>>
>> Send them out separately.
> 
> Noted, I will send kernel and btrfs-progs patches in separate series
> from next time.
> 
> Should I resend the two btrfs-progs patches as a separate series now?

If there are some changes needed.

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

end of thread, other threads:[~2026-11-04 23:17 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-04 10:23 [PATCH 0/5] btrfs: owner refs and tree blocks Siddharth Mishra
2026-11-04 10:23 ` [PATCH 1/5] btrfs: skip tree blocks in btrfs_get_extent_owner_root() Siddharth Mishra
2026-11-04 19:13   ` Boris Burkov
2026-11-04 10:23 ` [PATCH 2/5] btrfs: tree-checker: reject inline refs not matching the extent type Siddharth Mishra
2026-11-04 19:24   ` Boris Burkov
2026-11-04 19:56     ` Siddharth Mishra
2026-11-04 10:23 ` [PATCH 3/5] btrfs: tree-checker: reject keyed " Siddharth Mishra
2026-11-04 19:29   ` Boris Burkov
2026-11-04 10:23 ` [PATCH 4/5] btrfs-progs: check: count non-skinny tree blocks in simple quotas Siddharth Mishra
2026-11-04 19:21   ` Boris Burkov
2026-11-04 21:30     ` [PATCH v2] " Siddharth Mishra
2026-11-04 10:23 ` [PATCH 5/5] btrfs-progs: tune: do not remove owner refs from tree blocks Siddharth Mishra
2026-11-04 19:18   ` Boris Burkov
2026-11-04 22:50 ` [PATCH 0/5] btrfs: owner refs and " Qu Wenruo
2026-11-04 23:16   ` Siddharth Mishra
2026-11-04 23:17     ` Qu Wenruo

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

all inboxes | Powered by JetHome®