public inbox for stable@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] btrfs: reject global extent/csum roots without offset 0 when extent_tree_v2 is off
@ 2026-03-02 11:02 ZhengYuan Huang
  2026-03-02 11:51 ` Sun YangKai
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: ZhengYuan Huang @ 2026-03-02 11:02 UTC (permalink / raw)
  To: linux-btrfs
  Cc: clm, dsterba, josef, linux-kernel, baijiaju1990, r33s3n6,
	zzzccc427, ZhengYuan Huang, stable

Without EXTENT_TREE_V2, btrfs_extent_root() and btrfs_csum_root() always
look up the global roots at offset 0. A crafted image can provide only
non-zero offsets for the extent/csum global roots, so the offset 0 lookup
returns NULL and later leads to a NULL dereference
(e.g. in backup_super_roots()).

Fix this by detecting this at mount time: when loading extent/csum
global roots without EXTENT_TREE_V2, require that an offset 0 root item
exists, otherwise fail the mount with -EUCLEAN.

Tested with a crafted image that has only non-zero offset global roots,
which triggers the KASAN null-ptr-deref in backup_super_roots() before
the fix, and fails the mount with -EUCLEAN after the fix.

Fixes: f7238e509404 ("btrfs: add support for multiple global roots")
Cc: stable@vger.kernel.org # v5.18+
Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
---
A KASAN null-ptr-deref was triggered when mounting a crafted btrfs
image and then doing a simple write/rename workload (e.g. moving any
file into the mountpoint). The crash happens later during a transaction
commit, in the super backup roots path.

Root cause
==========

backup_super_roots() looks up both the extent and csum roots through
the "global roots" rb-tree:

extent_root = btrfs_extent_root(fs_info, 0);
csum_root = btrfs_csum_root(fs_info, 0);

and then unconditionally dereferences extent_root->node and
csum_root->node.

Both btrfs_extent_root() and btrfs_csum_root() build a key with:

.offset = btrfs_global_root_id(fs_info, bytenr)

and btrfs_global_root_id() returns 0 when the filesystem does not have
the EXTENT_TREE_V2 incompat feature. This means that for
non-extent_tree_v2 filesystems, the implementation assumes that there is
always a global root item for:

(BTRFS_EXTENT_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, offset=0)
(BTRFS_CSUM_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, offset=0)

However, an image without an offset = 0 root item can still be
successfully mounted. The mount-time validation logic for the csum tree
resides in load_global_roots_objectid(). Currently,
load_global_roots_objectid() loads whatever root items exist for
a given objectid, but it does not validate that offset = 0
exists when EXTENT_TREE_V2 is not enabled. With a crafted image,
the first (and only) root item for the csum tree (and similarly for
extent) can have a large non-zero offset (e.g. 0x10000000000).
load_global_roots_objectid() inserts that root into the rb-tree, but
there is no entry for offset 0. Later, btrfs_csum_root(..., 0) looks up
offset 0, returns NULL, and backup_super_roots() dereferences it,
causing the crash.

So the bug is an invariant mismatch:

- lookup side (btrfs_{extent,csum}_root) assumes offset=0 exists when
extent_tree_v2 is off

- load side (load_global_roots_objectid) does not enforce that assumption

Fix
===
Teach load_global_roots_objectid() to enforce the "offset 0 must exist"
invariant for the extent and csum trees when EXTENT_TREE_V2 is not enabled.
If at least one root item is found but none has offset 0, fail the mount
with -EUCLEAN and emit an explicit error message. This prevents later
NULL dereferences and also avoids propagating a corrupted/unsupported
global-root layout into runtime.

Note: extent_root is subject to the same assumption and is fixed by the
same check (backup_super_roots dereferences extent_root->node as well).

KASAN report
============
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000003: 0000 [#1] SMP KASAN NOPTI
KASAN: null-ptr-deref in range [0x0000000000000018-0x000000000000001f]
CPU: 0 UID: 0 PID: 34 Comm: kworker/u8:1 Tainted: G           OE       6.18.0 #1 PREEMPT(voluntary)
Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE
Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Workqueue: events_unbound btrfs_async_reclaim_data_space
RIP: 0010:backup_super_roots fs/btrfs/disk-io.c:1680 [inline]
RIP: 0010:write_all_supers+0x2d56/0x4980 fs/btrfs/disk-io.c:4022
Code: 40 38 f2 7f 08 84 d2 0f 85 72 19 00 00 49 8d 7c 24 18 41 88 9e 9a 00 00 00 48 ba 00 00 00 00 00 fc ff df 48 89 fe 48 c1 ee 03 <80> 3c 16 00 0f 85 33 19 00 00 48 ba 00 00 00 00 00 fc ff df 49 8b
RSP: 0018:ffff88800a617758 EFLAGS: 00010216
RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000000
RDX: dffffc0000000000 RSI: 0000000000000003 RDI: 0000000000000018
RBP: ffff88800a617898 R08: 1ffff110023f53a8 R09: 0000000000000000
R10: ffff8880173a8000 R11: 0000000000000001 R12: 0000000000000000
R13: ffff888000000000 R14: ffff88800b676d23 R15: ffffea0000000000
FS:  0000000000000000(0000) GS:ffff8880e4d3e000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00005baa3c26f678 CR3: 000000000e9b8000 CR4: 00000000000006f0
Call Trace:
<TASK>
? __pfx_write_all_supers+0x10/0x10 fs/btrfs/disk-io.c:3980
? btrfs_commit_transaction+0x2818/0x3d90 fs/btrfs/transaction.c:2526
? __lock_release kernel/locking/lockdep.c:5574 [inline]
? lock_release+0x122/0x2a0 kernel/locking/lockdep.c:5889
btrfs_commit_transaction+0x28cc/0x3d90 fs/btrfs/transaction.c:2541
? __pfx_btrfs_commit_transaction+0x10/0x10 fs/btrfs/transaction.c:1919
? start_transaction+0x244/0x1740 fs/btrfs/transaction.c:780
btrfs_commit_current_transaction+0x55/0xf0 fs/btrfs/transaction.c:2010
flush_space+0x7d1/0xc40 fs/btrfs/space-info.c:888
? mark_usage kernel/locking/lockdep.c:4674 [inline]
? __lock_acquire+0x43e/0x21e0 kernel/locking/lockdep.c:5191
? __pfx_flush_space+0x10/0x10 include/trace/events/btrfs.h:2362
? instrument_atomic_read_write include/linux/instrumented.h:96 [inline]
? atomic_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:1301 [inline]
? queued_spin_lock include/asm-generic/qspinlock.h:111 [inline]
? do_raw_spin_lock+0x133/0x290 kernel/locking/spinlock_debug.c:116
? find_held_lock+0x31/0x90 kernel/locking/lockdep.c:5350
? spin_unlock include/linux/spinlock.h:391 [inline]
? do_async_reclaim_data_space+0x369/0x5e0 fs/btrfs/space-info.c:1441
? __kasan_check_read+0x11/0x20 mm/kasan/shadow.c:31
? instrument_atomic_read include/linux/instrumented.h:68 [inline]
? atomic_read include/linux/atomic/atomic-instrumented.h:32 [inline]
? queued_spin_is_locked include/asm-generic/qspinlock.h:57 [inline]
? debug_spin_unlock kernel/locking/spinlock_debug.c:101 [inline]
? do_raw_spin_unlock+0x59/0x200 kernel/locking/spinlock_debug.c:141
do_async_reclaim_data_space+0x3c1/0x5e0 fs/btrfs/space-info.c:1410
btrfs_async_reclaim_data_space+0x3f/0xb0 fs/btrfs/space-info.c:1458
process_one_work+0x8e0/0x1980 kernel/workqueue.c:3263
? __pfx_process_one_work+0x10/0x10 include/linux/list.h:226
? move_linked_works+0x1a8/0x2c0 kernel/workqueue.c:1165
? assign_work+0x19d/0x240 kernel/workqueue.c:1206
? __lock_is_held kernel/locking/lockdep.c:5601 [inline]
? lock_is_held_type+0xa3/0x130 kernel/locking/lockdep.c:5940
process_scheduled_works kernel/workqueue.c:3346 [inline]
worker_thread+0x683/0xf80 kernel/workqueue.c:3427
? __pfx_worker_thread+0x10/0x10 kernel/workqueue.c:3570
kthread+0x3f0/0x850 kernel/kthread.c:463
? __pfx_kthread+0x10/0x10 arch/x86/include/asm/bitops.h:202
? trace_hardirqs_on+0x53/0x60 kernel/trace/trace_preemptirq.c:79
? __raw_spin_unlock_irq include/linux/spinlock_api_smp.h:159 [inline]
? _raw_spin_unlock_irq+0x27/0x70 kernel/locking/spinlock.c:202
? spin_unlock_irq include/linux/spinlock.h:401 [inline]
? calculate_sigpending+0x7c/0xb0 kernel/signal.c:194
? __pfx_kthread+0x10/0x10 arch/x86/include/asm/bitops.h:202
ret_from_fork+0x50f/0x610 arch/x86/kernel/process.c:158
? __pfx_kthread+0x10/0x10 arch/x86/include/asm/bitops.h:202
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Modules linked in:
---[ end trace 0000000000000000 ]---
RIP: 0010:backup_super_roots fs/btrfs/disk-io.c:1680 [inline]
RIP: 0010:write_all_supers+0x2d56/0x4980 fs/btrfs/disk-io.c:4022
Code: 40 38 f2 7f 08 84 d2 0f 85 72 19 00 00 49 8d 7c 24 18 41 88 9e 9a 00 00 00 48 ba 00 00 00 00 00 fc ff df 48 89 fe 48 c1 ee 03 <80> 3c 16 00 0f 85 33 19 00 00 48 ba 00 00 00 00 00 fc ff df 49 8b
RSP: 0018:ffff88800a617758 EFLAGS: 00010216
RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000000
RDX: dffffc0000000000 RSI: 0000000000000003 RDI: 0000000000000018
RBP: ffff88800a617898 R08: 1ffff110023f53a8 R09: 0000000000000000
R10: ffff8880173a8000 R11: 0000000000000001 R12: 0000000000000000
R13: ffff888000000000 R14: ffff88800b676d23 R15: ffffea0000000000
FS:  0000000000000000(0000) GS:ffff8880e4d3e000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00005baa3c26f678 CR3: 000000000e9b8000 CR4: 00000000000006f0
----------------
Code disassembly (best guess):
   0:	40 38 f2             	cmp    %sil,%dl
   3:	7f 08                	jg     0xd
   5:	84 d2                	test   %dl,%dl
   7:	0f 85 72 19 00 00    	jne    0x197f
   d:	49 8d 7c 24 18       	lea    0x18(%r12),%rdi
  12:	41 88 9e 9a 00 00 00 	mov    %bl,0x9a(%r14)
  19:	48 ba 00 00 00 00 00 	movabs $0xdffffc0000000000,%rdx
  20:	fc ff df
  23:	48 89 fe             	mov    %rdi,%rsi
  26:	48 c1 ee 03          	shr    $0x3,%rsi
* 2a:	80 3c 16 00          	cmpb   $0x0,(%rsi,%rdx,1) <-- trapping instruction
  2e:	0f 85 33 19 00 00    	jne    0x1967
  34:	48 ba 00 00 00 00 00 	movabs $0xdffffc0000000000,%rdx
  3b:	fc ff df
  3e:	49                   	rex.WB
  3f:	8b                   	.byte 0x8b

Reproduction (v6.18, x86_64, KASAN)
===================================
1. Download the crafted image (tested with Linux v6.18 + KASAN):
  https://drive.google.com/file/d/1xV0pjI3N-D83IzH62dphWCAD0RR-sV4U/view
2. Attach it as a block device (example assumes it shows up as /dev/vda),
then mount it read-write:
  mkdir -p /mnt/btrfs
  mount /dev/vda /mnt/btrfs
3. Trigger any metadata update, e.g. move a file into the mountpoint:
  echo test > /tmp/1.txt
  mv /tmp/1.txt /mnt/btrfs/
4. Result:
The system hits a NULL pointer dereference in the commit path, with KASAN
reporting the fault at backup_super_roots() (fs/btrfs/disk-io.c).

Thanks,
ZhengYuan Huang
---
 fs/btrfs/disk-io.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 0aa7e5d1b05f..900e462d8ea1 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -2119,6 +2119,18 @@ static int load_global_roots_objectid(struct btrfs_root *tree_root,
 	};
 	bool found = false;
 
+	/*
+	 * Without EXTENT_TREE_V2 we only have a single global extent/csum root.
+	 * btrfs_extent_root() and btrfs_csum_root() always look it up with offset
+	 * 0 (btrfs_global_root_id() returns 0). If we load only non-zero offsets
+	 * here, later users will see NULL and can crash (e.g. when backing up
+	 * super roots during a commit).
+	 */
+	bool need_offset0 = !btrfs_fs_incompat(fs_info, EXTENT_TREE_V2) &&
+		      (objectid == BTRFS_EXTENT_TREE_OBJECTID ||
+		       objectid == BTRFS_CSUM_TREE_OBJECTID);
+	bool found_offset0 = false;
+
 	/* If we have IGNOREDATACSUMS skip loading these roots. */
 	if (objectid == BTRFS_CSUM_TREE_OBJECTID &&
 	    btrfs_test_opt(fs_info, IGNOREDATACSUMS)) {
@@ -2144,6 +2156,8 @@ static int load_global_roots_objectid(struct btrfs_root *tree_root,
 		btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
 		if (key.objectid != objectid)
 			break;
+		if (need_offset0 && key.offset == 0)
+			found_offset0 = true;
 		btrfs_release_path(path);
 
 		/*
@@ -2169,6 +2183,13 @@ static int load_global_roots_objectid(struct btrfs_root *tree_root,
 	}
 	btrfs_release_path(path);
 
+	if (need_offset0 && found && !found_offset0) {
+		btrfs_err(fs_info,
+			  "missing global %s root item with offset 0 (extent_tree_v2 not enabled)",
+			  name);
+		return -EUCLEAN;
+	}
+
 	if (objectid == BTRFS_EXTENT_TREE_OBJECTID)
 		fs_info->nr_global_roots = max_global_id + 1;
 
-- 
2.43.0


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

* Re: [PATCH] btrfs: reject global extent/csum roots without offset 0 when extent_tree_v2 is off
  2026-03-02 11:02 [PATCH] btrfs: reject global extent/csum roots without offset 0 when extent_tree_v2 is off ZhengYuan Huang
@ 2026-03-02 11:51 ` Sun YangKai
  2026-03-02 13:23 ` Filipe Manana
  2026-03-05  2:09 ` David Sterba
  2 siblings, 0 replies; 4+ messages in thread
From: Sun YangKai @ 2026-03-02 11:51 UTC (permalink / raw)
  To: ZhengYuan Huang, linux-btrfs
  Cc: clm, dsterba, josef, linux-kernel, baijiaju1990, r33s3n6,
	zzzccc427, stable



On 2026/3/2 19:02, ZhengYuan Huang wrote:
> Without EXTENT_TREE_V2, btrfs_extent_root() and btrfs_csum_root() always
> look up the global roots at offset 0. A crafted image can provide only
> non-zero offsets for the extent/csum global roots, so the offset 0 lookup
> returns NULL and later leads to a NULL dereference
> (e.g. in backup_super_roots()).
> 
> Fix this by detecting this at mount time: when loading extent/csum
> global roots without EXTENT_TREE_V2, require that an offset 0 root item
> exists, otherwise fail the mount with -EUCLEAN.
> 
> Tested with a crafted image that has only non-zero offset global roots,
> which triggers the KASAN null-ptr-deref in backup_super_roots() before
> the fix, and fails the mount with -EUCLEAN after the fix.
> 
> Fixes: f7238e509404 ("btrfs: add support for multiple global roots")
> Cc: stable@vger.kernel.org # v5.18+
> Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
> ---
> A KASAN null-ptr-deref was triggered when mounting a crafted btrfs
> image and then doing a simple write/rename workload (e.g. moving any
> file into the mountpoint). The crash happens later during a transaction
> commit, in the super backup roots path.
> 
> Root cause
> ==========
> 
> backup_super_roots() looks up both the extent and csum roots through
> the "global roots" rb-tree:
> 
> extent_root = btrfs_extent_root(fs_info, 0);
> csum_root = btrfs_csum_root(fs_info, 0);
> 
> and then unconditionally dereferences extent_root->node and
> csum_root->node.
> 
> Both btrfs_extent_root() and btrfs_csum_root() build a key with:
> 
> .offset = btrfs_global_root_id(fs_info, bytenr)
> 
> and btrfs_global_root_id() returns 0 when the filesystem does not have
> the EXTENT_TREE_V2 incompat feature. This means that for
> non-extent_tree_v2 filesystems, the implementation assumes that there is
> always a global root item for:
> 
> (BTRFS_EXTENT_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, offset=0)
> (BTRFS_CSUM_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, offset=0)
> 
> However, an image without an offset = 0 root item can still be
> successfully mounted. The mount-time validation logic for the csum tree
> resides in load_global_roots_objectid(). Currently,
> load_global_roots_objectid() loads whatever root items exist for
> a given objectid, but it does not validate that offset = 0
> exists when EXTENT_TREE_V2 is not enabled. With a crafted image,
> the first (and only) root item for the csum tree (and similarly for
> extent) can have a large non-zero offset (e.g. 0x10000000000).
> load_global_roots_objectid() inserts that root into the rb-tree, but
> there is no entry for offset 0. Later, btrfs_csum_root(..., 0) looks up
> offset 0, returns NULL, and backup_super_roots() dereferences it,
> causing the crash.
> 
> So the bug is an invariant mismatch:
> 
> - lookup side (btrfs_{extent,csum}_root) assumes offset=0 exists when
> extent_tree_v2 is off
> 
> - load side (load_global_roots_objectid) does not enforce that assumption
> 
> Fix
> ===
> Teach load_global_roots_objectid() to enforce the "offset 0 must exist"
> invariant for the extent and csum trees when EXTENT_TREE_V2 is not enabled.
> If at least one root item is found but none has offset 0, fail the mount
> with -EUCLEAN and emit an explicit error message. This prevents later
> NULL dereferences and also avoids propagating a corrupted/unsupported
> global-root layout into runtime.
> 
> Note: extent_root is subject to the same assumption and is fixed by the
> same check (backup_super_roots dereferences extent_root->node as well).
> 
> KASAN report
> ============
> Oops: general protection fault, probably for non-canonical address 0xdffffc0000000003: 0000 [#1] SMP KASAN NOPTI
> KASAN: null-ptr-deref in range [0x0000000000000018-0x000000000000001f]
> CPU: 0 UID: 0 PID: 34 Comm: kworker/u8:1 Tainted: G           OE       6.18.0 #1 PREEMPT(voluntary)
> Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE
> Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> Workqueue: events_unbound btrfs_async_reclaim_data_space
> RIP: 0010:backup_super_roots fs/btrfs/disk-io.c:1680 [inline]
> RIP: 0010:write_all_supers+0x2d56/0x4980 fs/btrfs/disk-io.c:4022
> Code: 40 38 f2 7f 08 84 d2 0f 85 72 19 00 00 49 8d 7c 24 18 41 88 9e 9a 00 00 00 48 ba 00 00 00 00 00 fc ff df 48 89 fe 48 c1 ee 03 <80> 3c 16 00 0f 85 33 19 00 00 48 ba 00 00 00 00 00 fc ff df 49 8b
> RSP: 0018:ffff88800a617758 EFLAGS: 00010216
> RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000000
> RDX: dffffc0000000000 RSI: 0000000000000003 RDI: 0000000000000018
> RBP: ffff88800a617898 R08: 1ffff110023f53a8 R09: 0000000000000000
> R10: ffff8880173a8000 R11: 0000000000000001 R12: 0000000000000000
> R13: ffff888000000000 R14: ffff88800b676d23 R15: ffffea0000000000
> FS:  0000000000000000(0000) GS:ffff8880e4d3e000(0000) knlGS:0000000000000000
> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 00005baa3c26f678 CR3: 000000000e9b8000 CR4: 00000000000006f0
> Call Trace:
> <TASK>
> ? __pfx_write_all_supers+0x10/0x10 fs/btrfs/disk-io.c:3980
> ? btrfs_commit_transaction+0x2818/0x3d90 fs/btrfs/transaction.c:2526
> ? __lock_release kernel/locking/lockdep.c:5574 [inline]
> ? lock_release+0x122/0x2a0 kernel/locking/lockdep.c:5889
> btrfs_commit_transaction+0x28cc/0x3d90 fs/btrfs/transaction.c:2541
> ? __pfx_btrfs_commit_transaction+0x10/0x10 fs/btrfs/transaction.c:1919
> ? start_transaction+0x244/0x1740 fs/btrfs/transaction.c:780
> btrfs_commit_current_transaction+0x55/0xf0 fs/btrfs/transaction.c:2010
> flush_space+0x7d1/0xc40 fs/btrfs/space-info.c:888
> ? mark_usage kernel/locking/lockdep.c:4674 [inline]
> ? __lock_acquire+0x43e/0x21e0 kernel/locking/lockdep.c:5191
> ? __pfx_flush_space+0x10/0x10 include/trace/events/btrfs.h:2362
> ? instrument_atomic_read_write include/linux/instrumented.h:96 [inline]
> ? atomic_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:1301 [inline]
> ? queued_spin_lock include/asm-generic/qspinlock.h:111 [inline]
> ? do_raw_spin_lock+0x133/0x290 kernel/locking/spinlock_debug.c:116
> ? find_held_lock+0x31/0x90 kernel/locking/lockdep.c:5350
> ? spin_unlock include/linux/spinlock.h:391 [inline]
> ? do_async_reclaim_data_space+0x369/0x5e0 fs/btrfs/space-info.c:1441
> ? __kasan_check_read+0x11/0x20 mm/kasan/shadow.c:31
> ? instrument_atomic_read include/linux/instrumented.h:68 [inline]
> ? atomic_read include/linux/atomic/atomic-instrumented.h:32 [inline]
> ? queued_spin_is_locked include/asm-generic/qspinlock.h:57 [inline]
> ? debug_spin_unlock kernel/locking/spinlock_debug.c:101 [inline]
> ? do_raw_spin_unlock+0x59/0x200 kernel/locking/spinlock_debug.c:141
> do_async_reclaim_data_space+0x3c1/0x5e0 fs/btrfs/space-info.c:1410
> btrfs_async_reclaim_data_space+0x3f/0xb0 fs/btrfs/space-info.c:1458
> process_one_work+0x8e0/0x1980 kernel/workqueue.c:3263
> ? __pfx_process_one_work+0x10/0x10 include/linux/list.h:226
> ? move_linked_works+0x1a8/0x2c0 kernel/workqueue.c:1165
> ? assign_work+0x19d/0x240 kernel/workqueue.c:1206
> ? __lock_is_held kernel/locking/lockdep.c:5601 [inline]
> ? lock_is_held_type+0xa3/0x130 kernel/locking/lockdep.c:5940
> process_scheduled_works kernel/workqueue.c:3346 [inline]
> worker_thread+0x683/0xf80 kernel/workqueue.c:3427
> ? __pfx_worker_thread+0x10/0x10 kernel/workqueue.c:3570
> kthread+0x3f0/0x850 kernel/kthread.c:463
> ? __pfx_kthread+0x10/0x10 arch/x86/include/asm/bitops.h:202
> ? trace_hardirqs_on+0x53/0x60 kernel/trace/trace_preemptirq.c:79
> ? __raw_spin_unlock_irq include/linux/spinlock_api_smp.h:159 [inline]
> ? _raw_spin_unlock_irq+0x27/0x70 kernel/locking/spinlock.c:202
> ? spin_unlock_irq include/linux/spinlock.h:401 [inline]
> ? calculate_sigpending+0x7c/0xb0 kernel/signal.c:194
> ? __pfx_kthread+0x10/0x10 arch/x86/include/asm/bitops.h:202
> ret_from_fork+0x50f/0x610 arch/x86/kernel/process.c:158
> ? __pfx_kthread+0x10/0x10 arch/x86/include/asm/bitops.h:202
> ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
> </TASK>
> Modules linked in:
> ---[ end trace 0000000000000000 ]---
> RIP: 0010:backup_super_roots fs/btrfs/disk-io.c:1680 [inline]
> RIP: 0010:write_all_supers+0x2d56/0x4980 fs/btrfs/disk-io.c:4022
> Code: 40 38 f2 7f 08 84 d2 0f 85 72 19 00 00 49 8d 7c 24 18 41 88 9e 9a 00 00 00 48 ba 00 00 00 00 00 fc ff df 48 89 fe 48 c1 ee 03 <80> 3c 16 00 0f 85 33 19 00 00 48 ba 00 00 00 00 00 fc ff df 49 8b
> RSP: 0018:ffff88800a617758 EFLAGS: 00010216
> RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000000
> RDX: dffffc0000000000 RSI: 0000000000000003 RDI: 0000000000000018
> RBP: ffff88800a617898 R08: 1ffff110023f53a8 R09: 0000000000000000
> R10: ffff8880173a8000 R11: 0000000000000001 R12: 0000000000000000
> R13: ffff888000000000 R14: ffff88800b676d23 R15: ffffea0000000000
> FS:  0000000000000000(0000) GS:ffff8880e4d3e000(0000) knlGS:0000000000000000
> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 00005baa3c26f678 CR3: 000000000e9b8000 CR4: 00000000000006f0
> ----------------
> Code disassembly (best guess):
>     0:	40 38 f2             	cmp    %sil,%dl
>     3:	7f 08                	jg     0xd
>     5:	84 d2                	test   %dl,%dl
>     7:	0f 85 72 19 00 00    	jne    0x197f
>     d:	49 8d 7c 24 18       	lea    0x18(%r12),%rdi
>    12:	41 88 9e 9a 00 00 00 	mov    %bl,0x9a(%r14)
>    19:	48 ba 00 00 00 00 00 	movabs $0xdffffc0000000000,%rdx
>    20:	fc ff df
>    23:	48 89 fe             	mov    %rdi,%rsi
>    26:	48 c1 ee 03          	shr    $0x3,%rsi
> * 2a:	80 3c 16 00          	cmpb   $0x0,(%rsi,%rdx,1) <-- trapping instruction
>    2e:	0f 85 33 19 00 00    	jne    0x1967
>    34:	48 ba 00 00 00 00 00 	movabs $0xdffffc0000000000,%rdx
>    3b:	fc ff df
>    3e:	49                   	rex.WB
>    3f:	8b                   	.byte 0x8b
> 
> Reproduction (v6.18, x86_64, KASAN)
> ===================================
> 1. Download the crafted image (tested with Linux v6.18 + KASAN):
>    https://drive.google.com/file/d/1xV0pjI3N-D83IzH62dphWCAD0RR-sV4U/view
> 2. Attach it as a block device (example assumes it shows up as /dev/vda),
> then mount it read-write:
>    mkdir -p /mnt/btrfs
>    mount /dev/vda /mnt/btrfs
> 3. Trigger any metadata update, e.g. move a file into the mountpoint:
>    echo test > /tmp/1.txt
>    mv /tmp/1.txt /mnt/btrfs/
> 4. Result:
> The system hits a NULL pointer dereference in the commit path, with KASAN
> reporting the fault at backup_super_roots() (fs/btrfs/disk-io.c).
> 
> Thanks,
> ZhengYuan Huang
> ---
>   fs/btrfs/disk-io.c | 21 +++++++++++++++++++++
>   1 file changed, 21 insertions(+)
> 
> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
> index 0aa7e5d1b05f..900e462d8ea1 100644
> --- a/fs/btrfs/disk-io.c
> +++ b/fs/btrfs/disk-io.c
> @@ -2119,6 +2119,18 @@ static int load_global_roots_objectid(struct btrfs_root *tree_root,
>   	};
>   	bool found = false;
>   
> +	/*
> +	 * Without EXTENT_TREE_V2 we only have a single global extent/csum root.
> +	 * btrfs_extent_root() and btrfs_csum_root() always look it up with offset
> +	 * 0 (btrfs_global_root_id() returns 0). If we load only non-zero offsets
> +	 * here, later users will see NULL and can crash (e.g. when backing up
> +	 * super roots during a commit).
> +	 */
> +	bool need_offset0 = !btrfs_fs_incompat(fs_info, EXTENT_TREE_V2) &&
> +		      (objectid == BTRFS_EXTENT_TREE_OBJECTID ||
> +		       objectid == BTRFS_CSUM_TREE_OBJECTID);
> +	bool found_offset0 = false;
> +
>   	/* If we have IGNOREDATACSUMS skip loading these roots. */
>   	if (objectid == BTRFS_CSUM_TREE_OBJECTID &&
>   	    btrfs_test_opt(fs_info, IGNOREDATACSUMS)) {
> @@ -2144,6 +2156,8 @@ static int load_global_roots_objectid(struct btrfs_root *tree_root,
>   		btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
>   		if (key.objectid != objectid)
>   			break;
> +		if (need_offset0 && key.offset == 0)
> +			found_offset0 = true;
>   		btrfs_release_path(path);
>   
>   		/*
> @@ -2169,6 +2183,13 @@ static int load_global_roots_objectid(struct btrfs_root *tree_root,
>   	}
>   	btrfs_release_path(path);
>   
> +	if (need_offset0 && found && !found_offset0) {
> +		btrfs_err(fs_info,
> +			  "missing global %s root item with offset 0 (extent_tree_v2 not enabled)",
> +			  name);
> +		return -EUCLEAN;
> +	}
> +
>   	if (objectid == BTRFS_EXTENT_TREE_OBJECTID)
>   		fs_info->nr_global_roots = max_global_id + 1;
>   

I wonder if it would be better to put this check in 
read_tree_root_path() because that's where the trees are loaded so we 
can fail fast and prevent loading the corrupted root rather than 
allowing it to be inserted into the rb-tree first.

Thanks,
Sun YangKai


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

* Re: [PATCH] btrfs: reject global extent/csum roots without offset 0 when extent_tree_v2 is off
  2026-03-02 11:02 [PATCH] btrfs: reject global extent/csum roots without offset 0 when extent_tree_v2 is off ZhengYuan Huang
  2026-03-02 11:51 ` Sun YangKai
@ 2026-03-02 13:23 ` Filipe Manana
  2026-03-05  2:09 ` David Sterba
  2 siblings, 0 replies; 4+ messages in thread
From: Filipe Manana @ 2026-03-02 13:23 UTC (permalink / raw)
  To: ZhengYuan Huang
  Cc: linux-btrfs, clm, dsterba, josef, linux-kernel, baijiaju1990,
	r33s3n6, zzzccc427, stable

On Mon, Mar 2, 2026 at 11:03 AM ZhengYuan Huang <gality369@gmail.com> wrote:
>
> Without EXTENT_TREE_V2, btrfs_extent_root() and btrfs_csum_root() always
> look up the global roots at offset 0. A crafted image can provide only
> non-zero offsets for the extent/csum global roots, so the offset 0 lookup
> returns NULL and later leads to a NULL dereference
> (e.g. in backup_super_roots()).
>
> Fix this by detecting this at mount time: when loading extent/csum
> global roots without EXTENT_TREE_V2, require that an offset 0 root item
> exists, otherwise fail the mount with -EUCLEAN.
>
> Tested with a crafted image that has only non-zero offset global roots,
> which triggers the KASAN null-ptr-deref in backup_super_roots() before
> the fix, and fails the mount with -EUCLEAN after the fix.
>
> Fixes: f7238e509404 ("btrfs: add support for multiple global roots")
> Cc: stable@vger.kernel.org # v5.18+
> Signed-off-by: ZhengYuan Huang <gality369@gmail.com>

This was already fixed in for-next:

https://lore.kernel.org/linux-btrfs/cover.1770580436.git.fdmanana@suse.com/

Always check the github for-next branch before sending patches:

https://github.com/btrfs/linux/commits/for-next/

Thanks.

> ---
> A KASAN null-ptr-deref was triggered when mounting a crafted btrfs
> image and then doing a simple write/rename workload (e.g. moving any
> file into the mountpoint). The crash happens later during a transaction
> commit, in the super backup roots path.
>
> Root cause
> ==========
>
> backup_super_roots() looks up both the extent and csum roots through
> the "global roots" rb-tree:
>
> extent_root = btrfs_extent_root(fs_info, 0);
> csum_root = btrfs_csum_root(fs_info, 0);
>
> and then unconditionally dereferences extent_root->node and
> csum_root->node.
>
> Both btrfs_extent_root() and btrfs_csum_root() build a key with:
>
> .offset = btrfs_global_root_id(fs_info, bytenr)
>
> and btrfs_global_root_id() returns 0 when the filesystem does not have
> the EXTENT_TREE_V2 incompat feature. This means that for
> non-extent_tree_v2 filesystems, the implementation assumes that there is
> always a global root item for:
>
> (BTRFS_EXTENT_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, offset=0)
> (BTRFS_CSUM_TREE_OBJECTID, BTRFS_ROOT_ITEM_KEY, offset=0)
>
> However, an image without an offset = 0 root item can still be
> successfully mounted. The mount-time validation logic for the csum tree
> resides in load_global_roots_objectid(). Currently,
> load_global_roots_objectid() loads whatever root items exist for
> a given objectid, but it does not validate that offset = 0
> exists when EXTENT_TREE_V2 is not enabled. With a crafted image,
> the first (and only) root item for the csum tree (and similarly for
> extent) can have a large non-zero offset (e.g. 0x10000000000).
> load_global_roots_objectid() inserts that root into the rb-tree, but
> there is no entry for offset 0. Later, btrfs_csum_root(..., 0) looks up
> offset 0, returns NULL, and backup_super_roots() dereferences it,
> causing the crash.
>
> So the bug is an invariant mismatch:
>
> - lookup side (btrfs_{extent,csum}_root) assumes offset=0 exists when
> extent_tree_v2 is off
>
> - load side (load_global_roots_objectid) does not enforce that assumption
>
> Fix
> ===
> Teach load_global_roots_objectid() to enforce the "offset 0 must exist"
> invariant for the extent and csum trees when EXTENT_TREE_V2 is not enabled.
> If at least one root item is found but none has offset 0, fail the mount
> with -EUCLEAN and emit an explicit error message. This prevents later
> NULL dereferences and also avoids propagating a corrupted/unsupported
> global-root layout into runtime.
>
> Note: extent_root is subject to the same assumption and is fixed by the
> same check (backup_super_roots dereferences extent_root->node as well).
>
> KASAN report
> ============
> Oops: general protection fault, probably for non-canonical address 0xdffffc0000000003: 0000 [#1] SMP KASAN NOPTI
> KASAN: null-ptr-deref in range [0x0000000000000018-0x000000000000001f]
> CPU: 0 UID: 0 PID: 34 Comm: kworker/u8:1 Tainted: G           OE       6.18.0 #1 PREEMPT(voluntary)
> Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE
> Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
> Workqueue: events_unbound btrfs_async_reclaim_data_space
> RIP: 0010:backup_super_roots fs/btrfs/disk-io.c:1680 [inline]
> RIP: 0010:write_all_supers+0x2d56/0x4980 fs/btrfs/disk-io.c:4022
> Code: 40 38 f2 7f 08 84 d2 0f 85 72 19 00 00 49 8d 7c 24 18 41 88 9e 9a 00 00 00 48 ba 00 00 00 00 00 fc ff df 48 89 fe 48 c1 ee 03 <80> 3c 16 00 0f 85 33 19 00 00 48 ba 00 00 00 00 00 fc ff df 49 8b
> RSP: 0018:ffff88800a617758 EFLAGS: 00010216
> RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000000
> RDX: dffffc0000000000 RSI: 0000000000000003 RDI: 0000000000000018
> RBP: ffff88800a617898 R08: 1ffff110023f53a8 R09: 0000000000000000
> R10: ffff8880173a8000 R11: 0000000000000001 R12: 0000000000000000
> R13: ffff888000000000 R14: ffff88800b676d23 R15: ffffea0000000000
> FS:  0000000000000000(0000) GS:ffff8880e4d3e000(0000) knlGS:0000000000000000
> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 00005baa3c26f678 CR3: 000000000e9b8000 CR4: 00000000000006f0
> Call Trace:
> <TASK>
> ? __pfx_write_all_supers+0x10/0x10 fs/btrfs/disk-io.c:3980
> ? btrfs_commit_transaction+0x2818/0x3d90 fs/btrfs/transaction.c:2526
> ? __lock_release kernel/locking/lockdep.c:5574 [inline]
> ? lock_release+0x122/0x2a0 kernel/locking/lockdep.c:5889
> btrfs_commit_transaction+0x28cc/0x3d90 fs/btrfs/transaction.c:2541
> ? __pfx_btrfs_commit_transaction+0x10/0x10 fs/btrfs/transaction.c:1919
> ? start_transaction+0x244/0x1740 fs/btrfs/transaction.c:780
> btrfs_commit_current_transaction+0x55/0xf0 fs/btrfs/transaction.c:2010
> flush_space+0x7d1/0xc40 fs/btrfs/space-info.c:888
> ? mark_usage kernel/locking/lockdep.c:4674 [inline]
> ? __lock_acquire+0x43e/0x21e0 kernel/locking/lockdep.c:5191
> ? __pfx_flush_space+0x10/0x10 include/trace/events/btrfs.h:2362
> ? instrument_atomic_read_write include/linux/instrumented.h:96 [inline]
> ? atomic_try_cmpxchg_acquire include/linux/atomic/atomic-instrumented.h:1301 [inline]
> ? queued_spin_lock include/asm-generic/qspinlock.h:111 [inline]
> ? do_raw_spin_lock+0x133/0x290 kernel/locking/spinlock_debug.c:116
> ? find_held_lock+0x31/0x90 kernel/locking/lockdep.c:5350
> ? spin_unlock include/linux/spinlock.h:391 [inline]
> ? do_async_reclaim_data_space+0x369/0x5e0 fs/btrfs/space-info.c:1441
> ? __kasan_check_read+0x11/0x20 mm/kasan/shadow.c:31
> ? instrument_atomic_read include/linux/instrumented.h:68 [inline]
> ? atomic_read include/linux/atomic/atomic-instrumented.h:32 [inline]
> ? queued_spin_is_locked include/asm-generic/qspinlock.h:57 [inline]
> ? debug_spin_unlock kernel/locking/spinlock_debug.c:101 [inline]
> ? do_raw_spin_unlock+0x59/0x200 kernel/locking/spinlock_debug.c:141
> do_async_reclaim_data_space+0x3c1/0x5e0 fs/btrfs/space-info.c:1410
> btrfs_async_reclaim_data_space+0x3f/0xb0 fs/btrfs/space-info.c:1458
> process_one_work+0x8e0/0x1980 kernel/workqueue.c:3263
> ? __pfx_process_one_work+0x10/0x10 include/linux/list.h:226
> ? move_linked_works+0x1a8/0x2c0 kernel/workqueue.c:1165
> ? assign_work+0x19d/0x240 kernel/workqueue.c:1206
> ? __lock_is_held kernel/locking/lockdep.c:5601 [inline]
> ? lock_is_held_type+0xa3/0x130 kernel/locking/lockdep.c:5940
> process_scheduled_works kernel/workqueue.c:3346 [inline]
> worker_thread+0x683/0xf80 kernel/workqueue.c:3427
> ? __pfx_worker_thread+0x10/0x10 kernel/workqueue.c:3570
> kthread+0x3f0/0x850 kernel/kthread.c:463
> ? __pfx_kthread+0x10/0x10 arch/x86/include/asm/bitops.h:202
> ? trace_hardirqs_on+0x53/0x60 kernel/trace/trace_preemptirq.c:79
> ? __raw_spin_unlock_irq include/linux/spinlock_api_smp.h:159 [inline]
> ? _raw_spin_unlock_irq+0x27/0x70 kernel/locking/spinlock.c:202
> ? spin_unlock_irq include/linux/spinlock.h:401 [inline]
> ? calculate_sigpending+0x7c/0xb0 kernel/signal.c:194
> ? __pfx_kthread+0x10/0x10 arch/x86/include/asm/bitops.h:202
> ret_from_fork+0x50f/0x610 arch/x86/kernel/process.c:158
> ? __pfx_kthread+0x10/0x10 arch/x86/include/asm/bitops.h:202
> ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
> </TASK>
> Modules linked in:
> ---[ end trace 0000000000000000 ]---
> RIP: 0010:backup_super_roots fs/btrfs/disk-io.c:1680 [inline]
> RIP: 0010:write_all_supers+0x2d56/0x4980 fs/btrfs/disk-io.c:4022
> Code: 40 38 f2 7f 08 84 d2 0f 85 72 19 00 00 49 8d 7c 24 18 41 88 9e 9a 00 00 00 48 ba 00 00 00 00 00 fc ff df 48 89 fe 48 c1 ee 03 <80> 3c 16 00 0f 85 33 19 00 00 48 ba 00 00 00 00 00 fc ff df 49 8b
> RSP: 0018:ffff88800a617758 EFLAGS: 00010216
> RAX: 0000000000000000 RBX: 0000000000000000 RCX: 0000000000000000
> RDX: dffffc0000000000 RSI: 0000000000000003 RDI: 0000000000000018
> RBP: ffff88800a617898 R08: 1ffff110023f53a8 R09: 0000000000000000
> R10: ffff8880173a8000 R11: 0000000000000001 R12: 0000000000000000
> R13: ffff888000000000 R14: ffff88800b676d23 R15: ffffea0000000000
> FS:  0000000000000000(0000) GS:ffff8880e4d3e000(0000) knlGS:0000000000000000
> CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 00005baa3c26f678 CR3: 000000000e9b8000 CR4: 00000000000006f0
> ----------------
> Code disassembly (best guess):
>    0:   40 38 f2                cmp    %sil,%dl
>    3:   7f 08                   jg     0xd
>    5:   84 d2                   test   %dl,%dl
>    7:   0f 85 72 19 00 00       jne    0x197f
>    d:   49 8d 7c 24 18          lea    0x18(%r12),%rdi
>   12:   41 88 9e 9a 00 00 00    mov    %bl,0x9a(%r14)
>   19:   48 ba 00 00 00 00 00    movabs $0xdffffc0000000000,%rdx
>   20:   fc ff df
>   23:   48 89 fe                mov    %rdi,%rsi
>   26:   48 c1 ee 03             shr    $0x3,%rsi
> * 2a:   80 3c 16 00             cmpb   $0x0,(%rsi,%rdx,1) <-- trapping instruction
>   2e:   0f 85 33 19 00 00       jne    0x1967
>   34:   48 ba 00 00 00 00 00    movabs $0xdffffc0000000000,%rdx
>   3b:   fc ff df
>   3e:   49                      rex.WB
>   3f:   8b                      .byte 0x8b
>
> Reproduction (v6.18, x86_64, KASAN)
> ===================================
> 1. Download the crafted image (tested with Linux v6.18 + KASAN):
>   https://drive.google.com/file/d/1xV0pjI3N-D83IzH62dphWCAD0RR-sV4U/view
> 2. Attach it as a block device (example assumes it shows up as /dev/vda),
> then mount it read-write:
>   mkdir -p /mnt/btrfs
>   mount /dev/vda /mnt/btrfs
> 3. Trigger any metadata update, e.g. move a file into the mountpoint:
>   echo test > /tmp/1.txt
>   mv /tmp/1.txt /mnt/btrfs/
> 4. Result:
> The system hits a NULL pointer dereference in the commit path, with KASAN
> reporting the fault at backup_super_roots() (fs/btrfs/disk-io.c).
>
> Thanks,
> ZhengYuan Huang
> ---
>  fs/btrfs/disk-io.c | 21 +++++++++++++++++++++
>  1 file changed, 21 insertions(+)
>
> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
> index 0aa7e5d1b05f..900e462d8ea1 100644
> --- a/fs/btrfs/disk-io.c
> +++ b/fs/btrfs/disk-io.c
> @@ -2119,6 +2119,18 @@ static int load_global_roots_objectid(struct btrfs_root *tree_root,
>         };
>         bool found = false;
>
> +       /*
> +        * Without EXTENT_TREE_V2 we only have a single global extent/csum root.
> +        * btrfs_extent_root() and btrfs_csum_root() always look it up with offset
> +        * 0 (btrfs_global_root_id() returns 0). If we load only non-zero offsets
> +        * here, later users will see NULL and can crash (e.g. when backing up
> +        * super roots during a commit).
> +        */
> +       bool need_offset0 = !btrfs_fs_incompat(fs_info, EXTENT_TREE_V2) &&
> +                     (objectid == BTRFS_EXTENT_TREE_OBJECTID ||
> +                      objectid == BTRFS_CSUM_TREE_OBJECTID);
> +       bool found_offset0 = false;
> +
>         /* If we have IGNOREDATACSUMS skip loading these roots. */
>         if (objectid == BTRFS_CSUM_TREE_OBJECTID &&
>             btrfs_test_opt(fs_info, IGNOREDATACSUMS)) {
> @@ -2144,6 +2156,8 @@ static int load_global_roots_objectid(struct btrfs_root *tree_root,
>                 btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
>                 if (key.objectid != objectid)
>                         break;
> +               if (need_offset0 && key.offset == 0)
> +                       found_offset0 = true;
>                 btrfs_release_path(path);
>
>                 /*
> @@ -2169,6 +2183,13 @@ static int load_global_roots_objectid(struct btrfs_root *tree_root,
>         }
>         btrfs_release_path(path);
>
> +       if (need_offset0 && found && !found_offset0) {
> +               btrfs_err(fs_info,
> +                         "missing global %s root item with offset 0 (extent_tree_v2 not enabled)",
> +                         name);
> +               return -EUCLEAN;
> +       }
> +
>         if (objectid == BTRFS_EXTENT_TREE_OBJECTID)
>                 fs_info->nr_global_roots = max_global_id + 1;
>
> --
> 2.43.0
>
>

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

* Re: [PATCH] btrfs: reject global extent/csum roots without offset 0 when extent_tree_v2 is off
  2026-03-02 11:02 [PATCH] btrfs: reject global extent/csum roots without offset 0 when extent_tree_v2 is off ZhengYuan Huang
  2026-03-02 11:51 ` Sun YangKai
  2026-03-02 13:23 ` Filipe Manana
@ 2026-03-05  2:09 ` David Sterba
  2 siblings, 0 replies; 4+ messages in thread
From: David Sterba @ 2026-03-05  2:09 UTC (permalink / raw)
  To: ZhengYuan Huang
  Cc: linux-btrfs, clm, dsterba, josef, linux-kernel, baijiaju1990,
	r33s3n6, zzzccc427, stable

On Mon, Mar 02, 2026 at 07:02:02PM +0800, ZhengYuan Huang wrote:
> Without EXTENT_TREE_V2, btrfs_extent_root() and btrfs_csum_root() always
> look up the global roots at offset 0. A crafted image can provide only
> non-zero offsets for the extent/csum global roots, so the offset 0 lookup
> returns NULL and later leads to a NULL dereference
> (e.g. in backup_super_roots()).
> 
> Fix this by detecting this at mount time: when loading extent/csum
> global roots without EXTENT_TREE_V2, require that an offset 0 root item
> exists, otherwise fail the mount with -EUCLEAN.
> 
> Tested with a crafted image that has only non-zero offset global roots,
> which triggers the KASAN null-ptr-deref in backup_super_roots() before
> the fix, and fails the mount with -EUCLEAN after the fix.
> 
> Fixes: f7238e509404 ("btrfs: add support for multiple global roots")
> Cc: stable@vger.kernel.org # v5.18+
> Signed-off-by: ZhengYuan Huang <gality369@gmail.com>

You've cut the changelog here but the rest of the information should be
also there, perhaps in a more condensed form. You can add other things
under the "---" line in case it's relevant for the patch submission but
not for the long term git history.

> ---
[...]

> CPU: 0 UID: 0 PID: 34 Comm: kworker/u8:1 Tainted: G           OE       6.18.0 #1 PREEMPT(voluntary)

> Reproduction (v6.18, x86_64, KASAN)
> ===================================
> 1. Download the crafted image (tested with Linux v6.18 + KASAN):

This seems to be testing 6.18 and probably the exact release, not the
updated stable branch which is 6.18.16 right now. It's OK to test on
older versions but please use the latest available one as the fixes get
backported.

As Filipe said this has been fixed and the patch is in for-next, not yet
in master or any stable tree. For fuzzing or crafted images it makes
more sense to test on recent development branches. If you don't track
each subsystem individually you should use linux-next, where the btrfs
for-next branch gets merged and the updates happen every other day.

I'll queue the fixes for some of the upcoming -rc. Thanks.

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

end of thread, other threads:[~2026-03-05  2:09 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-02 11:02 [PATCH] btrfs: reject global extent/csum roots without offset 0 when extent_tree_v2 is off ZhengYuan Huang
2026-03-02 11:51 ` Sun YangKai
2026-03-02 13:23 ` Filipe Manana
2026-03-05  2:09 ` David Sterba

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