| 0/0 |
2026/06/02 10:05 |
flow |
assessment-security |
57m
Results: map[DenialOfService:true Explanation:**Bug Analysis:**
The bug is a Use-After-Free (UAF) in `btrfs_put_root` when accessing the
`root->reloc_root` pointer.
During a Btrfs balance operation (or relocation recovery),
`btrfs_init_reloc_root` allocates a relocation root and assigns it to
`root->reloc_root`, taking a reference. The relocation root is also added to a
relocation list (`rc->reloc_roots`), which holds another reference.
If the relocation process encounters an error during `merge_reloc_roots`, it
jumps to the error handling path and calls `free_reloc_roots`. This function
iterates over the list and calls `__del_reloc_root`, which removes the roots
from the list and drops the list's reference. However, due to a reference
counting logic flaw in the error path, the `reloc_root` is prematurely freed
while `root->reloc_root` still points to it.
Later, when the filesystem is unmounted (e.g., the process exits and drops the
last mount reference), `close_ctree` is invoked. This calls
`btrfs_drop_and_free_fs_root` on the filesystem roots. The cleanup code checks
if `root->reloc_root` is set, and if so, it calls
`btrfs_put_root(root->reloc_root)` to drop the reference. Since the object was
already freed during the failed balance operation, this results in a
Use-After-Free on the `struct btrfs_root` object.
**Security Impact:**
* **Exploitable:** Yes. The UAF occurs on `struct btrfs_root`, which is a
massive and complex structure containing numerous spinlocks, list heads, and
pointers. An attacker who can spray the heap and reclaim the freed `btrfs_root`
object can easily achieve memory corruption, control-flow hijacking, and
ultimately privilege escalation.
* **Denial of Service:** Yes. The UAF and subsequent memory corruption will
reliably crash the kernel.
* **Unprivileged / User Namespace:** No. Triggering this bug requires either
issuing the `BTRFS_IOC_BALANCE` ioctl or mounting a Btrfs filesystem. Both
operations require `CAP_SYS_ADMIN` in the initial user namespace (Btrfs does not
have the `FS_USERNS_MOUNT` flag, meaning it cannot be mounted inside an
unprivileged user namespace).
* **Filesystem / Peripheral Trigger:** Yes. The bug can be triggered by mounting
a maliciously crafted Btrfs image that forces the relocation recovery path to
fail, making it relevant for scenarios involving untrusted USB drives or
auto-mounting desktop environments. Exploitable:true FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/06/02 10:05 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/06/02 10:05 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/a2a52845f4a8345ec80ef0d5d344ebe161ab22e0]
|
| 3/1 |
2026/06/02 10:07 |
action |
kernel-builder |
29m
Results: map[KernelObj:/app/workdir/cache/build/5125b02d145101f0647dc31780c0dfcd5d7aac87]
|
| 4/1 |
2026/06/02 10:36 |
action |
codesearch-prepare |
18m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/06/02 10:55 |
agent |
expert |
7m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption or elevated privileges.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
BTRFS info (device loop0 state EA): last unmount of filesystem 3a375e4e-b156-4d76-a2ad-16e198ce1409
==================================================================
BUG: KASAN: slab-use-after-free in instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
BUG: KASAN: slab-use-after-free in atomic_fetch_sub_release include/linux/atomic/atomic-instrumented.h:400 [inline]
BUG: KASAN: slab-use-after-free in __refcount_sub_and_test include/linux/refcount.h:389 [inline]
BUG: KASAN: slab-use-after-free in __refcount_dec_and_test include/linux/refcount.h:432 [inline]
BUG: KASAN: slab-use-after-free in refcount_dec_and_test include/linux/refcount.h:450 [inline]
BUG: KASAN: slab-use-after-free in btrfs_put_root+0x2f/0x250 fs/btrfs/disk-io.c:1806
Write of size 4 at addr ffff88803f978630 by task syz.0.0/5338
CPU: 0 UID: 0 PID: 5338 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_address_description+0x55/0x1e0 mm/kasan/report.c:378
print_report+0x58/0x70 mm/kasan/report.c:482
kasan_report+0x117/0x150 mm/kasan/report.c:595
check_region_inline mm/kasan/generic.c:-1 [inline]
kasan_check_range+0x264/0x2c0 mm/kasan/generic.c:200
instrument_atomic_read_write include/linux/instrumented.h:112 [inline]
atomic_fetch_sub_release include/linux/atomic/atomic-instrumented.h:400 [inline]
__refcount_sub_and_test include/linux/refcount.h:389 [inline]
__refcount_dec_and_test include/linux/refcount.h:432 [inline]
refcount_dec_and_test include/linux/refcount.h:450 [inline]
btrfs_put_root+0x2f/0x250 fs/btrfs/disk-io.c:1806
btrfs_drop_and_free_fs_root+0x160/0x210 fs/btrfs/disk-io.c:4154
btrfs_free_fs_roots+0x2f9/0x3c0 fs/btrfs/disk-io.c:1847
close_ctree+0x798/0x12d0 fs/btrfs/disk-io.c:4442
generic_shutdown_super+0x13d/0x2d0 fs/super.c:646
kill_anon_super+0x3b/0x70 fs/super.c:1292
btrfs_kill_super+0x41/0x50 fs/btrfs/super.c:2127
deactivate_locked_super+0xbc/0x130 fs/super.c:476
cleanup_mnt+0x437/0x4d0 fs/namespace.c:1312
task_work_run+0x1d9/0x270 kernel/task_work.c:233
exit_task_work include/linux/task_work.h:40 [inline]
do_exit+0x70f/0x22c0 kernel/exit.c:976
do_group_exit+0x21b/0x2d0 kernel/exit.c:1119
__do_sys_exit_group kernel/exit.c:1130 [inline]
__se_sys_exit_group kernel/exit.c:1128 [inline]
__x64_sys_exit_group+0x3f/0x40 kernel/exit.c:1128
x64_sys_call+0x221a/0x2240 arch/x86/include/generated/asm/syscalls_64.h:232
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f958ef9ce59
Code: Unable to access opcode bytes at 0x7f958ef9ce2f.
RSP: 002b:00007fffd4058318 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7
RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f958ef9ce59
RDX: 00007f958c3f8000 RSI: 0000000000000000 RDI: 0000000000000000
RBP: 0000000000000003 R08: 0000000000000000 R09: 00007f958f1e73e0
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f958f1e73e0 R14: 0000000000000003 R15: 00007fffd40583d0
</TASK>
Allocated by task 1061:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_noprof+0x31c/0x660 mm/slub.c:5420
kmalloc_noprof include/linux/slab.h:950 [inline]
kzalloc_noprof include/linux/slab.h:1188 [inline]
btrfs_alloc_root+0x75/0x930 fs/btrfs/disk-io.c:611
read_tree_root_path+0x127/0xb00 fs/btrfs/disk-io.c:976
btrfs_read_tree_root+0x34/0x60 fs/btrfs/disk-io.c:1040
create_reloc_root+0x6b3/0xcb0 fs/btrfs/relocation.c:677
btrfs_init_reloc_root+0x2ec/0x4b0 fs/btrfs/relocation.c:744
record_root_in_trans+0x2ab/0x350 fs/btrfs/transaction.c:461
btrfs_record_root_in_trans+0x15c/0x180 fs/btrfs/transaction.c:507
start_transaction+0x39c/0x1820 fs/btrfs/transaction.c:793
btrfs_finish_one_ordered+0x88e/0x2680 fs/btrfs/inode.c:3254
btrfs_work_helper+0x37b/0xc20 fs/btrfs/async-thread.c:312
process_one_work kernel/workqueue.c:3314 [inline]
process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478
kthread+0x389/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Freed by task 5339:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x46/0x50 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5c/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2689 [inline]
slab_free mm/slub.c:6251 [inline]
kfree+0x1c5/0x640 mm/slub.c:6566
__del_reloc_root+0x341/0x3b0 fs/btrfs/relocation.c:547
free_reloc_roots+0x5f/0x90 fs/btrfs/relocation.c:1803
merge_reloc_roots+0x73f/0x8a0 fs/btrfs/relocation.c:1888
relocate_block_group+0xbcc/0xe70 fs/btrfs/relocation.c:3638
do_nonremap_reloc+0xa8/0x5b0 fs/btrfs/relocation.c:5268
btrfs_relocate_block_group+0x7e6/0xc40 fs/btrfs/relocation.c:5434
btrfs_relocate_chunk+0x115/0x820 fs/btrfs/volumes.c:3598
__btrfs_balance+0x1db0/0x2ae0 fs/btrfs/volumes.c:4509
btrfs_balance+0xaf3/0x11b0 fs/btrfs/volumes.c:4896
btrfs_ioctl_balance+0x3d3/0x610 fs/btrfs/ioctl.c:3453
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:597 [inline]
__se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline]
do_syscall_64+0x174/0x580 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
The buggy address belongs to the object at ffff88803f978000
which belongs to the cache kmalloc-4k of size 4096
The buggy address is located 1584 bytes inside of
freed 4096-byte region [ffff88803f978000, ffff88803f979000)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x3f978
head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0x4fff00000000040(head|node=1|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 04fff00000000040 ffff88801ac42140 dead000000000122 0000000000000000
raw: 0000000000000000 0000000800040004 00000000f5000000 0000000000000000
head: 04fff00000000040 ffff88801ac42140 dead000000000122 0000000000000000
head: 0000000000000000 0000000800040004 00000000f5000000 0000000000000000
head: 04fff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd2820(GFP_ATOMIC|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 24, tgid 24 (kworker/u4:2), ts 105728970387, free_ts 29540875453
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x22d/0x280 mm/page_alloc.c:1853
prep_new_page mm/page_alloc.c:1861 [inline]
get_page_from_freelist+0x2593/0x2610 mm/page_alloc.c:3941
__alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5221
alloc_slab_page mm/slub.c:3278 [inline]
allocate_slab+0x77/0x660 mm/slub.c:3467
new_slab mm/slub.c:3525 [inline]
refill_objects+0x339/0x3d0 mm/slub.c:7272
refill_sheaf mm/slub.c:2816 [inline]
__pcs_replace_empty_main+0x321/0x720 mm/slub.c:4652
alloc_from_pcs mm/slub.c:4750 [inline]
slab_alloc_node mm/slub.c:4884 [inline]
__do_kmalloc_node mm/slub.c:5295 [inline]
__kmalloc_node_track_caller_noprof+0x572/0x7b0 mm/slub.c:5408
kmalloc_reserve net/core/skbuff.c:635 [inline]
__alloc_skb+0x2c1/0x7d0 net/core/skbuff.c:713
alloc_skb include/linux/skbuff.h:1385 [inline]
nsim_dev_trap_skb_build drivers/net/netdevsim/dev.c:819 [inline]
nsim_dev_trap_report drivers/net/netdevsim/dev.c:876 [inline]
nsim_dev_trap_report_work+0x29a/0xb90 drivers/net/netdevsim/dev.c:922
process_one_work kernel/workqueue.c:3314 [inline]
process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478
kthread+0x389/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
page last free pid 77 tgid 77 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
__free_pages_prepare mm/page_alloc.c:1397 [inline]
__free_frozen_pages+0xc1c/0xd30 mm/page_alloc.c:2938
vfree+0x1d1/0x2f0 mm/vmalloc.c:3472
delayed_vfree_work+0x55/0x80 mm/vmalloc.c:3392
process_one_work kernel/workqueue.c:3314 [inline]
process_scheduled_works+0xb5d/0x1860 kernel/workqueue.c:3397
worker_thread+0xa53/0xfc0 kernel/workqueue.c:3478
kthread+0x389/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Memory state around the buggy address:
ffff88803f978500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88803f978580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88803f978600: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88803f978680: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88803f978700: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
Reply: **Bug Analysis:**
The bug is a Use-After-Free (UAF) in `btrfs_put_root` when accessing the `root->reloc_root` pointer.
During a Btrfs balance operation (or relocation recovery), `btrfs_init_reloc_root` allocates a relocation root and assigns it to `root->reloc_root`, taking a reference. The relocation root is also added to a relocation list (`rc->reloc_roots`), which holds another reference.
If the relocation process encounters an error during `merge_reloc_roots`, it jumps to the error handling path and calls `free_reloc_roots`. This function iterates over the list and calls `__del_reloc_root`, which removes the roots from the list and drops the list's reference. However, due to a reference counting logic flaw in the error path, the `reloc_root` is prematurely freed while `root->reloc_root` still points to it.
Later, when the filesystem is unmounted (e.g., the process exits and drops the last mount reference), `close_ctree` is invoked. This calls `btrfs_drop_and_free_fs_root` on the filesystem roots. The cleanup code checks if `root->reloc_root` is set, and if so, it calls `btrfs_put_root(root->reloc_root)` to drop the reference. Since the object was already freed during the failed balance operation, this results in a Use-After-Free on the `struct btrfs_root` object.
**Security Impact:**
* **Exploitable:** Yes. The UAF occurs on `struct btrfs_root`, which is a massive and complex structure containing numerous spinlocks, list heads, and pointers. An attacker who can spray the heap and reclaim the freed `btrfs_root` object can easily achieve memory corruption, control-flow hijacking, and ultimately privilege escalation.
* **Denial of Service:** Yes. The UAF and subsequent memory corruption will reliably crash the kernel.
* **Unprivileged / User Namespace:** No. Triggering this bug requires either issuing the `BTRFS_IOC_BALANCE` ioctl or mounting a Btrfs filesystem. Both operations require `CAP_SYS_ADMIN` in the initial user namespace (Btrfs does not have the `FS_USERNS_MOUNT` flag, meaning it cannot be mounted inside an unprivileged user namespace).
* **Filesystem / Peripheral Trigger:** Yes. The bug can be triggered by mounting a maliciously crafted Btrfs image that forces the relocation recovery path to fail, making it relevant for scenarios involving untrusted USB drives or auto-mounting desktop environments.
|
| 6/2 |
2026/06/02 10:55 |
llm |
expert |
0m
Model:
Tokens:
input: 10510
output: 42
thoughts: 121
Thoughts: **Analyzing a KASAN Slab-Use-After-Free Report in btrfs**
Okay, so I've got a KASAN report pointing me towards a use-after-free issue within `btrfs_put_root`. It looks like the error is occurring in `fs/btrfs/disk-io.c` at line 1806, specifically within the `btrfs_put_root` function. The offset provided, `+0x2f/0x250`, suggests it's a specific instruction within the function, rather than an issue at the very beginning or end. The write of size 4 is also crucial; it strongly suggests the culprit is likely related to reference count management, most probably a decrement on `root->refs`, using `refcount_dec_and_test`.
My next step is clear: I need to dig into the `btrfs_put_root` function itself in `fs/btrfs/disk-io.c` and trace the execution path leading up to that line. The `refcount_dec_and_test` function is a known source of subtle race conditions in concurrent filesystems, and the use-after-free suggests the object might be freed prematurely, meaning there is a potential race here.
|
| 7/2 |
2026/06/02 10:56 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/disk-io.c Name:btrfs_put_root]
Results: map[SourceCode:1801: void btrfs_put_root(struct btrfs_root *root)
1802: {
1803: if (!root)
1804: return;
1805:
1806: if (refcount_dec_and_test(&root->refs)) {
1807: if (WARN_ON(!xa_empty(&root->inodes)))
1808: xa_destroy(&root->inodes);
1809: if (WARN_ON(!xa_empty(&root->delayed_nodes)))
1810: xa_destroy(&root->delayed_nodes);
1811: WARN_ON(test_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state));
1812: if (root->anon_dev)
1813: free_anon_bdev(root->anon_dev);
1814: free_root_extent_buffers(root);
1815: #ifdef CONFIG_BTRFS_DEBUG
1816: spin_lock(&root->fs_info->fs_roots_radix_lock);
1817: list_del_init(&root->leak_list);
1818: spin_unlock(&root->fs_info->fs_roots_radix_lock);
1819: #endif
1820: kfree(root);
1821: }
1822: }
SourceFile:fs/btrfs/disk-io.c]
|
| 8/2 |
2026/06/02 10:56 |
llm |
expert |
0m
Model:
Tokens:
input: 11351
output: 48
thoughts: 1173
Thoughts: **Analyzing a Btrfs UAF Bug**
Alright, let's break this down. It looks like I've got a Use-After-Free (UAF) situation in Btrfs, specifically concerning a `struct btrfs_root` object. The stack trace clearly shows the object (`root->refs`) being freed, then later accessed.
Initially, I see the object was allocated using `btrfs_alloc_root`, which seems to be part of the relocation process, more precisely during `btrfs_finish_one_ordered`. That allocation leads to a reloc root, allocated within the transaction system.
The interesting part is the deallocation. The backtrace points to `__del_reloc_root`, part of `free_reloc_roots`, `merge_reloc_roots` and culminating in `btrfs_ioctl_balance`. So, this `btrfs_root`, allocated during relocation, is being freed during a balance operation, which makes sense.
Then, there's the UAF. The stack traces point to a `btrfs_drop_and_free_fs_root` call within `close_ctree`. The `btrfs_put_root` call is triggered, at the beginning of the trace, causing the fatal error.
The key is in the timing and the object. A reloc root, which should be temporary, is being freed in `__del_reloc_root` as part of a balance, but some other part of the file system later tries to access it through `btrfs_drop_and_free_fs_root`.
It looks like the problem originates in either `__del_reloc_root` or `btrfs_drop_and_free_fs_root` when balancing the file system. Next step is looking at those specific functions, to try to pinpoint what went wrong!
|
| 9/2 |
2026/06/02 10:56 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/disk-io.c Name:btrfs_drop_and_free_fs_root]
Results: map[SourceCode:4138: /* Drop a fs root from the radix tree and free it. */
4139: void btrfs_drop_and_free_fs_root(struct btrfs_fs_info *fs_info,
4140: struct btrfs_root *root)
4141: {
4142: bool drop_ref = false;
4143:
4144: spin_lock(&fs_info->fs_roots_radix_lock);
4145: radix_tree_delete(&fs_info->fs_roots_radix,
4146: (unsigned long)btrfs_root_id(root));
4147: if (test_and_clear_bit(BTRFS_ROOT_IN_RADIX, &root->state))
4148: drop_ref = true;
4149: spin_unlock(&fs_info->fs_roots_radix_lock);
4150:
4151: if (unlikely(BTRFS_FS_ERROR(fs_info))) {
4152: ASSERT(root->log_root == NULL);
4153: if (root->reloc_root) {
4154: btrfs_put_root(root->reloc_root);
4155: root->reloc_root = NULL;
4156: }
4157: }
4158:
4159: if (drop_ref)
4160: btrfs_put_root(root);
4161: }
SourceFile:fs/btrfs/disk-io.c]
|
| 10/2 |
2026/06/02 10:56 |
llm |
expert |
0m
Model:
Tokens:
input: 13415
output: 41
thoughts: 187
Thoughts: **Analyzing a Btrfs Crash**
Okay, so the crash is happening right in either `btrfs_put_root(root->reloc_root)` or just `btrfs_put_root(root)`. The stack trace is pretty clear on that: `btrfs_put_root` and then `btrfs_drop_and_free_fs_root`, and it's pointing to the disk-io code. Specifically, line 4154, which is `btrfs_put_root(root->reloc_root);`. That strongly suggests that `root->reloc_root` is a dangling pointer, which is not good. Time to figure out *why*. I need to examine how that `reloc_root` is handled. Let's dig into `__del_reloc_root` in `fs/btrfs/relocation.c` to see when and how this is freed, and ensure there isn't a race or premature free going on.
|
| 11/2 |
2026/06/02 10:56 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:__del_reloc_root]
Results: map[SourceCode: 507: /*
508: * helper to delete the 'address of tree root -> reloc tree'
509: * mapping
510: */
511: static void __del_reloc_root(struct btrfs_root *root)
512: {
513: struct btrfs_fs_info *fs_info = root->fs_info;
514: struct rb_node *rb_node;
515: struct mapping_node AUTO_KFREE(node);
516: struct reloc_control *rc = fs_info->reloc_ctl;
517: bool put_ref = false;
518:
519: if (rc && root->node) {
520: spin_lock(&rc->reloc_root_tree.lock);
521: rb_node = rb_simple_search(&rc->reloc_root_tree.rb_root,
522: root->commit_root->start);
523: if (rb_node) {
524: node = rb_entry(rb_node, struct mapping_node, rb_node);
525: rb_erase(&node->rb_node, &rc->reloc_root_tree.rb_root);
526: RB_CLEAR_NODE(&node->rb_node);
527: }
528: spin_unlock(&rc->reloc_root_tree.lock);
529: ASSERT(!node || (struct btrfs_root *)node->data == root);
530: }
531:
532: /*
533: * We only put the reloc root here if it's on the list. There's a lot
534: * of places where the pattern is to splice the rc->reloc_roots, process
535: * the reloc roots, and then add the reloc root back onto
536: * rc->reloc_roots. If we call __del_reloc_root while it's off of the
537: * list we don't want the reference being dropped, because the guy
538: * messing with the list is in charge of the reference.
539: */
540: spin_lock(&fs_info->trans_lock);
541: if (!list_empty(&root->root_list)) {
542: put_ref = true;
543: list_del_init(&root->root_list);
544: }
545: spin_unlock(&fs_info->trans_lock);
546: if (put_ref)
547: btrfs_put_root(root);
548: }
SourceFile:fs/btrfs/relocation.c]
|
| 12/2 |
2026/06/02 10:56 |
llm |
expert |
0m
Model:
Tokens:
input: 14624
output: 63
thoughts: 74
Thoughts: **Analyzing Root Relocation in Btrfs**
Okay, so I'm looking at how Btrfs handles root relocation, specifically around the `__del_reloc_root` function. I'm seeing that it frees the root, using `btrfs_put_root(root)`. The thing that's snagging me is that the code doesn't explicitly clear `root->reloc_root` within the original root when the relocated root is released. That feels like a potential leak, or at the very least, a point of confusion down the line. I need to understand where and when this `root->reloc_root` pointer *is* cleared. That's the key question now. Let's trace the code and figure out the exact point of clearing this pointer, so I can ensure this code is correct, or identify the problem!
|
| 13/2 |
2026/06/02 10:56 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:fs/btrfs/relocation.c IncludeSnippetLines:0 Name:btrfs_root::reloc_root SourceTreePrefix:fs/btrfs/]
Results: map[References:[map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_drop_and_free_fs_root SourceFile:fs/btrfs/disk-io.c SourceLine:4153] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_drop_and_free_fs_root SourceFile:fs/btrfs/disk-io.c SourceLine:4154] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:btrfs_drop_and_free_fs_root SourceFile:fs/btrfs/disk-io.c SourceLine:4155] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_init_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:726] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_init_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:727] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:btrfs_init_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:757] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_qgroup_trace_subtree_after_cow SourceFile:fs/btrfs/qgroup.c SourceLine:4871] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:btrfs_recover_relocation SourceFile:fs/btrfs/relocation.c SourceLine:5609] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_reloc_post_snapshot SourceFile:fs/btrfs/relocation.c SourceLine:5833] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:btrfs_reloc_post_snapshot SourceFile:fs/btrfs/relocation.c SourceLine:5844] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_reloc_pre_snapshot SourceFile:fs/btrfs/relocation.c SourceLine:5786] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_should_ignore_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:288] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_update_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:775] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:clean_dirty_subvols SourceFile:fs/btrfs/relocation.c SourceLine:1463] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:clean_dirty_subvols SourceFile:fs/btrfs/relocation.c SourceLine:1466] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:have_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:272] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:insert_dirty_subvol SourceFile:fs/btrfs/relocation.c SourceLine:1427] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:merge_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:1527] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:merge_reloc_roots SourceFile:fs/btrfs/relocation.c SourceLine:1847] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:merge_reloc_roots SourceFile:fs/btrfs/relocation.c SourceLine:1865] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:merge_reloc_roots SourceFile:fs/btrfs/relocation.c SourceLine:1866] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:need_reserve_reloc_root SourceFile:fs/btrfs/transaction.c SourceLine:563] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1732] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1733] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1734] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1734] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1734] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1734] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:record_reloc_root_in_trans SourceFile:fs/btrfs/relocation.c SourceLine:1949] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:relocate_tree_block SourceFile:fs/btrfs/relocation.c SourceLine:2519] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:relocate_tree_block SourceFile:fs/btrfs/relocation.c SourceLine:2523] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:select_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:2011]] TruncatedOutput:false]
|
| 14/2 |
2026/06/02 10:56 |
llm |
expert |
0m
Model:
Tokens:
input: 16754
output: 40
thoughts: 33
Thoughts: Okay, here's my interpretation of those functions, presented as if I were considering them:
**Analyzing Btrfs Recovery Functions**
Alright, let's break this down. My focus right now is on these core Btrfs recovery functions: `merge_reloc_roots`, `clean_dirty_subvols`, and `btrfs_recover_relocation`. I'm immediately interested in how they interrelate and what specific scenarios they're designed to handle.
`merge_reloc_roots`... that sounds like it deals with integrating relocated data. Potentially after a failed device, maybe a scrub, or perhaps a balance operation has gone awry. I'm imagining it's taking multiple sets of root trees and combining them, ensuring data consistency across them. I'll need to dig deeper into the specifics: the implementation details, how it handles potential conflicts, and how it ensures atomic updates to the filesystem metadata. This likely gets tricky if there's any ongoing IO.
`clean_dirty_subvols`... this one's a bit more straightforward, although it is likely still complex. This sounds like it's addressing dirty subvolumes. I'm assuming we're talking about subvolumes that have been modified but haven't been fully flushed to disk. It's probably responsible for bringing these subvolumes to a consistent state, perhaps after a crash or power loss. I need to understand its strategy for identifying which subvolumes are "dirty," its methods for ensuring data integrity during the clean-up process, and how it interacts with the journaling system. What are the corner cases here, such as if there's no space on the device to write the clean data? I need to think about that, too.
Finally, `btrfs_recover_relocation`... this is the most intriguing. The word "relocation" is an important keyword. This feels like the overarching process for data recovery. Perhaps it's orchestrating the interaction between the other functions. Perhaps it's involved in scenarios where we have a corruption event and a section of the filesystem has to be moved. It's likely coordinating the process, verifying data integrity, and perhaps handling rollback in case things go wrong. I definitely need to understand the control flow here, especially how it handles potential errors. This probably touches almost all layers of the Btrfs stack, but especially on-disk metadata and transaction management. This is the crucial function to understanding the core recovery process.
|
| 15/2 |
2026/06/02 10:56 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:merge_reloc_roots]
Results: map[SourceCode:1806: static noinline_for_stack
1807: void merge_reloc_roots(struct reloc_control *rc)
1808: {
1809: struct btrfs_fs_info *fs_info = rc->extent_root->fs_info;
1810: struct btrfs_root *root;
1811: struct btrfs_root *reloc_root;
1812: LIST_HEAD(reloc_roots);
1813: int found = 0;
1814: int ret = 0;
1815: again:
1816: root = rc->extent_root;
1817:
1818: /*
1819: * this serializes us with btrfs_record_root_in_transaction,
1820: * we have to make sure nobody is in the middle of
1821: * adding their roots to the list while we are
1822: * doing this splice
1823: */
1824: mutex_lock(&fs_info->reloc_mutex);
1825: list_splice_init(&rc->reloc_roots, &reloc_roots);
1826: mutex_unlock(&fs_info->reloc_mutex);
1827:
1828: while (!list_empty(&reloc_roots)) {
1829: found = 1;
1830: reloc_root = list_first_entry(&reloc_roots, struct btrfs_root, root_list);
1831:
1832: root = btrfs_get_fs_root(fs_info, reloc_root->root_key.offset,
1833: false);
1834: if (btrfs_root_refs(&reloc_root->root_item) > 0) {
1835: if (WARN_ON(IS_ERR(root))) {
1836: /*
1837: * For recovery we read the fs roots on mount,
1838: * and if we didn't find the root then we marked
1839: * the reloc root as a garbage root. For normal
1840: * relocation obviously the root should exist in
1841: * memory. However there's no reason we can't
1842: * handle the error properly here just in case.
1843: */
1844: ret = PTR_ERR(root);
1845: goto out;
1846: }
1847: if (WARN_ON(root->reloc_root != reloc_root)) {
1848: /*
1849: * This can happen if on-disk metadata has some
1850: * corruption, e.g. bad reloc tree key offset.
1851: */
1852: ret = -EINVAL;
1853: goto out;
1854: }
1855: ret = merge_reloc_root(rc, root);
1856: btrfs_put_root(root);
1857: if (ret) {
1858: if (list_empty(&reloc_root->root_list))
1859: list_add_tail(&reloc_root->root_list,
1860: &reloc_roots);
1861: goto out;
1862: }
1863: } else {
1864: if (!IS_ERR(root)) {
1865: if (root->reloc_root == reloc_root) {
1866: root->reloc_root = NULL;
1867: btrfs_put_root(reloc_root);
1868: }
1869: clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE,
1870: &root->state);
1871: btrfs_put_root(root);
1872: }
1873:
1874: list_del_init(&reloc_root->root_list);
1875: /* Don't forget to queue this reloc root for cleanup */
1876: list_add_tail(&reloc_root->reloc_dirty_list,
1877: &rc->dirty_subvol_roots);
1878: }
1879: }
1880:
1881: if (found) {
1882: found = 0;
1883: goto again;
1884: }
1885: out:
1886: if (ret) {
1887: btrfs_handle_fs_error(fs_info, ret, NULL);
1888: free_reloc_roots(&reloc_roots);
1889:
1890: /* new reloc root may be added */
1891: mutex_lock(&fs_info->reloc_mutex);
1892: list_splice_init(&rc->reloc_roots, &reloc_roots);
1893: mutex_unlock(&fs_info->reloc_mutex);
1894: free_reloc_roots(&reloc_roots);
1895: }
1896:
1897: /*
1898: * We used to have
1899: *
1900: * BUG_ON(!RB_EMPTY_ROOT(&rc->reloc_root_tree.rb_root));
1901: *
1902: * here, but it's wrong. If we fail to start the transaction in
1903: * prepare_to_merge() we will have only 0 ref reloc roots, none of which
1904: * have actually been removed from the reloc_root_tree rb tree. This is
1905: * fine because we're bailing here, and we hold a reference on the root
1906: * for the list that holds it, so these roots will be cleaned up when we
1907: * do the reloc_dirty_list afterwards. Meanwhile the root->reloc_root
1908: * will be cleaned up on unmount.
1909: *
1910: * The remaining nodes will be cleaned up by free_reloc_control.
1911: */
1912: }
SourceFile:fs/btrfs/relocation.c]
|
| 16/2 |
2026/06/02 10:56 |
llm |
expert |
0m
Model:
Tokens:
input: 19060
output: 40
thoughts: 187
Thoughts: **Analyzing Relocation Root Handling in Btrfs**
Okay, so I'm looking at this `merge_reloc_roots` function. Seems straightforward enough at first glance, but I see a potential issue in how it handles error conditions. Specifically, if the function bails out early, either due to a generic failure like an invalid argument (`-EINVAL`), a `PTR_ERR` from a root pointer, or an issue within the `merge_reloc_root` call itself, it jumps to the `out` label.
The problem? While the error path correctly calls `free_reloc_roots(&reloc_roots)` which then cleans up the roots by removing them from the list and calling `btrfs_put_root`, it **doesn't** seem to clear `root->reloc_root` after this cleanup. That's a classic memory management oversight and could lead to some nasty bugs down the line. I need to dig deeper into the code of `free_reloc_roots` to confirm my suspicions and pinpoint exactly where this `root->reloc_root` assignment is being missed. This could be a subtle but critical issue that could lead to data corruption in certain scenarios.
|
| 17/2 |
2026/06/02 10:56 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:free_reloc_roots]
Results: map[SourceCode:1797: static noinline_for_stack
1798: void free_reloc_roots(struct list_head *list)
1799: {
1800: struct btrfs_root *reloc_root, *tmp;
1801:
1802: list_for_each_entry_safe(reloc_root, tmp, list, root_list)
1803: __del_reloc_root(reloc_root);
1804: }
SourceFile:fs/btrfs/relocation.c]
|
| 18/2 |
2026/06/02 10:56 |
llm |
expert |
0m
Model:
Tokens:
input: 19707
output: 44
thoughts: 163
Thoughts: **UAF Alert! A Relocation Root Reference Mystery**
Okay, so `__del_reloc_root` seems straightforward enough. It just cleans up the bookkeeping: removes the relocation root from the `rc->reloc_root_tree` and `root_list` and drops its reference. Fine. But now I'm having a sinking feeling... `root->reloc_root` *still* points to it, right?
If that's the case, we're in trouble. Because when `btrfs_drop_and_free_fs_root` runs, it's going to call `btrfs_put_root(root->reloc_root)`. That's going to trigger a `btrfs_put_root` on a pointer that's been freed... a Use-After-Free! Oh, crap.
Okay, let's back up. Where *is* the reference to `root->reloc_root` held? I can't immediately see it. Does setting `root->reloc_root` actually increment the reference count? If it doesn't, that's a HUGE problem. I better check `btrfs_init_reloc_root` to verify. I need to be sure. I *really* need to be sure.
|
| 19/2 |
2026/06/02 10:56 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:btrfs_init_reloc_root]
Results: map[SourceCode: 691: /*
692: * create reloc tree for a given fs tree. reloc tree is just a
693: * snapshot of the fs tree with special root objectid.
694: *
695: * The reloc_root comes out of here with two references, one for
696: * root->reloc_root, and another for being on the rc->reloc_roots list.
697: */
698: int btrfs_init_reloc_root(struct btrfs_trans_handle *trans,
699: struct btrfs_root *root)
700: {
701: struct btrfs_fs_info *fs_info = root->fs_info;
702: struct btrfs_root *reloc_root;
703: struct reloc_control *rc = fs_info->reloc_ctl;
704: struct btrfs_block_rsv *rsv;
705: int clear_rsv = 0;
706: int ret;
707:
708: if (!rc)
709: return 0;
710:
711: /*
712: * The subvolume has reloc tree but the swap is finished, no need to
713: * create/update the dead reloc tree
714: */
715: if (reloc_root_is_dead(root))
716: return 0;
717:
718: /*
719: * This is subtle but important. We do not do
720: * record_root_in_transaction for reloc roots, instead we record their
721: * corresponding fs root, and then here we update the last trans for the
722: * reloc root. This means that we have to do this for the entire life
723: * of the reloc root, regardless of which stage of the relocation we are
724: * in.
725: */
726: if (root->reloc_root) {
727: reloc_root = root->reloc_root;
728: btrfs_set_root_last_trans(reloc_root, trans->transid);
729: return 0;
730: }
731:
732: /*
733: * We are merging reloc roots, we do not need new reloc trees. Also
734: * reloc trees never need their own reloc tree.
735: */
736: if (!rc->create_reloc_tree || btrfs_root_id(root) == BTRFS_TREE_RELOC_OBJECTID)
737: return 0;
738:
739: if (!trans->reloc_reserved) {
740: rsv = trans->block_rsv;
741: trans->block_rsv = rc->block_rsv;
742: clear_rsv = 1;
743: }
744: reloc_root = create_reloc_root(trans, root, btrfs_root_id(root));
745: if (clear_rsv)
746: trans->block_rsv = rsv;
747: if (IS_ERR(reloc_root))
748: return PTR_ERR(reloc_root);
749:
750: ret = __add_reloc_root(reloc_root);
751: ASSERT(ret != -EEXIST);
752: if (ret) {
753: /* Pairs with create_reloc_root */
754: btrfs_put_root(reloc_root);
755: return ret;
756: }
757: root->reloc_root = btrfs_grab_root(reloc_root);
758: return 0;
759: }
SourceFile:fs/btrfs/relocation.c]
|
| 20/2 |
2026/06/02 10:56 |
llm |
expert |
0m
Model:
Tokens:
input: 21319
output: 40
thoughts: 181
Thoughts: **Unraveling a Btrfs Relocation Puzzle**
Okay, so I'm digging into this Btrfs relocation logic, and I've hit a snag. That line, `root->reloc_root = btrfs_grab_root(reloc_root);`, confirms that `root->reloc_root` *does* maintain a reference to the relocated root. That's good, that's what I expect. But then, why the use-after-free (UAF)? This is the crux.
If `root->reloc_root` is indeed holding a reference, then the corresponding `btrfs_put_root(root->reloc_root)` should, logically, simply decrement that reference count. It should be a clean operation.
Wait a second... if `root->reloc_root` has a reference, wouldn't `__del_reloc_root` be dropping the list reference? That's probably true.
So, the issue isn't that `root->reloc_root` is being cleared *without* dropping the reference, because that would be a leak, right? Hmm, maybe it's the other way around: What if `root->reloc_root` isn't cleared, but the reference itself *is* dropped prematurely, somehow?
Time to revisit `merge_reloc_roots`, and see what that's actually doing. It feels like there's a reference count manipulation bug buried somewhere. Gotta trace the ref counts and logic.
|
| 21/2 |
2026/06/02 10:56 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:merge_reloc_roots]
Results: map[SourceCode:1806: static noinline_for_stack
1807: void merge_reloc_roots(struct reloc_control *rc)
1808: {
1809: struct btrfs_fs_info *fs_info = rc->extent_root->fs_info;
1810: struct btrfs_root *root;
1811: struct btrfs_root *reloc_root;
1812: LIST_HEAD(reloc_roots);
1813: int found = 0;
1814: int ret = 0;
1815: again:
1816: root = rc->extent_root;
1817:
1818: /*
1819: * this serializes us with btrfs_record_root_in_transaction,
1820: * we have to make sure nobody is in the middle of
1821: * adding their roots to the list while we are
1822: * doing this splice
1823: */
1824: mutex_lock(&fs_info->reloc_mutex);
1825: list_splice_init(&rc->reloc_roots, &reloc_roots);
1826: mutex_unlock(&fs_info->reloc_mutex);
1827:
1828: while (!list_empty(&reloc_roots)) {
1829: found = 1;
1830: reloc_root = list_first_entry(&reloc_roots, struct btrfs_root, root_list);
1831:
1832: root = btrfs_get_fs_root(fs_info, reloc_root->root_key.offset,
1833: false);
1834: if (btrfs_root_refs(&reloc_root->root_item) > 0) {
1835: if (WARN_ON(IS_ERR(root))) {
1836: /*
1837: * For recovery we read the fs roots on mount,
1838: * and if we didn't find the root then we marked
1839: * the reloc root as a garbage root. For normal
1840: * relocation obviously the root should exist in
1841: * memory. However there's no reason we can't
1842: * handle the error properly here just in case.
1843: */
1844: ret = PTR_ERR(root);
1845: goto out;
1846: }
1847: if (WARN_ON(root->reloc_root != reloc_root)) {
1848: /*
1849: * This can happen if on-disk metadata has some
1850: * corruption, e.g. bad reloc tree key offset.
1851: */
1852: ret = -EINVAL;
1853: goto out;
1854: }
1855: ret = merge_reloc_root(rc, root);
1856: btrfs_put_root(root);
1857: if (ret) {
1858: if (list_empty(&reloc_root->root_list))
1859: list_add_tail(&reloc_root->root_list,
1860: &reloc_roots);
1861: goto out;
1862: }
1863: } else {
1864: if (!IS_ERR(root)) {
1865: if (root->reloc_root == reloc_root) {
1866: root->reloc_root = NULL;
1867: btrfs_put_root(reloc_root);
1868: }
1869: clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE,
1870: &root->state);
1871: btrfs_put_root(root);
1872: }
1873:
1874: list_del_init(&reloc_root->root_list);
1875: /* Don't forget to queue this reloc root for cleanup */
1876: list_add_tail(&reloc_root->reloc_dirty_list,
1877: &rc->dirty_subvol_roots);
1878: }
1879: }
1880:
1881: if (found) {
1882: found = 0;
1883: goto again;
1884: }
1885: out:
1886: if (ret) {
1887: btrfs_handle_fs_error(fs_info, ret, NULL);
1888: free_reloc_roots(&reloc_roots);
1889:
1890: /* new reloc root may be added */
1891: mutex_lock(&fs_info->reloc_mutex);
1892: list_splice_init(&rc->reloc_roots, &reloc_roots);
1893: mutex_unlock(&fs_info->reloc_mutex);
1894: free_reloc_roots(&reloc_roots);
1895: }
1896:
1897: /*
1898: * We used to have
1899: *
1900: * BUG_ON(!RB_EMPTY_ROOT(&rc->reloc_root_tree.rb_root));
1901: *
1902: * here, but it's wrong. If we fail to start the transaction in
1903: * prepare_to_merge() we will have only 0 ref reloc roots, none of which
1904: * have actually been removed from the reloc_root_tree rb tree. This is
1905: * fine because we're bailing here, and we hold a reference on the root
1906: * for the list that holds it, so these roots will be cleaned up when we
1907: * do the reloc_dirty_list afterwards. Meanwhile the root->reloc_root
1908: * will be cleaned up on unmount.
1909: *
1910: * The remaining nodes will be cleaned up by free_reloc_control.
1911: */
1912: }
SourceFile:fs/btrfs/relocation.c]
|
| 22/2 |
2026/06/02 10:56 |
llm |
expert |
0m
Model:
Tokens:
input: 23579
output: 41
thoughts: 223
Thoughts: **Investigating a Potential Reference Counting Bug in Btrfs Relocation**
Okay, let's circle back to `free_reloc_roots`. I need to really understand this. This code snippet looks familiar:
```c
static noinline_for_stack
void free_reloc_roots(struct list_head *list)
{
struct btrfs_root *reloc_root, *tmp;
list_for_each_entry_safe(reloc_root, tmp, list, root_list)
__del_reloc_root(reloc_root);
}
```
Right, if `merge_reloc_roots` fails, it calls this to clean up, using `reloc_roots`. `__del_reloc_root` is supposed to drop the reference for everything in the list, but something doesn't feel right. My immediate concern is that `root->reloc_root` *should* still point to `reloc_root` and *still* hold a reference, after `__del_reloc_root` has executed. If that's true, why is it being freed? That's a classic race condition and memory management issue! If `root->reloc_root` is still pointing to `reloc_root` then we can't free `reloc_root` yet. We might be using freed memory, or corrupting structures. This deserves a very close look. Let me examine `__del_reloc_root` again; I need to understand exactly what's going on there.
|
| 23/2 |
2026/06/02 10:56 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:__del_reloc_root]
Results: map[SourceCode: 507: /*
508: * helper to delete the 'address of tree root -> reloc tree'
509: * mapping
510: */
511: static void __del_reloc_root(struct btrfs_root *root)
512: {
513: struct btrfs_fs_info *fs_info = root->fs_info;
514: struct rb_node *rb_node;
515: struct mapping_node AUTO_KFREE(node);
516: struct reloc_control *rc = fs_info->reloc_ctl;
517: bool put_ref = false;
518:
519: if (rc && root->node) {
520: spin_lock(&rc->reloc_root_tree.lock);
521: rb_node = rb_simple_search(&rc->reloc_root_tree.rb_root,
522: root->commit_root->start);
523: if (rb_node) {
524: node = rb_entry(rb_node, struct mapping_node, rb_node);
525: rb_erase(&node->rb_node, &rc->reloc_root_tree.rb_root);
526: RB_CLEAR_NODE(&node->rb_node);
527: }
528: spin_unlock(&rc->reloc_root_tree.lock);
529: ASSERT(!node || (struct btrfs_root *)node->data == root);
530: }
531:
532: /*
533: * We only put the reloc root here if it's on the list. There's a lot
534: * of places where the pattern is to splice the rc->reloc_roots, process
535: * the reloc roots, and then add the reloc root back onto
536: * rc->reloc_roots. If we call __del_reloc_root while it's off of the
537: * list we don't want the reference being dropped, because the guy
538: * messing with the list is in charge of the reference.
539: */
540: spin_lock(&fs_info->trans_lock);
541: if (!list_empty(&root->root_list)) {
542: put_ref = true;
543: list_del_init(&root->root_list);
544: }
545: spin_unlock(&fs_info->trans_lock);
546: if (put_ref)
547: btrfs_put_root(root);
548: }
SourceFile:fs/btrfs/relocation.c]
|
| 24/2 |
2026/06/02 10:56 |
llm |
expert |
0m
Model:
Tokens:
input: 24945
output: 41
thoughts: 473
Thoughts: **Debugging a Btrfs Relocation Root Issue**
Okay, so I'm looking at this code and trying to figure out why a `btrfs_root` object is getting freed prematurely. The stack trace from KASAN is screaming that `__del_reloc_root` is the culprit, specifically when it calls `btrfs_put_root(root)`. The logic *should* be that if `root->reloc_root` has a reference to something, then this `btrfs_put_root` call should *only* drop a list reference, not the last one. But the KASAN output clearly shows that the object itself is being freed right then and there.
That's the problem; the `btrfs_put_root` call in `__del_reloc_root` is dropping the *last* reference. But wait, `root->reloc_root` still points to this freed memory! That makes no sense. The code's supposed to maintain that reference. How can `root->reloc_root` possibly point to an object without holding a valid reference to it? This is a textbook use-after-free scenario. This is definitely going to lead to corruption.
I need to trace the flow, and what's calling that function. I should check `clean_dirty_subvols` to see if there is something amiss there.
|
| 25/2 |
2026/06/02 10:57 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:clean_dirty_subvols]
Results: map[SourceCode:1452: static int clean_dirty_subvols(struct reloc_control *rc)
1453: {
1454: struct btrfs_root *root;
1455: struct btrfs_root *next;
1456: int ret = 0;
1457: int ret2;
1458:
1459: list_for_each_entry_safe(root, next, &rc->dirty_subvol_roots,
1460: reloc_dirty_list) {
1461: if (btrfs_root_id(root) != BTRFS_TREE_RELOC_OBJECTID) {
1462: /* Merged subvolume, cleanup its reloc root */
1463: struct btrfs_root *reloc_root = root->reloc_root;
1464:
1465: list_del_init(&root->reloc_dirty_list);
1466: root->reloc_root = NULL;
1467: /*
1468: * Need barrier to ensure clear_bit() only happens after
1469: * root->reloc_root = NULL. Pairs with have_reloc_root.
1470: */
1471: smp_wmb();
1472: clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state);
1473: if (reloc_root) {
1474: /*
1475: * btrfs_drop_snapshot drops our ref we hold for
1476: * ->reloc_root. If it fails however we must
1477: * drop the ref ourselves.
1478: */
1479: ret2 = btrfs_drop_snapshot(reloc_root, false, true);
1480: if (ret2 < 0) {
1481: btrfs_put_root(reloc_root);
1482: if (!ret)
1483: ret = ret2;
1484: }
1485: }
1486: btrfs_put_root(root);
1487: } else {
1488: /* Orphan reloc tree, just clean it up */
1489: ret2 = btrfs_drop_snapshot(root, false, true);
1490: if (ret2 < 0) {
1491: btrfs_put_root(root);
1492: if (!ret)
1493: ret = ret2;
1494: }
1495: }
1496: }
1497: return ret;
1498: }
SourceFile:fs/btrfs/relocation.c]
|
| 26/2 |
2026/06/02 10:57 |
llm |
expert |
0m
Model:
Tokens:
input: 26510
output: 44
thoughts: 734
Thoughts: **Analyzing Relocation Root Handling**
Okay, so I'm looking at this `merge_reloc_roots` function and something's bugging me. Specifically, if `merge_reloc_roots` returns a non-zero value, meaning it failed, the code goes to `out`, and calls `free_reloc_roots(&reloc_roots)`. This calls `__del_reloc_root`, but... wait a second. If the merge fails, doesn't that mean `root->reloc_root` isn't necessarily cleared? I need to double-check this flow. Let's trace it again.
Okay, here's the relevant section from the code, I'll go through it step-by-step.
First, a `root` is gotten.
If the number of references of the `reloc_root` is greater than 0, then we do the following: If `merge_reloc_root(rc, root)` fails, it jumps to `out`. At `out`, it's clear: `ret` indicates a failure. The error is handled, and then `free_reloc_roots` is called. That, in turn, calls `__del_reloc_root`. But `__del_reloc_root` drops the list reference.
Now, if `merge_reloc_root` succeeds, then the `root` is put, and the `reloc_root`'s list is deleted.
Otherwise, if `merge_reloc_root` fails, then the original `reloc_root` is *not* cleaned up. This would explain where the `root->reloc_root` reference went.
But if `root->reloc_root` holds a reference, why does `__del_reloc_root` drop the *last* reference? It seems like it shouldn't. Something's wrong here. The call to `free_reloc_roots` is potentially dropping a valid reference. Where did the `root->reloc_root` reference disappear to? It seems like this might be related to how these relocation roots are initialized. I need to go back and check `btrfs_init_reloc_root` to see how that's handled. This looks like a potential double-free or reference count issue, likely stemming from improper reference management when a merge fails. Time to dig deeper into the initialization path.
|
| 27/2 |
2026/06/02 10:57 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:btrfs_init_reloc_root]
Results: map[SourceCode: 691: /*
692: * create reloc tree for a given fs tree. reloc tree is just a
693: * snapshot of the fs tree with special root objectid.
694: *
695: * The reloc_root comes out of here with two references, one for
696: * root->reloc_root, and another for being on the rc->reloc_roots list.
697: */
698: int btrfs_init_reloc_root(struct btrfs_trans_handle *trans,
699: struct btrfs_root *root)
700: {
701: struct btrfs_fs_info *fs_info = root->fs_info;
702: struct btrfs_root *reloc_root;
703: struct reloc_control *rc = fs_info->reloc_ctl;
704: struct btrfs_block_rsv *rsv;
705: int clear_rsv = 0;
706: int ret;
707:
708: if (!rc)
709: return 0;
710:
711: /*
712: * The subvolume has reloc tree but the swap is finished, no need to
713: * create/update the dead reloc tree
714: */
715: if (reloc_root_is_dead(root))
716: return 0;
717:
718: /*
719: * This is subtle but important. We do not do
720: * record_root_in_transaction for reloc roots, instead we record their
721: * corresponding fs root, and then here we update the last trans for the
722: * reloc root. This means that we have to do this for the entire life
723: * of the reloc root, regardless of which stage of the relocation we are
724: * in.
725: */
726: if (root->reloc_root) {
727: reloc_root = root->reloc_root;
728: btrfs_set_root_last_trans(reloc_root, trans->transid);
729: return 0;
730: }
731:
732: /*
733: * We are merging reloc roots, we do not need new reloc trees. Also
734: * reloc trees never need their own reloc tree.
735: */
736: if (!rc->create_reloc_tree || btrfs_root_id(root) == BTRFS_TREE_RELOC_OBJECTID)
737: return 0;
738:
739: if (!trans->reloc_reserved) {
740: rsv = trans->block_rsv;
741: trans->block_rsv = rc->block_rsv;
742: clear_rsv = 1;
743: }
744: reloc_root = create_reloc_root(trans, root, btrfs_root_id(root));
745: if (clear_rsv)
746: trans->block_rsv = rsv;
747: if (IS_ERR(reloc_root))
748: return PTR_ERR(reloc_root);
749:
750: ret = __add_reloc_root(reloc_root);
751: ASSERT(ret != -EEXIST);
752: if (ret) {
753: /* Pairs with create_reloc_root */
754: btrfs_put_root(reloc_root);
755: return ret;
756: }
757: root->reloc_root = btrfs_grab_root(reloc_root);
758: return 0;
759: }
SourceFile:fs/btrfs/relocation.c]
|
| 28/2 |
2026/06/02 10:57 |
llm |
expert |
0m
Model:
Tokens:
input: 28934
output: 41
thoughts: 42
Thoughts: Okay, let's break this down.
**Understanding Root Reference Counting in Relocation Management**
Hold on a second... `create_reloc_root` creates a new relocation root, and from what I recall, it returns this root with an initial reference count of 1. That makes sense, a newly created object inherently has one user, itself. Now, the next piece is where I need to clarify my mental model. `__add_reloc_root` enters that root into the relocation root list. My primary concern is, *does* this list entry effectively add another reference to the root? In other words, is the list managing its own reference to the root, so the root's refcount needs to be incremented, or does the list simply *hold* a pointer, assuming the original caller still maintains the sole reference? It's a subtle but critical distinction. Improper handling here would result in either double-frees or memory leaks, depending on the implementation details elsewhere. I need to review the `__add_reloc_root` function's source or documentation to ensure the refcount is managed correctly; otherwise, I'll need to add a manual increment.
|
| 29/2 |
2026/06/02 10:57 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:__add_reloc_root]
Results: map[SourceCode: 476: /*
477: * helper to add 'address of tree root -> reloc tree' mapping
478: */
479: static int __add_reloc_root(struct btrfs_root *root)
480: {
481: struct btrfs_fs_info *fs_info = root->fs_info;
482: struct rb_node *rb_node;
483: struct mapping_node *node;
484: struct reloc_control *rc = fs_info->reloc_ctl;
485:
486: node = kmalloc_obj(*node, GFP_NOFS);
487: if (!node)
488: return -ENOMEM;
489:
490: node->bytenr = root->commit_root->start;
491: node->data = root;
492:
493: spin_lock(&rc->reloc_root_tree.lock);
494: rb_node = rb_simple_insert(&rc->reloc_root_tree.rb_root, &node->simple_node);
495: spin_unlock(&rc->reloc_root_tree.lock);
496: if (rb_node) {
497: btrfs_err(fs_info,
498: "Duplicate root found for start=%llu while inserting into relocation tree",
499: node->bytenr);
500: return -EEXIST;
501: }
502:
503: list_add_tail(&root->root_list, &rc->reloc_roots);
504: return 0;
505: }
SourceFile:fs/btrfs/relocation.c]
|
| 30/2 |
2026/06/02 10:57 |
llm |
expert |
0m
Model:
Tokens:
input: 29769
output: 42
thoughts: 501
Thoughts: **Analyzing Relocation Root Refcounting**
Okay, so let's break down how this relocation root refcounting works. My initial thought was that `__add_reloc_root` was taking a reference, but no, it's just adding the root to the `reloc_roots` and `reloc_root_tree` structures.
Then `btrfs_init_reloc_root` comes along. It calls `__add_reloc_root`, checks for an error, and then *grabs* a root: `root->reloc_root = btrfs_grab_root(reloc_root);`. Hold on, `create_reloc_root` *returns* the root with a refcount of 1! `__add_reloc_root` adds the root to the list and then the `btrfs_grab_root` increments the refcount. So, we end up with a refcount of 2.
The comment confirms this: "The reloc_root comes out of here with two references, one for `root->reloc_root`, and another for being on the `rc->reloc_roots` list." Okay, so the initial reference is the "list reference" and the second one is the `root->reloc_root` reference. Got it.
Now, let's look at the deletion side with `__del_reloc_root`. It does a list removal:
```c
spin_lock(&fs_info->trans_lock);
if (!list_empty(&root->root_list)) {
put_ref = true;
list_del_init(&root->root_list);
}
spin_unlock(&fs_info->trans_lock);
if (put_ref)
btrfs_put_root(root);
```
So, the code drops the "list reference", which means if `root->reloc_root` is still set, the refcount should now be 1, because that `btrfs_grab_root` we saw earlier gave us a second reference. But then it drops to zero? That shouldn't happen! There should be a valid reference. This deserves a closer look at `btrfs_recover_relocation` to see where the reference is dropped.
|
| 31/2 |
2026/06/02 10:57 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:btrfs_recover_relocation]
Results: map[SourceCode:5473: /*
5474: * recover relocation interrupted by system crash.
5475: *
5476: * this function resumes merging reloc trees with corresponding fs trees.
5477: * this is important for keeping the sharing of tree blocks
5478: */
5479: int btrfs_recover_relocation(struct btrfs_fs_info *fs_info)
5480: {
5481: LIST_HEAD(reloc_roots);
5482: struct btrfs_key key;
5483: struct btrfs_root *fs_root;
5484: struct btrfs_root *reloc_root;
5485: struct btrfs_path *path;
5486: struct extent_buffer *leaf;
5487: struct reloc_control *rc = NULL;
5488: struct btrfs_trans_handle *trans;
5489: int ret2;
5490: int ret = 0;
5491:
5492: path = btrfs_alloc_path();
5493: if (!path)
5494: return -ENOMEM;
5495: path->reada = READA_BACK;
5496:
5497: key.objectid = BTRFS_TREE_RELOC_OBJECTID;
5498: key.type = BTRFS_ROOT_ITEM_KEY;
5499: key.offset = (u64)-1;
5500:
5501: while (1) {
5502: ret = btrfs_search_slot(NULL, fs_info->tree_root, &key,
5503: path, 0, 0);
5504: if (ret < 0)
5505: goto out;
5506: if (ret > 0) {
5507: if (path->slots[0] == 0)
5508: break;
5509: path->slots[0]--;
5510: }
5511: ret = 0;
5512: leaf = path->nodes[0];
5513: btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
5514: btrfs_release_path(path);
5515:
5516: if (key.objectid != BTRFS_TREE_RELOC_OBJECTID ||
5517: key.type != BTRFS_ROOT_ITEM_KEY)
5518: break;
5519:
5520: reloc_root = btrfs_read_tree_root(fs_info->tree_root, &key);
5521: if (IS_ERR(reloc_root)) {
5522: ret = PTR_ERR(reloc_root);
5523: goto out;
5524: }
5525:
5526: set_bit(BTRFS_ROOT_SHAREABLE, &reloc_root->state);
5527: list_add(&reloc_root->root_list, &reloc_roots);
5528:
5529: if (btrfs_root_refs(&reloc_root->root_item) > 0) {
5530: fs_root = btrfs_get_fs_root(fs_info,
5531: reloc_root->root_key.offset, false);
5532: if (IS_ERR(fs_root)) {
5533: ret = PTR_ERR(fs_root);
5534: if (ret != -ENOENT)
5535: goto out;
5536: ret = mark_garbage_root(reloc_root);
5537: if (ret < 0)
5538: goto out;
5539: ret = 0;
5540: } else {
5541: btrfs_put_root(fs_root);
5542: }
5543: }
5544:
5545: if (key.offset == 0)
5546: break;
5547:
5548: key.offset--;
5549: }
5550: btrfs_release_path(path);
5551:
5552: if (list_empty(&reloc_roots))
5553: goto out;
5554:
5555: rc = alloc_reloc_control(fs_info);
5556: if (!rc) {
5557: ret = -ENOMEM;
5558: goto out;
5559: }
5560:
5561: rc->extent_root = btrfs_extent_root(fs_info, 0);
5562: if (unlikely(!rc->extent_root)) {
5563: btrfs_err(fs_info, "missing extent root for extent at bytenr 0");
5564: ret = -EUCLEAN;
5565: goto out;
5566: }
5567:
5568: ret = reloc_chunk_start(fs_info);
5569: if (ret < 0)
5570: goto out_end;
5571:
5572: set_reloc_control(rc);
5573:
5574: trans = btrfs_join_transaction(rc->extent_root);
5575: if (IS_ERR(trans)) {
5576: ret = PTR_ERR(trans);
5577: goto out_unset;
5578: }
5579:
5580: rc->merge_reloc_tree = true;
5581:
5582: while (!list_empty(&reloc_roots)) {
5583: reloc_root = list_first_entry(&reloc_roots, struct btrfs_root, root_list);
5584: list_del(&reloc_root->root_list);
5585:
5586: if (btrfs_root_refs(&reloc_root->root_item) == 0) {
5587: list_add_tail(&reloc_root->root_list,
5588: &rc->reloc_roots);
5589: continue;
5590: }
5591:
5592: fs_root = btrfs_get_fs_root(fs_info, reloc_root->root_key.offset,
5593: false);
5594: if (IS_ERR(fs_root)) {
5595: ret = PTR_ERR(fs_root);
5596: list_add_tail(&reloc_root->root_list, &reloc_roots);
5597: btrfs_end_transaction(trans);
5598: goto out_unset;
5599: }
5600:
5601: ret = __add_reloc_root(reloc_root);
5602: ASSERT(ret != -EEXIST);
5603: if (ret) {
5604: list_add_tail(&reloc_root->root_list, &reloc_roots);
5605: btrfs_put_root(fs_root);
5606: btrfs_end_transaction(trans);
5607: goto out_unset;
5608: }
5609: fs_root->reloc_root = btrfs_grab_root(reloc_root);
5610: btrfs_put_root(fs_root);
5611: }
5612:
5613: ret = btrfs_commit_transaction(trans);
5614: if (ret)
5615: goto out_unset;
5616:
5617: merge_reloc_roots(rc);
5618:
5619: unset_reloc_control(rc);
5620:
5621: trans = btrfs_join_transaction(rc->extent_root);
5622: if (IS_ERR(trans)) {
5623: ret = PTR_ERR(trans);
5624: goto out_clean;
5625: }
5626: ret = btrfs_commit_transaction(trans);
5627: out_clean:
5628: ret2 = clean_dirty_subvols(rc);
5629: if (ret2 < 0 && !ret)
5630: ret = ret2;
5631: out_unset:
5632: unset_reloc_control(rc);
5633: reloc_chunk_end(fs_info);
5634: out_end:
5635: free_reloc_control(rc);
5636: out:
5637: free_reloc_roots(&reloc_roots);
5638:
5639: btrfs_free_path(path);
5640:
5641: if (ret == 0 && !btrfs_fs_incompat(fs_info, REMAP_TREE)) {
5642: /* cleanup orphan inode in data relocation tree */
5643: fs_root = btrfs_grab_root(fs_info->data_reloc_root);
5644: ASSERT(fs_root);
5645: ret = btrfs_orphan_cleanup(fs_root);
5646: btrfs_put_root(fs_root);
5647: }
5648: return ret;
5649: }
SourceFile:fs/btrfs/relocation.c]
|
| 32/2 |
2026/06/02 10:57 |
llm |
expert |
0m
Model:
Tokens:
input: 33495
output: 63
thoughts: 448
Thoughts: **Investigating a Potential Use-After-Free in Btrfs Root Management**
Okay, so I'm looking at this `btrfs_recover_relocation` function and I see it does this: it grabs a reference to `fs_root->reloc_root`. Specifically, this code snip:
```c
fs_root->reloc_root = btrfs_grab_root(reloc_root);
btrfs_put_root(fs_root);
```
Then I'm trying to wrap my head around a possible UAF issue, and I'm reminded of `btrfs_drop_and_free_fs_root`. If there's an FS error, it checks `root->reloc_root` and potentially releases it:
```c
if (unlikely(BTRFS_FS_ERROR(fs_info))) {
ASSERT(root->log_root == NULL);
if (root->reloc_root) {
btrfs_put_root(root->reloc_root);
root->reloc_root = NULL;
}
}
```
Now, I'm thinking, if `root->reloc_root` holds a reference, why would there be a UAF? It *shouldn't* happen. Then it clicks β `root->reloc_root` is *just* a pointer! `btrfs_put_root` decrements the reference count of the root object pointed to, so I need to make sure the reference count is correct. `btrfs_grab_root` increments it. So, after `btrfs_grab_root` is called, `root->reloc_root` should have a refcount of at least 1. What's causing a refcount of 0? Why is the refcount 0?
I need to revisit how `btrfs_put_root` itself works to understand the issue. I recall that it has this structure:
```c
void btrfs_put_root(struct btrfs_root *root)
{
if (!root)
return;
if (refcount_dec_and_test(&root->refs)) {
...
kfree(root);
}
}
```
Could it be that `root->reloc_root` is set *without* an initial reference being taken? This is the most likely cause of the issue.
I need to thoroughly check *all* assignments to `root->reloc_root` to identify where this refcount is potentially being mishandled. I need to be sure the reference counting is consistent.
|
| 33/2 |
2026/06/02 10:57 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:fs/btrfs/relocation.c IncludeSnippetLines:1 Name:btrfs_root::reloc_root SourceTreePrefix:fs/btrfs/]
Results: map[References:[map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_drop_and_free_fs_root SourceFile:fs/btrfs/disk-io.c SourceLine:4153 SourceSnippet:4152: ASSERT(root->log_root == NULL);
4153: if (root->reloc_root) {
4154: btrfs_put_root(root->reloc_root);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_drop_and_free_fs_root SourceFile:fs/btrfs/disk-io.c SourceLine:4154 SourceSnippet:4153: if (root->reloc_root) {
4154: btrfs_put_root(root->reloc_root);
4155: root->reloc_root = NULL;
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:btrfs_drop_and_free_fs_root SourceFile:fs/btrfs/disk-io.c SourceLine:4155 SourceSnippet:4154: btrfs_put_root(root->reloc_root);
4155: root->reloc_root = NULL;
4156: }
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_init_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:726 SourceSnippet: 725: */
726: if (root->reloc_root) {
727: reloc_root = root->reloc_root;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_init_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:727 SourceSnippet: 726: if (root->reloc_root) {
727: reloc_root = root->reloc_root;
728: btrfs_set_root_last_trans(reloc_root, trans->transid);
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:btrfs_init_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:757 SourceSnippet: 756: }
757: root->reloc_root = btrfs_grab_root(reloc_root);
758: return 0;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_qgroup_trace_subtree_after_cow SourceFile:fs/btrfs/qgroup.c SourceLine:4871 SourceSnippet:4870: return 0;
4871: if (!btrfs_is_fstree(btrfs_root_id(root)) || !root->reloc_root)
4872: return 0;
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:btrfs_recover_relocation SourceFile:fs/btrfs/relocation.c SourceLine:5609 SourceSnippet:5608: }
5609: fs_root->reloc_root = btrfs_grab_root(reloc_root);
5610: btrfs_put_root(fs_root);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_reloc_post_snapshot SourceFile:fs/btrfs/relocation.c SourceLine:5833 SourceSnippet:5832: new_root = pending->snap;
5833: reloc_root = create_reloc_root(trans, root->reloc_root, btrfs_root_id(new_root));
5834: if (IS_ERR(reloc_root))
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:btrfs_reloc_post_snapshot SourceFile:fs/btrfs/relocation.c SourceLine:5844 SourceSnippet:5843: }
5844: new_root->reloc_root = btrfs_grab_root(reloc_root);
5845: return 0;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_reloc_pre_snapshot SourceFile:fs/btrfs/relocation.c SourceLine:5786 SourceSnippet:5785:
5786: root = root->reloc_root;
5787: BUG_ON(btrfs_root_refs(&root->root_item) == 0);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_should_ignore_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:288 SourceSnippet: 287:
288: reloc_root = root->reloc_root;
289: if (!reloc_root)
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_update_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:775 SourceSnippet: 774:
775: reloc_root = root->reloc_root;
776: root_item = &reloc_root->root_item;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:clean_dirty_subvols SourceFile:fs/btrfs/relocation.c SourceLine:1463 SourceSnippet:1462: /* Merged subvolume, cleanup its reloc root */
1463: struct btrfs_root *reloc_root = root->reloc_root;
1464:
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:clean_dirty_subvols SourceFile:fs/btrfs/relocation.c SourceLine:1466 SourceSnippet:1465: list_del_init(&root->reloc_dirty_list);
1466: root->reloc_root = NULL;
1467: /*
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:have_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:272 SourceSnippet: 271: return false;
272: if (!root->reloc_root)
273: return false;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:insert_dirty_subvol SourceFile:fs/btrfs/relocation.c SourceLine:1427 SourceSnippet:1426: {
1427: struct btrfs_root *reloc_root = root->reloc_root;
1428: struct btrfs_root_item *reloc_root_item;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:merge_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:1527 SourceSnippet:1526:
1527: reloc_root = root->reloc_root;
1528: root_item = &reloc_root->root_item;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:merge_reloc_roots SourceFile:fs/btrfs/relocation.c SourceLine:1847 SourceSnippet:1846: }
1847: if (WARN_ON(root->reloc_root != reloc_root)) {
1848: /*
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:merge_reloc_roots SourceFile:fs/btrfs/relocation.c SourceLine:1865 SourceSnippet:1864: if (!IS_ERR(root)) {
1865: if (root->reloc_root == reloc_root) {
1866: root->reloc_root = NULL;
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:merge_reloc_roots SourceFile:fs/btrfs/relocation.c SourceLine:1866 SourceSnippet:1865: if (root->reloc_root == reloc_root) {
1866: root->reloc_root = NULL;
1867: btrfs_put_root(reloc_root);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:need_reserve_reloc_root SourceFile:fs/btrfs/transaction.c SourceLine:563 SourceSnippet: 562: btrfs_root_id(root) == BTRFS_TREE_RELOC_OBJECTID ||
563: root->reloc_root)
564: return false;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1732 SourceSnippet:1731:
1732: if (unlikely(root->reloc_root != reloc_root)) {
1733: if (root->reloc_root) {
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1733 SourceSnippet:1732: if (unlikely(root->reloc_root != reloc_root)) {
1733: if (root->reloc_root) {
1734: btrfs_err(fs_info,
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1734 SourceSnippet:1733: if (root->reloc_root) {
1734: btrfs_err(fs_info,
1735: "reloc tree mismatch, root %lld has reloc root key (%lld %u %llu) gen %llu, expect reloc root key (%lld %u %llu) gen %llu",
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1734 SourceSnippet:1733: if (root->reloc_root) {
1734: btrfs_err(fs_info,
1735: "reloc tree mismatch, root %lld has reloc root key (%lld %u %llu) gen %llu, expect reloc root key (%lld %u %llu) gen %llu",
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1734 SourceSnippet:1733: if (root->reloc_root) {
1734: btrfs_err(fs_info,
1735: "reloc tree mismatch, root %lld has reloc root key (%lld %u %llu) gen %llu, expect reloc root key (%lld %u %llu) gen %llu",
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1734 SourceSnippet:1733: if (root->reloc_root) {
1734: btrfs_err(fs_info,
1735: "reloc tree mismatch, root %lld has reloc root key (%lld %u %llu) gen %llu, expect reloc root key (%lld %u %llu) gen %llu",
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:record_reloc_root_in_trans SourceFile:fs/btrfs/relocation.c SourceLine:1949 SourceSnippet:1948: }
1949: if (unlikely(root->reloc_root != reloc_root)) {
1950: DEBUG_WARN("unexpected reloc root found");
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:relocate_tree_block SourceFile:fs/btrfs/relocation.c SourceLine:2519 SourceSnippet:2518: */
2519: if (!root->reloc_root) {
2520: ret = -ENOENT;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:relocate_tree_block SourceFile:fs/btrfs/relocation.c SourceLine:2523 SourceSnippet:2522: }
2523: root = root->reloc_root;
2524: node->new_bytenr = root->node->start;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:select_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:2011 SourceSnippet:2010: return ERR_PTR(ret);
2011: root = root->reloc_root;
2012:
]] TruncatedOutput:false]
|
| 34/2 |
2026/06/02 10:57 |
llm |
expert |
0m
Model:
Tokens:
input: 38335
output: 44
thoughts: 16
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in file systems, likely Btrfs, and am in the middle of diagnosing a complex issue:
**Delving into `btrfs_reloc_post_snapshot`**
Alright, I need to get to the bottom of this. The problem seems to be related to snapshots, which means I should start by looking at `btrfs_reloc_post_snapshot`. This is a critical function in Btrfs, a core routine that probably handles the tricky logic *after* a snapshot is created, especially when dealing with data relocation or other optimizations related to copy-on-write. I need to understand what this function does and if it might be the cause of the problem, or at least be a place where the issue is surfacing. My instincts tell me the issue is probably related to relocation, or the moving of blocks, after a snapshot. Let's see if this hunch is correct, or if this routine simply surfaces the issue.
|
| 35/2 |
2026/06/02 10:57 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:btrfs_reloc_post_snapshot]
Results: map[SourceCode:5801: /*
5802: * called after snapshot is created. migrate block reservation
5803: * and create reloc root for the newly created snapshot
5804: *
5805: * This is similar to btrfs_init_reloc_root(), we come out of here with two
5806: * references held on the reloc_root, one for root->reloc_root and one for
5807: * rc->reloc_roots.
5808: */
5809: int btrfs_reloc_post_snapshot(struct btrfs_trans_handle *trans,
5810: struct btrfs_pending_snapshot *pending)
5811: {
5812: struct btrfs_root *root = pending->root;
5813: struct btrfs_root *reloc_root;
5814: struct btrfs_root *new_root;
5815: struct reloc_control *rc = root->fs_info->reloc_ctl;
5816: int ret;
5817:
5818: if (!rc || !have_reloc_root(root))
5819: return 0;
5820:
5821: rc = root->fs_info->reloc_ctl;
5822: rc->merging_rsv_size += rc->nodes_relocated;
5823:
5824: if (rc->merge_reloc_tree) {
5825: ret = btrfs_block_rsv_migrate(&pending->block_rsv,
5826: rc->block_rsv,
5827: rc->nodes_relocated, true);
5828: if (ret)
5829: return ret;
5830: }
5831:
5832: new_root = pending->snap;
5833: reloc_root = create_reloc_root(trans, root->reloc_root, btrfs_root_id(new_root));
5834: if (IS_ERR(reloc_root))
5835: return PTR_ERR(reloc_root);
5836:
5837: ret = __add_reloc_root(reloc_root);
5838: ASSERT(ret != -EEXIST);
5839: if (ret) {
5840: /* Pairs with create_reloc_root */
5841: btrfs_put_root(reloc_root);
5842: return ret;
5843: }
5844: new_root->reloc_root = btrfs_grab_root(reloc_root);
5845: return 0;
5846: }
SourceFile:fs/btrfs/relocation.c]
|
| 36/2 |
2026/06/02 10:57 |
llm |
expert |
0m
Model:
Tokens:
input: 39384
output: 44
thoughts: 2213
Thoughts: **Analysis of a Btrfs Relocation Root Reference Leak**
Okay, so I'm digging into this Btrfs code, specifically around relocation roots. Initially, I see that `new_root->reloc_root` is assigned a reference. The core concern hits me: what happens if `btrfs_drop_and_free_fs_root` is called *while* `root->reloc_root` is set? The code has a check within that function: if `BTRFS_FS_ERROR(fs_info)` is true, it drops the reference and sets `root->reloc_root` to NULL, all good there. But what if it's *false*? Then `root->reloc_root` isn't cleared! That means `btrfs_put_root(root)` frees `root`, but the `root->reloc_root` *pointer* inside `root` is still dangling, which I quickly realize is a leak.
Now, the potential for a Use-After-Free (UAF) is inside `btrfs_put_root(root->reloc_root)`. So, if `BTRFS_FS_ERROR(fs_info)` is false, then `root->reloc_root` becomes a dangling pointer. The reason is because the refcount of `root->reloc_root` is dropped to 0, it gets freed, but the `root->reloc_root` pointer isn't set to NULL.
I started to investigate the potential places where this could happen. I check `clean_dirty_subvols` and `merge_reloc_roots`, and in both cases `root->reloc_root` is set to NULL *before* dropping the reference. So those aren't the source of the leak.
I thought about `merge_reloc_roots` failing. In that case, the `free_reloc_roots` function is called, which calls `__del_reloc_root(reloc_root)`. But then, the stack trace indicates that `__del_reloc_root` is the one freeing the memory. I was confused, because `__del_reloc_root` shouldn't be dropping the *last* reference if `root->reloc_root` still points to the reloc root. This means that there's a possibility that `root->reloc_root` was set, but the refcount wasn't incremented properly, or decremented elsewhere without clearing `root->reloc_root`.
I started to look at `btrfs_init_reloc_root` again. The critical part is when `root->reloc_root` is assigned. It seems like the bug could happen if `root->reloc_root` was *already* set. If `root->reloc_root` was set, then it just returns 0 and uses the already existing reloc root.
I'll have to investigate further and trace calls to `btrfs_record_root_in_trans`, just in case that's relevant.
|
| 37/2 |
2026/06/02 10:57 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/transaction.c Name:btrfs_record_root_in_trans]
Results: map[SourceCode: 488: int btrfs_record_root_in_trans(struct btrfs_trans_handle *trans,
489: struct btrfs_root *root)
490: {
491: struct btrfs_fs_info *fs_info = root->fs_info;
492: int ret;
493:
494: if (!test_bit(BTRFS_ROOT_SHAREABLE, &root->state))
495: return 0;
496:
497: /*
498: * see record_root_in_trans for comments about IN_TRANS_SETUP usage
499: * and barriers
500: */
501: smp_rmb();
502: if (btrfs_get_root_last_trans(root) == trans->transid &&
503: !test_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state))
504: return 0;
505:
506: mutex_lock(&fs_info->reloc_mutex);
507: ret = record_root_in_trans(trans, root, false);
508: mutex_unlock(&fs_info->reloc_mutex);
509:
510: return ret;
511: }
SourceFile:fs/btrfs/transaction.c]
|
| 38/2 |
2026/06/02 10:57 |
llm |
expert |
0m
Model:
Tokens:
input: 42710
output: 40
thoughts: 13
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:
**Checking `record_root_in_trans`**
Alright, I need to get a handle on `record_root_in_trans`. Given my current task, that variable is critical. I'm immediately running through the potential implications in my head: What data does it hold? How is it populated? Is it thread-safe? What are the read/write patterns? What's the scope of its use? Specifically, I need to determine its role in the transaction context. If itβs influencing the propagation of change through the system I must fully comprehend its interactions with dependent routines. I'm going to look deeper into its usage, tracing back any potential dependencies on it. I need to understand if it is a central coordination point, a simple flag, or something more complex, before moving forward. Let's see what's what.
|
| 39/2 |
2026/06/02 10:57 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/transaction.c Name:record_root_in_trans]
Results: map[SourceCode: 402: /*
403: * This does all the record keeping required to make sure that a shareable root
404: * is properly recorded in a given transaction. This is required to make sure
405: * the old root from before we joined the transaction is deleted when the
406: * transaction commits.
407: */
408: static int record_root_in_trans(struct btrfs_trans_handle *trans,
409: struct btrfs_root *root,
410: bool force)
411: {
412: struct btrfs_fs_info *fs_info = root->fs_info;
413: int ret = 0;
414:
415: if ((test_bit(BTRFS_ROOT_SHAREABLE, &root->state) &&
416: btrfs_get_root_last_trans(root) < trans->transid) || force) {
417: WARN_ON(!force && root->commit_root != root->node);
418:
419: /*
420: * see below for IN_TRANS_SETUP usage rules
421: * we have the reloc mutex held now, so there
422: * is only one writer in this function
423: */
424: set_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state);
425:
426: /* make sure readers find IN_TRANS_SETUP before
427: * they find our root->last_trans update
428: */
429: smp_wmb();
430:
431: spin_lock(&fs_info->fs_roots_radix_lock);
432: if (btrfs_get_root_last_trans(root) == trans->transid && !force) {
433: spin_unlock(&fs_info->fs_roots_radix_lock);
434: return 0;
435: }
436: radix_tree_tag_set(&fs_info->fs_roots_radix,
437: (unsigned long)btrfs_root_id(root),
438: BTRFS_ROOT_TRANS_TAG);
439: spin_unlock(&fs_info->fs_roots_radix_lock);
440: btrfs_set_root_last_trans(root, trans->transid);
441:
442: /* this is pretty tricky. We don't want to
443: * take the relocation lock in btrfs_record_root_in_trans
444: * unless we're really doing the first setup for this root in
445: * this transaction.
446: *
447: * Normally we'd use root->last_trans as a flag to decide
448: * if we want to take the expensive mutex.
449: *
450: * But, we have to set root->last_trans before we
451: * init the relocation root, otherwise, we trip over warnings
452: * in ctree.c. The solution used here is to flag ourselves
453: * with root IN_TRANS_SETUP. When this is 1, we're still
454: * fixing up the reloc trees and everyone must wait.
455: *
456: * When this is zero, they can trust root->last_trans and fly
457: * through btrfs_record_root_in_trans without having to take the
458: * lock. smp_wmb() makes sure that all the writes above are
459: * done before we pop in the zero below
460: */
461: ret = btrfs_init_reloc_root(trans, root);
462: smp_mb__before_atomic();
463: clear_bit(BTRFS_ROOT_IN_TRANS_SETUP, &root->state);
464: }
465: return ret;
466: }
SourceFile:fs/btrfs/transaction.c]
|
| 40/2 |
2026/06/02 10:57 |
llm |
expert |
0m
Model:
Tokens:
input: 44118
output: 42
thoughts: 986
Thoughts: **Analyzing a Potential Use-After-Free Vulnerability in Btrfs Relocation Code**
Okay, let's break this down. My initial thought is, there's a potential issue where a `root->reloc_root` pointer is being freed prematurely, leading to a Use-After-Free (UAF) situation. I'm focusing on the interaction between `btrfs_init_reloc_root`, `btrfs_drop_and_free_fs_root`, `__del_reloc_root`, and related functions.
First, I see that `btrfs_init_reloc_root` takes a reference to `root->reloc_root`. If it fails, `root->reloc_root` isn't set. But what if it *succeeds*, and later `root->reloc_root` gets cleared? That's a red flag. I know `clean_dirty_subvols` and `merge_reloc_roots` clear `root->reloc_root`, but I'm concerned about cases where it might *not* be cleared and still be referenced.
Then, I'm examining `btrfs_drop_and_free_fs_root`. Specifically, this block:
```c
if (unlikely(BTRFS_FS_ERROR(fs_info))) {
ASSERT(root->log_root == NULL);
if (root->reloc_root) {
btrfs_put_root(root->reloc_root);
root->reloc_root = NULL;
}
}
```
If the filesystem has an error, it drops the reference to `root->reloc_root`. But if the root has been freed before, that's a UAF. I need to understand why `root->reloc_root` might be freed *before* this `btrfs_put_root` call.
My reasoning leads me to `__del_reloc_root`. If `__del_reloc_root` freed the root, it had to be because the refcount was 1. Let's trace how the refcount is managed:
1. `btrfs_init_reloc_root` calls `create_reloc_root`, which creates a root with refcount 1.
2. `__add_reloc_root` adds it to a list, *but doesn't* increment the refcount.
3. `btrfs_grab_root` *does* increment the refcount to 2.
4. Then, `__del_reloc_root` drops the list reference, the refcount becomes 1.
5. `root->reloc_root` still holds a reference, so the refcount should still be 1.
So, `__del_reloc_root` *shouldn't* free the root! But the stack trace shows it *did*:
```
Freed by task 5339:
...
kfree+0x1c5/0x640 mm/slub.c:6566
__del_reloc_root+0x341/0x3b0 fs/btrfs/relocation.c:547
free_reloc_roots+0x5f/0x90 fs/btrfs/relocation.c:1803
```
This means the refcount must have dropped to 0 before `__del_reloc_root` called `btrfs_put_root`. That's the core of the problem. Something, somewhere, dropped a reference to this relocated root *before* `__del_reloc_root`. This means that either `btrfs_grab_root` was *never* called, or some other part of the code is decrementing the refcount when it shouldn't.
Now, I'm thinking about other possible locations where `btrfs_put_root` might be called prematurely. Let me check `clean_dirty_subvols`:
```c
if (reloc_root) {
/*
* btrfs_drop_snapshot drops our ref we hold for
* ->reloc_root. If it fails however we must
* drop the ref ourselves.
*/
ret2 = btrfs_drop_snapshot(reloc_root, false, true);
if (ret2 < 0) {
btrfs_put_root(reloc_root);
if (!ret)
ret = ret2;
}
}
```
|
| 41/2 |
2026/06/02 10:58 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/extent-tree.c Name:btrfs_drop_snapshot]
Results: map[SourceCode:6234: /*
6235: * drop a subvolume tree.
6236: *
6237: * this function traverses the tree freeing any blocks that only
6238: * referenced by the tree.
6239: *
6240: * when a shared tree block is found. this function decreases its
6241: * reference count by one. if update_ref is true, this function
6242: * also make sure backrefs for the shared block and all lower level
6243: * blocks are properly updated.
6244: *
6245: * If called with for_reloc set, may exit early with -EAGAIN
6246: */
6247: int btrfs_drop_snapshot(struct btrfs_root *root, bool update_ref, bool for_reloc)
6248: {
6249: const bool is_reloc_root = (btrfs_root_id(root) == BTRFS_TREE_RELOC_OBJECTID);
6250: struct btrfs_fs_info *fs_info = root->fs_info;
6251: struct btrfs_path *path;
6252: struct btrfs_trans_handle *trans;
6253: struct btrfs_root *tree_root = fs_info->tree_root;
6254: struct btrfs_root_item *root_item = &root->root_item;
6255: struct walk_control AUTO_KFREE(wc);
6256: struct btrfs_key key;
6257: const u64 rootid = btrfs_root_id(root);
6258: int ret = 0;
6259: int level;
6260: bool root_dropped = false;
6261: bool unfinished_drop = false;
6262:
6263: btrfs_debug(fs_info, "Drop subvolume %llu", btrfs_root_id(root));
6264:
6265: path = btrfs_alloc_path();
6266: if (!path) {
6267: ret = -ENOMEM;
6268: goto out;
6269: }
6270:
6271: wc = kzalloc(sizeof(*wc), GFP_NOFS);
6272: if (!wc) {
6273: ret = -ENOMEM;
6274: goto out_free;
6275: }
6276:
6277: /*
6278: * Use join to avoid potential EINTR from transaction start. See
6279: * wait_reserve_ticket and the whole reservation callchain.
6280: */
6281: if (for_reloc)
6282: trans = btrfs_join_transaction(tree_root);
6283: else
6284: trans = btrfs_start_transaction(tree_root, 0);
6285: if (IS_ERR(trans)) {
6286: ret = PTR_ERR(trans);
6287: goto out_free;
6288: }
6289:
6290: ret = btrfs_run_delayed_items(trans);
6291: if (ret)
6292: goto out_end_trans;
6293:
6294: /*
6295: * This will help us catch people modifying the fs tree while we're
6296: * dropping it. It is unsafe to mess with the fs tree while it's being
6297: * dropped as we unlock the root node and parent nodes as we walk down
6298: * the tree, assuming nothing will change. If something does change
6299: * then we'll have stale information and drop references to blocks we've
6300: * already dropped.
6301: */
6302: set_bit(BTRFS_ROOT_DELETING, &root->state);
6303: unfinished_drop = test_bit(BTRFS_ROOT_UNFINISHED_DROP, &root->state);
6304:
6305: if (btrfs_disk_key_objectid(&root_item->drop_progress) == 0) {
6306: level = btrfs_header_level(root->node);
6307: path->nodes[level] = btrfs_lock_root_node(root);
6308: path->slots[level] = 0;
6309: path->locks[level] = BTRFS_WRITE_LOCK;
6310: memset(&wc->update_progress, 0,
6311: sizeof(wc->update_progress));
6312: } else {
6313: btrfs_disk_key_to_cpu(&key, &root_item->drop_progress);
6314: memcpy(&wc->update_progress, &key,
6315: sizeof(wc->update_progress));
6316:
6317: level = btrfs_root_drop_level(root_item);
6318: BUG_ON(level == 0);
6319: path->lowest_level = level;
6320: ret = btrfs_search_slot(NULL, root, &key, path, 0, 0);
6321: path->lowest_level = 0;
6322: if (ret < 0)
6323: goto out_end_trans;
6324:
6325: WARN_ON(ret > 0);
6326: ret = 0;
6327:
6328: /*
6329: * unlock our path, this is safe because only this
6330: * function is allowed to delete this snapshot
6331: */
6332: btrfs_unlock_up_safe(path, 0);
6333:
6334: level = btrfs_header_level(root->node);
6335: while (1) {
6336: btrfs_tree_lock(path->nodes[level]);
6337: path->locks[level] = BTRFS_WRITE_LOCK;
6338:
6339: /*
6340: * btrfs_lookup_extent_info() returns 0 for success,
6341: * or < 0 for error.
6342: */
6343: ret = btrfs_lookup_extent_info(trans, fs_info,
6344: path->nodes[level]->start,
6345: level, 1, &wc->refs[level],
6346: &wc->flags[level], NULL);
6347: if (ret < 0)
6348: goto out_end_trans;
6349:
6350: BUG_ON(wc->refs[level] == 0);
6351:
6352: if (level == btrfs_root_drop_level(root_item))
6353: break;
6354:
6355: btrfs_tree_unlock(path->nodes[level]);
6356: path->locks[level] = 0;
6357: WARN_ON(wc->refs[level] != 1);
6358: level--;
6359: }
6360: }
6361:
6362: wc->restarted = test_bit(BTRFS_ROOT_DEAD_TREE, &root->state);
6363: wc->level = level;
6364: wc->shared_level = -1;
6365: wc->stage = DROP_REFERENCE;
6366: wc->update_ref = update_ref;
6367: wc->keep_locks = 0;
6368: wc->reada_count = BTRFS_NODEPTRS_PER_BLOCK(fs_info);
6369:
6370: while (1) {
6371:
6372: ret = walk_down_tree(trans, root, path, wc);
6373: if (unlikely(ret < 0)) {
6374: btrfs_abort_transaction(trans, ret);
6375: break;
6376: }
6377:
6378: ret = walk_up_tree(trans, root, path, wc, BTRFS_MAX_LEVEL);
6379: if (unlikely(ret < 0)) {
6380: btrfs_abort_transaction(trans, ret);
6381: break;
6382: }
6383:
6384: if (ret > 0) {
6385: BUG_ON(wc->stage != DROP_REFERENCE);
6386: ret = 0;
6387: break;
6388: }
6389:
6390: if (wc->stage == DROP_REFERENCE) {
6391: wc->drop_level = wc->level;
6392: btrfs_node_key_to_cpu(path->nodes[wc->drop_level],
6393: &wc->drop_progress,
6394: path->slots[wc->drop_level]);
6395: }
6396: btrfs_cpu_key_to_disk(&root_item->drop_progress,
6397: &wc->drop_progress);
6398: btrfs_set_root_drop_level(root_item, wc->drop_level);
6399:
6400: BUG_ON(wc->level == 0);
6401: if (btrfs_should_end_transaction(trans) ||
6402: (!for_reloc && btrfs_need_cleaner_sleep(fs_info))) {
6403: ret = btrfs_update_root(trans, tree_root,
6404: &root->root_key,
6405: root_item);
6406: if (unlikely(ret)) {
6407: btrfs_abort_transaction(trans, ret);
6408: goto out_end_trans;
6409: }
6410:
6411: if (!is_reloc_root)
6412: btrfs_set_last_root_drop_gen(fs_info, trans->transid);
6413:
6414: btrfs_end_transaction_throttle(trans);
6415: if (!for_reloc && btrfs_need_cleaner_sleep(fs_info)) {
6416: btrfs_debug(fs_info,
6417: "drop snapshot early exit");
6418: ret = -EAGAIN;
6419: goto out_free;
6420: }
6421:
6422: /*
6423: * Use join to avoid potential EINTR from transaction
6424: * start. See wait_reserve_ticket and the whole
6425: * reservation callchain.
6426: */
6427: if (for_reloc)
6428: trans = btrfs_join_transaction(tree_root);
6429: else
6430: trans = btrfs_start_transaction(tree_root, 0);
6431: if (IS_ERR(trans)) {
6432: ret = PTR_ERR(trans);
6433: goto out_free;
6434: }
6435: }
6436: }
6437: btrfs_release_path(path);
6438: if (ret)
6439: goto out_end_trans;
6440:
6441: ret = btrfs_del_root(trans, &root->root_key);
6442: if (unlikely(ret)) {
6443: btrfs_abort_transaction(trans, ret);
6444: goto out_end_trans;
6445: }
6446:
6447: if (!is_reloc_root) {
6448: ret = btrfs_find_root(tree_root, &root->root_key, path,
6449: NULL, NULL);
6450: if (unlikely(ret < 0)) {
6451: btrfs_abort_transaction(trans, ret);
6452: goto out_end_trans;
6453: } else if (ret > 0) {
6454: ret = 0;
6455: /*
6456: * If we fail to delete the orphan item this time
6457: * around, it'll get picked up the next time.
6458: *
6459: * The most common failure here is just -ENOENT.
6460: */
6461: btrfs_del_orphan_item(trans, tree_root, btrfs_root_id(root));
6462: }
6463: }
6464:
6465: /*
6466: * This subvolume is going to be completely dropped, and won't be
6467: * recorded as dirty roots, thus pertrans meta rsv will not be freed at
6468: * commit transaction time. So free it here manually.
6469: */
6470: btrfs_qgroup_convert_reserved_meta(root, INT_MAX);
6471: btrfs_qgroup_free_meta_all_pertrans(root);
6472:
6473: if (test_bit(BTRFS_ROOT_IN_RADIX, &root->state))
6474: btrfs_add_dropped_root(trans, root);
6475: else
6476: btrfs_put_root(root);
6477: root_dropped = true;
6478: out_end_trans:
6479: if (!is_reloc_root)
6480: btrfs_set_last_root_drop_gen(fs_info, trans->transid);
6481:
6482: btrfs_end_transaction_throttle(trans);
6483: out_free:
6484: btrfs_free_path(path);
6485: out:
6486: if (!ret && root_dropped) {
6487: ret = btrfs_qgroup_cleanup_dropped_subvolume(fs_info, rootid);
6488: if (ret < 0)
6489: btrfs_warn_rl(fs_info,
6490: "failed to cleanup qgroup 0/%llu: %d",
6491: rootid, ret);
6492: ret = 0;
6493: }
6494: /*
6495: * We were an unfinished drop root, check to see if there are any
6496: * pending, and if not clear and wake up any waiters.
6497: */
6498: if (!ret && unfinished_drop)
6499: btrfs_maybe_wake_unfinished_drop(fs_info);
6500:
6501: /*
6502: * So if we need to stop dropping the snapshot for whatever reason we
6503: * need to make sure to add it back to the dead root list so that we
6504: * keep trying to do the work later. This also cleans up roots if we
6505: * don't have it in the radix (like when we recover after a power fail
6506: * or unmount) so we don't leak memory.
6507: */
6508: if (!for_reloc && !root_dropped)
6509: btrfs_add_dead_root(root);
6510: return ret;
6511: }
SourceFile:fs/btrfs/extent-tree.c]
|
| 42/2 |
2026/06/02 10:58 |
llm |
expert |
0m
Model:
Tokens:
input: 50553
output: 44
thoughts: 4316
Thoughts: **Relocation Root Reference Tracking: A Deep Dive**
Okay, so I've been wrestling with how these relocation roots are handled in Btrfs, specifically regarding their reference counts and lifecycle. It's a bit of a dance, and I think I've identified a potential issue. Here's what I've got:
First, `btrfs_drop_snapshot` seems straightforward: it drops the reference when successful. No surprises there. `clean_dirty_subvols` later calls `btrfs_drop_snapshot` in several cases, and that behavior looks correct. It's balanced so far.
Now, consider `merge_reloc_roots`. This function can either successfully merge reloc roots *or* cleanup a failed merge. When merging roots fails, `free_reloc_roots` cleans up. If *merge_reloc_roots* succeeds, things looks balanced. There's a point where `reloc_root` is put on `dirty_subvol_roots`. This means the list reference is retained, and `clean_dirty_subvols` will handle dropping the actual reference.
Here's where things get interesting and where a potential issue exists. Let's look at the scenario of a *failed* merge. In the failure path, `free_reloc_roots` calls `__del_reloc_root`. Now, in the happy path, it seems fine since it will drop a reference and free the root. However, what if there's a reference held by `root->reloc_root` that was never cleared? If the root's `reloc_root` was set, then the refcount should be 2. If `__del_reloc_root` drops the list reference, then the refcount would be 1, and the root *should not* be freed. Yet, the stack trace indicates that it *is* freed in the failure scenario. Something doesn't add up.
I considered `btrfs_recover_relocation`, which happens during mount. It appears the references are handled there in a safe way and this failure isn't coming from that part of the code.
Let's trace the normal relocation code. A failure in `merge_reloc_roots` called from `relocate_block_group` should result in freeing a reloc root where refcount is 2 - from `btrfs_init_reloc_root` and `list`. This refcount would be 1 after `__del_reloc_root`. Yet, it's being freed, which means it was 1 before `__del_reloc_root`, *or* some other reference was dropped.
Now, it would only get to 1 if the reference from `root->reloc_root` was dropped or never set. But if that reference *wasn't* set and `merge_reloc_roots` is failing, then UAF from `btrfs_drop_and_free_fs_root` should not occur.
So it's either:
1. The refcount is not 2, indicating the reference from `root->reloc_root` is somehow being dropped or not set in `btrfs_init_reloc_root`.
2. The code is dropping the `root->reloc_root` reference, or setting it to a new value somehow.
I need to dig deeper into the transaction management and error handling within `btrfs_init_reloc_root` and `merge_reloc_roots` to understand *why* the refcount is not what I expect, and more importantly, how it's being freed. The stack trace is pointing to `__del_reloc_root`, but the refcount should prevent the free. This discrepancy is a definite concern. I'm going to track the reference count carefully in a debugger and follow the path when `merge_reloc_roots` fails, tracing from `btrfs_init_reloc_root` to `__del_reloc_root`.
|
| 43/2 |
2026/06/02 10:58 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:btrfs_update_reloc_root]
Results: map[SourceCode: 761: /*
762: * update root item of reloc tree
763: */
764: int btrfs_update_reloc_root(struct btrfs_trans_handle *trans,
765: struct btrfs_root *root)
766: {
767: struct btrfs_fs_info *fs_info = root->fs_info;
768: struct btrfs_root *reloc_root;
769: struct btrfs_root_item *root_item;
770: int ret;
771:
772: if (!have_reloc_root(root))
773: return 0;
774:
775: reloc_root = root->reloc_root;
776: root_item = &reloc_root->root_item;
777:
778: /*
779: * We are probably ok here, but __del_reloc_root() will drop its ref of
780: * the root. We have the ref for root->reloc_root, but just in case
781: * hold it while we update the reloc root.
782: */
783: btrfs_grab_root(reloc_root);
784:
785: /* root->reloc_root will stay until current relocation finished */
786: if (fs_info->reloc_ctl && fs_info->reloc_ctl->merge_reloc_tree &&
787: btrfs_root_refs(root_item) == 0) {
788: set_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state);
789: /*
790: * Mark the tree as dead before we change reloc_root so
791: * have_reloc_root will not touch it from now on.
792: */
793: smp_wmb();
794: __del_reloc_root(reloc_root);
795: }
796:
797: if (reloc_root->commit_root != reloc_root->node) {
798: __update_reloc_root(reloc_root);
799: btrfs_set_root_node(root_item, reloc_root->node);
800: free_extent_buffer(reloc_root->commit_root);
801: reloc_root->commit_root = btrfs_root_node(reloc_root);
802: }
803:
804: ret = btrfs_update_root(trans, fs_info->tree_root,
805: &reloc_root->root_key, root_item);
806: btrfs_put_root(reloc_root);
807: return ret;
808: }
SourceFile:fs/btrfs/relocation.c]
|
| 44/2 |
2026/06/02 10:58 |
llm |
expert |
0m
Model:
Tokens:
input: 56625
output: 63
thoughts: 1679
Thoughts: **Delving into the Btrfs Relocation Root Management**
Okay, so I'm digging into this `btrfs_update_reloc_root` function, and something's not quite right. It calls `__del_reloc_root(reloc_root)`, which *should* drop a reference, but it doesn't clear `root->reloc_root`. The comment tries to explain this by saying it's done *before* the relocation root changes, but it never actually changes here, it just sets a flag: `BTRFS_ROOT_DEAD_RELOC_TREE`.
The following function, `have_reloc_root`, checks that flag and `root->reloc_root`, but since we only set the flag, `have_reloc_root` should return false. However, even though the flag is set, `root->reloc_root` is *still* set, and, more importantly, it's *still* holding a reference. If the list reference gets dropped by `__del_reloc_root`, the reference count should be 1. Why did the stack trace say `__del_reloc_root` freed it?
My initial thought was that it was a call from `free_reloc_roots`, but the stack trace clearly shows this is not the case.
Looking at `merge_reloc_roots`, there are two paths. If the refcount is zero, it *does* clear `root->reloc_root` and drop a reference, then adds the reloc root to `dirty_subvol_roots`. This makes sense, as the `btrfs_drop_snapshot` later does the final drop.
However, the problem comes if `merge_reloc_roots` fails. It then calls `free_reloc_roots`. In this case, `__del_reloc_root` *should* drop a reference, but only if `root->reloc_root` *wasn't* already cleared. If it was, then `__del_reloc_root` *shouldn't* free the memory. But the stack trace said it *did* free it. That means `root->reloc_root` was already clear, and so the reference was already dropped.
If it was already cleared, then it means that `btrfs_drop_and_free_fs_root` wouldn't see it. The only way around this would be if `root` in `btrfs_drop_and_free_fs_root` was a different root!
But, this is not the case! `root->reloc_root` is a pointer to the reloc root, and these should be unique. So, it can't be shared among multiple roots.
So, how can this happen?
Wait, what if `btrfs_drop_and_free_fs_root` is called on the `reloc_root` itself? That still doesn't make sense... It is called on `root`, and drops `root->reloc_root`.
Okay, let's re-examine the stack trace: The stack means `__del_reloc_root` dropped the *last* reference. This means that, at the time of `__del_reloc_root`, `root->reloc_root` did *not* hold a reference.
This means that `root->reloc_root` must have been set *without* taking a reference. But, I just looked at `btrfs_init_reloc_root`. It sets `root->reloc_root` by calling `btrfs_grab_root`, which *should* increment the refcount! So, where else could `root->reloc_root` be set? I need to check for other places it's written to!
|
| 45/2 |
2026/06/02 10:59 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:fs/btrfs/relocation.c IncludeSnippetLines:1 Name:btrfs_root::reloc_root SourceTreePrefix:fs/btrfs/]
Results: map[References:[map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_drop_and_free_fs_root SourceFile:fs/btrfs/disk-io.c SourceLine:4153 SourceSnippet:4152: ASSERT(root->log_root == NULL);
4153: if (root->reloc_root) {
4154: btrfs_put_root(root->reloc_root);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_drop_and_free_fs_root SourceFile:fs/btrfs/disk-io.c SourceLine:4154 SourceSnippet:4153: if (root->reloc_root) {
4154: btrfs_put_root(root->reloc_root);
4155: root->reloc_root = NULL;
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:btrfs_drop_and_free_fs_root SourceFile:fs/btrfs/disk-io.c SourceLine:4155 SourceSnippet:4154: btrfs_put_root(root->reloc_root);
4155: root->reloc_root = NULL;
4156: }
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_init_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:726 SourceSnippet: 725: */
726: if (root->reloc_root) {
727: reloc_root = root->reloc_root;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_init_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:727 SourceSnippet: 726: if (root->reloc_root) {
727: reloc_root = root->reloc_root;
728: btrfs_set_root_last_trans(reloc_root, trans->transid);
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:btrfs_init_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:757 SourceSnippet: 756: }
757: root->reloc_root = btrfs_grab_root(reloc_root);
758: return 0;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_qgroup_trace_subtree_after_cow SourceFile:fs/btrfs/qgroup.c SourceLine:4871 SourceSnippet:4870: return 0;
4871: if (!btrfs_is_fstree(btrfs_root_id(root)) || !root->reloc_root)
4872: return 0;
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:btrfs_recover_relocation SourceFile:fs/btrfs/relocation.c SourceLine:5609 SourceSnippet:5608: }
5609: fs_root->reloc_root = btrfs_grab_root(reloc_root);
5610: btrfs_put_root(fs_root);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_reloc_post_snapshot SourceFile:fs/btrfs/relocation.c SourceLine:5833 SourceSnippet:5832: new_root = pending->snap;
5833: reloc_root = create_reloc_root(trans, root->reloc_root, btrfs_root_id(new_root));
5834: if (IS_ERR(reloc_root))
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:btrfs_reloc_post_snapshot SourceFile:fs/btrfs/relocation.c SourceLine:5844 SourceSnippet:5843: }
5844: new_root->reloc_root = btrfs_grab_root(reloc_root);
5845: return 0;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_reloc_pre_snapshot SourceFile:fs/btrfs/relocation.c SourceLine:5786 SourceSnippet:5785:
5786: root = root->reloc_root;
5787: BUG_ON(btrfs_root_refs(&root->root_item) == 0);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_should_ignore_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:288 SourceSnippet: 287:
288: reloc_root = root->reloc_root;
289: if (!reloc_root)
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:btrfs_update_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:775 SourceSnippet: 774:
775: reloc_root = root->reloc_root;
776: root_item = &reloc_root->root_item;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:clean_dirty_subvols SourceFile:fs/btrfs/relocation.c SourceLine:1463 SourceSnippet:1462: /* Merged subvolume, cleanup its reloc root */
1463: struct btrfs_root *reloc_root = root->reloc_root;
1464:
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:clean_dirty_subvols SourceFile:fs/btrfs/relocation.c SourceLine:1466 SourceSnippet:1465: list_del_init(&root->reloc_dirty_list);
1466: root->reloc_root = NULL;
1467: /*
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:have_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:272 SourceSnippet: 271: return false;
272: if (!root->reloc_root)
273: return false;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:insert_dirty_subvol SourceFile:fs/btrfs/relocation.c SourceLine:1427 SourceSnippet:1426: {
1427: struct btrfs_root *reloc_root = root->reloc_root;
1428: struct btrfs_root_item *reloc_root_item;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:merge_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:1527 SourceSnippet:1526:
1527: reloc_root = root->reloc_root;
1528: root_item = &reloc_root->root_item;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:merge_reloc_roots SourceFile:fs/btrfs/relocation.c SourceLine:1847 SourceSnippet:1846: }
1847: if (WARN_ON(root->reloc_root != reloc_root)) {
1848: /*
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:merge_reloc_roots SourceFile:fs/btrfs/relocation.c SourceLine:1865 SourceSnippet:1864: if (!IS_ERR(root)) {
1865: if (root->reloc_root == reloc_root) {
1866: root->reloc_root = NULL;
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:merge_reloc_roots SourceFile:fs/btrfs/relocation.c SourceLine:1866 SourceSnippet:1865: if (root->reloc_root == reloc_root) {
1866: root->reloc_root = NULL;
1867: btrfs_put_root(reloc_root);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:need_reserve_reloc_root SourceFile:fs/btrfs/transaction.c SourceLine:563 SourceSnippet: 562: btrfs_root_id(root) == BTRFS_TREE_RELOC_OBJECTID ||
563: root->reloc_root)
564: return false;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1732 SourceSnippet:1731:
1732: if (unlikely(root->reloc_root != reloc_root)) {
1733: if (root->reloc_root) {
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1733 SourceSnippet:1732: if (unlikely(root->reloc_root != reloc_root)) {
1733: if (root->reloc_root) {
1734: btrfs_err(fs_info,
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1734 SourceSnippet:1733: if (root->reloc_root) {
1734: btrfs_err(fs_info,
1735: "reloc tree mismatch, root %lld has reloc root key (%lld %u %llu) gen %llu, expect reloc root key (%lld %u %llu) gen %llu",
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1734 SourceSnippet:1733: if (root->reloc_root) {
1734: btrfs_err(fs_info,
1735: "reloc tree mismatch, root %lld has reloc root key (%lld %u %llu) gen %llu, expect reloc root key (%lld %u %llu) gen %llu",
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1734 SourceSnippet:1733: if (root->reloc_root) {
1734: btrfs_err(fs_info,
1735: "reloc tree mismatch, root %lld has reloc root key (%lld %u %llu) gen %llu, expect reloc root key (%lld %u %llu) gen %llu",
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:prepare_to_merge SourceFile:fs/btrfs/relocation.c SourceLine:1734 SourceSnippet:1733: if (root->reloc_root) {
1734: btrfs_err(fs_info,
1735: "reloc tree mismatch, root %lld has reloc root key (%lld %u %llu) gen %llu, expect reloc root key (%lld %u %llu) gen %llu",
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:record_reloc_root_in_trans SourceFile:fs/btrfs/relocation.c SourceLine:1949 SourceSnippet:1948: }
1949: if (unlikely(root->reloc_root != reloc_root)) {
1950: DEBUG_WARN("unexpected reloc root found");
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:relocate_tree_block SourceFile:fs/btrfs/relocation.c SourceLine:2519 SourceSnippet:2518: */
2519: if (!root->reloc_root) {
2520: ret = -ENOENT;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:relocate_tree_block SourceFile:fs/btrfs/relocation.c SourceLine:2523 SourceSnippet:2522: }
2523: root = root->reloc_root;
2524: node->new_bytenr = root->node->start;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:select_reloc_root SourceFile:fs/btrfs/relocation.c SourceLine:2011 SourceSnippet:2010: return ERR_PTR(ret);
2011: root = root->reloc_root;
2012:
]] TruncatedOutput:false]
|
| 46/2 |
2026/06/02 10:59 |
llm |
expert |
1m
Model:
Tokens:
input: 62938
output: 42
thoughts: 7644
Thoughts: **Analyzing a Btrfs Relocation Root UAF**
Okay, so I'm diving into this Btrfs UAF issue related to relocation roots, and it's a real head-scratcher. My initial thought is that `btrfs_update_reloc_root` might be the culprit, specifically this bit: if the relocation tree is dead and references are zero, `__del_reloc_root` is called, dropping the list reference, but critically, the `root->reloc_root` pointer isn't cleared. If this is the case, `root->reloc_root` would still hold a reference, so the reference count should be one. However, the code also calls `btrfs_put_root` at the end, which does pair with a `btrfs_grab_root` at the start of the function, and therefore, it doesn't net change the reference count.
My thought then goes to `merge_reloc_roots`, where if the root reference count is zero, it clears `root->reloc_root` and drops the ref. If `__del_reloc_root` was already called in `btrfs_update_reloc_root`, `reloc_root` is *no longer* on the `rc->reloc_roots` list. Then, when `merge_reloc_roots` iterates, it never sees this specific `reloc_root`, so `root->reloc_root` remains uncleared, and the reference count *should* remain at one.
Now I double-check `__del_reloc_root`. It *does* remove the item from the list, and it drops the list reference. This would mean that `merge_reloc_roots` *should* never see the item. So, I start to suspect that somehow `merge_reloc_roots` is failing and then the `free_reloc_roots` path is getting called. I then look at the stack trace and it points to the free being triggered by `__del_reloc_root` called from `free_reloc_roots` which is called from `merge_reloc_roots`. This confirms that `merge_reloc_roots` did fail and `reloc_root` *was* on the list, even though `__del_reloc_root` had already removed it.
Then, I trace the stack trace, and it points to `btrfs_ioctl_balance` being run concurrently with an unmount! If `btrfs_ioctl_balance` is running and the filesystem is unmounting, that is a problem! It looks like an ioctl is running while the filesystem is being unmounted. The `btrfs_ioctl_balance` calls the balance function, and it sets `BTRFS_FS_BALANCE_RUNNING` to false to pause, but what if something has gone wrong during the balance, and this happened during unmount? I realize that it's possible for task 5339 to have *already finished* `btrfs_ioctl_balance`, freeing the `reloc_root`, *then* task 5338 unmounted the filesystem in `close_ctree`.
However, the UAF would still happen if the refcount was already at zero before the `__del_reloc_root` in the `free_reloc_roots` path. But, in `btrfs_init_reloc_root`, `btrfs_grab_root` should increment the refcount to two.
I then search for all calls of `btrfs_put_root` for a `reloc_root`. I see that `btrfs_drop_snapshot` in `clean_dirty_subvols` will clear the reference if the item is no longer referenced. But the code *already* clears `root->reloc_root` before calling `btrfs_drop_snapshot`.
So I look again to `merge_reloc_roots`. If it fails, `btrfs_handle_fs_error` is called, and `BTRFS_FS_STATE_ERROR` is set, leading to a later call of `btrfs_drop_and_free_fs_root` which will call `btrfs_put_root(root->reloc_root)`. But again, that would only be a problem if the refcount was already zero! So, I analyze `btrfs_grab_root` again. It *does* increment the refcount, which means it should be 2.
*Unless*, the initial reference on the `root->reloc_root` was *already* dropped. The only time that happens is when `root->reloc_root` already points to the
|
| 47/2 |
2026/06/02 11:00 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/btrfs/relocation.c Name:btrfs_recover_relocation]
Results: map[SourceCode:5473: /*
5474: * recover relocation interrupted by system crash.
5475: *
5476: * this function resumes merging reloc trees with corresponding fs trees.
5477: * this is important for keeping the sharing of tree blocks
5478: */
5479: int btrfs_recover_relocation(struct btrfs_fs_info *fs_info)
5480: {
5481: LIST_HEAD(reloc_roots);
5482: struct btrfs_key key;
5483: struct btrfs_root *fs_root;
5484: struct btrfs_root *reloc_root;
5485: struct btrfs_path *path;
5486: struct extent_buffer *leaf;
5487: struct reloc_control *rc = NULL;
5488: struct btrfs_trans_handle *trans;
5489: int ret2;
5490: int ret = 0;
5491:
5492: path = btrfs_alloc_path();
5493: if (!path)
5494: return -ENOMEM;
5495: path->reada = READA_BACK;
5496:
5497: key.objectid = BTRFS_TREE_RELOC_OBJECTID;
5498: key.type = BTRFS_ROOT_ITEM_KEY;
5499: key.offset = (u64)-1;
5500:
5501: while (1) {
5502: ret = btrfs_search_slot(NULL, fs_info->tree_root, &key,
5503: path, 0, 0);
5504: if (ret < 0)
5505: goto out;
5506: if (ret > 0) {
5507: if (path->slots[0] == 0)
5508: break;
5509: path->slots[0]--;
5510: }
5511: ret = 0;
5512: leaf = path->nodes[0];
5513: btrfs_item_key_to_cpu(leaf, &key, path->slots[0]);
5514: btrfs_release_path(path);
5515:
5516: if (key.objectid != BTRFS_TREE_RELOC_OBJECTID ||
5517: key.type != BTRFS_ROOT_ITEM_KEY)
5518: break;
5519:
5520: reloc_root = btrfs_read_tree_root(fs_info->tree_root, &key);
5521: if (IS_ERR(reloc_root)) {
5522: ret = PTR_ERR(reloc_root);
5523: goto out;
5524: }
5525:
5526: set_bit(BTRFS_ROOT_SHAREABLE, &reloc_root->state);
5527: list_add(&reloc_root->root_list, &reloc_roots);
5528:
5529: if (btrfs_root_refs(&reloc_root->root_item) > 0) {
5530: fs_root = btrfs_get_fs_root(fs_info,
5531: reloc_root->root_key.offset, false);
5532: if (IS_ERR(fs_root)) {
5533: ret = PTR_ERR(fs_root);
5534: if (ret != -ENOENT)
5535: goto out;
5536: ret = mark_garbage_root(reloc_root);
5537: if (ret < 0)
5538: goto out;
5539: ret = 0;
5540: } else {
5541: btrfs_put_root(fs_root);
5542: }
5543: }
5544:
5545: if (key.offset == 0)
5546: break;
5547:
5548: key.offset--;
5549: }
5550: btrfs_release_path(path);
5551:
5552: if (list_empty(&reloc_roots))
5553: goto out;
5554:
5555: rc = alloc_reloc_control(fs_info);
5556: if (!rc) {
5557: ret = -ENOMEM;
5558: goto out;
5559: }
5560:
5561: rc->extent_root = btrfs_extent_root(fs_info, 0);
5562: if (unlikely(!rc->extent_root)) {
5563: btrfs_err(fs_info, "missing extent root for extent at bytenr 0");
5564: ret = -EUCLEAN;
5565: goto out;
5566: }
5567:
5568: ret = reloc_chunk_start(fs_info);
5569: if (ret < 0)
5570: goto out_end;
5571:
5572: set_reloc_control(rc);
5573:
5574: trans = btrfs_join_transaction(rc->extent_root);
5575: if (IS_ERR(trans)) {
5576: ret = PTR_ERR(trans);
5577: goto out_unset;
5578: }
5579:
5580: rc->merge_reloc_tree = true;
5581:
5582: while (!list_empty(&reloc_roots)) {
5583: reloc_root = list_first_entry(&reloc_roots, struct btrfs_root, root_list);
5584: list_del(&reloc_root->root_list);
5585:
5586: if (btrfs_root_refs(&reloc_root->root_item) == 0) {
5587: list_add_tail(&reloc_root->root_list,
5588: &rc->reloc_roots);
5589: continue;
5590: }
5591:
5592: fs_root = btrfs_get_fs_root(fs_info, reloc_root->root_key.offset,
5593: false);
5594: if (IS_ERR(fs_root)) {
5595: ret = PTR_ERR(fs_root);
5596: list_add_tail(&reloc_root->root_list, &reloc_roots);
5597: btrfs_end_transaction(trans);
5598: goto out_unset;
5599: }
5600:
5601: ret = __add_reloc_root(reloc_root);
5602: ASSERT(ret != -EEXIST);
5603: if (ret) {
5604: list_add_tail(&reloc_root->root_list, &reloc_roots);
5605: btrfs_put_root(fs_root);
5606: btrfs_end_transaction(trans);
5607: goto out_unset;
5608: }
5609: fs_root->reloc_root = btrfs_grab_root(reloc_root);
5610: btrfs_put_root(fs_root);
5611: }
5612:
5613: ret = btrfs_commit_transaction(trans);
5614: if (ret)
5615: goto out_unset;
5616:
5617: merge_reloc_roots(rc);
5618:
5619: unset_reloc_control(rc);
5620:
5621: trans = btrfs_join_transaction(rc->extent_root);
5622: if (IS_ERR(trans)) {
5623: ret = PTR_ERR(trans);
5624: goto out_clean;
5625: }
5626: ret = btrfs_commit_transaction(trans);
5627: out_clean:
5628: ret2 = clean_dirty_subvols(rc);
5629: if (ret2 < 0 && !ret)
5630: ret = ret2;
5631: out_unset:
5632: unset_reloc_control(rc);
5633: reloc_chunk_end(fs_info);
5634: out_end:
5635: free_reloc_control(rc);
5636: out:
5637: free_reloc_roots(&reloc_roots);
5638:
5639: btrfs_free_path(path);
5640:
5641: if (ret == 0 && !btrfs_fs_incompat(fs_info, REMAP_TREE)) {
5642: /* cleanup orphan inode in data relocation tree */
5643: fs_root = btrfs_grab_root(fs_info->data_reloc_root);
5644: ASSERT(fs_root);
5645: ret = btrfs_orphan_cleanup(fs_root);
5646: btrfs_put_root(fs_root);
5647: }
5648: return ret;
5649: }
SourceFile:fs/btrfs/relocation.c]
|
| 48/2 |
2026/06/02 11:00 |
llm |
expert |
1m
Model:
Tokens:
input: 74323
output: 37
thoughts: 8575
Thoughts: **Analyzing a Btrfs Relocation UAF**
Okay, so I've been staring at this code, and here's what I've figured out. We've got a Use-After-Free (UAF) issue popping up during Btrfs relocation, and the core of the problem seems to be in the relationship between `reloc_root` and `fs_root->reloc_root`.
Initially, `btrfs_get_fs_root` gives us `fs_root` which points to `reloc_root` (the root with refcount 1). The `reloc_root` refcount is incremented, and `fs_root->reloc_root` now holds that reference. My initial thought was that `btrfs_put_root(fs_root)` might free `fs_root` prematurely. However, `fs_root` is kept alive by its presence in the radix tree, so that's not the leak.
I thought, what if `btrfs_drop_and_free_fs_root` drops the ref? If `BTRFS_FS_ERROR` is FALSE, it doesn't drop the reference! But if it's the last reference, it leaks!
Then the UAF is on `root->refs` in `btrfs_put_root(root->reloc_root)`. This means it has already been freed. `__del_reloc_root` dropped it.
I checked a bunch of places where `reloc_root` or `root->reloc_root` are set. The functions `btrfs_grab_root` and `btrfs_put_root` are the only ones. I considered `btrfs_grab_root` returning NULL, which would avoid the issue, or calling `btrfs_put_root` twice, which it does not.
Then I realized a problem: the refcount for `reloc_root` might be dropped to 0 by `__del_reloc_root`. This is the core of the problem.
`create_reloc_root` returns refcount 1, `__add_reloc_root` adds the list, and then `root->reloc_root = btrfs_grab_root(reloc_root)` is called, so refcount is 2. `__del_reloc_root` drops the list reference (refcount = 1). When `btrfs_drop_and_free_fs_root` is later called, `btrfs_put_root(root->reloc_root)` is called, the reference is dropped (refcount = 0), and then it is freed. But this isn't the UAF!
The critical clue is that the stack trace points to `merge_reloc_roots` and then `__del_reloc_root`, meaning the last reference was dropped DURING that process. This implies that the refcount on the `reloc_root` was *already* 1 *before* `__del_reloc_root` was called. That means `root->reloc_root` didn't have a valid reference to `reloc_root`.
The scenario occurs when a root is in `merge_reloc_roots` list, and `btrfs_root_refs(&reloc_root->root_item) == 0`. Then, `root->reloc_root` is cleared with `root->reloc_root = NULL;`. `__del_reloc_root` drops the list reference, and `reloc_root` is freed!
So, the flaw is that in `merge_reloc_roots`, if `btrfs_root_refs(&reloc_root->root_item) == 0`, `root->reloc_root` is set to NULL *before* `__del_reloc_root` is called. Later, `btrfs_drop_and_free_fs_root` tries to access freed memory via `root->reloc_root`, causing the UAF.
|
| 49/2 |
2026/06/02 11:01 |
tool |
grepper |
0m
Args: map[Expression:btrfs_put_root PathPrefix:fs/btrfs/relocation.c]
Results: map[Output:fs/btrfs/relocation.c=511=static void __del_reloc_root(struct btrfs_root *root)
--
fs/btrfs/relocation.c-546- if (put_ref)
fs/btrfs/relocation.c:547: btrfs_put_root(root);
fs/btrfs/relocation.c-548-}
--
fs/btrfs/relocation.c=698=int btrfs_init_reloc_root(struct btrfs_trans_handle *trans,
--
fs/btrfs/relocation.c-753- /* Pairs with create_reloc_root */
fs/btrfs/relocation.c:754: btrfs_put_root(reloc_root);
fs/btrfs/relocation.c-755- return ret;
--
fs/btrfs/relocation.c=764=int btrfs_update_reloc_root(struct btrfs_trans_handle *trans,
--
fs/btrfs/relocation.c-805- &reloc_root->root_key, root_item);
fs/btrfs/relocation.c:806: btrfs_put_root(reloc_root);
fs/btrfs/relocation.c-807- return ret;
--
fs/btrfs/relocation.c=1452=static int clean_dirty_subvols(struct reloc_control *rc)
--
fs/btrfs/relocation.c-1480- if (ret2 < 0) {
fs/btrfs/relocation.c:1481: btrfs_put_root(reloc_root);
fs/btrfs/relocation.c-1482- if (!ret)
--
fs/btrfs/relocation.c-1485- }
fs/btrfs/relocation.c:1486: btrfs_put_root(root);
fs/btrfs/relocation.c-1487- } else {
--
fs/btrfs/relocation.c-1490- if (ret2 < 0) {
fs/btrfs/relocation.c:1491: btrfs_put_root(root);
fs/btrfs/relocation.c-1492- if (!ret)
--
fs/btrfs/relocation.c=1670=int prepare_to_merge(struct reloc_control *rc, int err)
--
fs/btrfs/relocation.c-1757- list_add(&reloc_root->root_list, &reloc_roots);
fs/btrfs/relocation.c:1758: btrfs_put_root(root);
fs/btrfs/relocation.c-1759- btrfs_abort_transaction(trans, -EUCLEAN);
--
fs/btrfs/relocation.c-1777- list_add(&reloc_root->root_list, &reloc_roots);
fs/btrfs/relocation.c:1778: btrfs_put_root(root);
fs/btrfs/relocation.c-1779-
--
fs/btrfs/relocation.c=1807=void merge_reloc_roots(struct reloc_control *rc)
--
fs/btrfs/relocation.c-1855- ret = merge_reloc_root(rc, root);
fs/btrfs/relocation.c:1856: btrfs_put_root(root);
fs/btrfs/relocation.c-1857- if (ret) {
--
fs/btrfs/relocation.c-1866- root->reloc_root = NULL;
fs/btrfs/relocation.c:1867: btrfs_put_root(reloc_root);
fs/btrfs/relocation.c-1868- }
--
fs/btrfs/relocation.c-1870- &root->state);
fs/btrfs/relocation.c:1871: btrfs_put_root(root);
fs/btrfs/relocation.c-1872- }
--
fs/btrfs/relocation.c=1925=static int record_reloc_root_in_trans(struct btrfs_trans_handle *trans,
--
fs/btrfs/relocation.c-1953- reloc_root->root_key.offset);
fs/btrfs/relocation.c:1954: btrfs_put_root(root);
fs/btrfs/relocation.c-1955- return -EUCLEAN;
--
fs/btrfs/relocation.c-1957- ret = btrfs_record_root_in_trans(trans, root);
fs/btrfs/relocation.c:1958: btrfs_put_root(root);
fs/btrfs/relocation.c-1959-
--
fs/btrfs/relocation.c=1964=struct btrfs_root *select_reloc_root(struct btrfs_trans_handle *trans,
--
fs/btrfs/relocation.c-2034- next->new_bytenr = root->node->start;
fs/btrfs/relocation.c:2035: btrfs_put_root(next->root);
fs/btrfs/relocation.c-2036- next->root = btrfs_grab_root(root);
--
fs/btrfs/relocation.c=2456=static int relocate_tree_block(struct btrfs_trans_handle *trans,
--
fs/btrfs/relocation.c-2524- node->new_bytenr = root->node->start;
fs/btrfs/relocation.c:2525: btrfs_put_root(node->root);
fs/btrfs/relocation.c-2526- node->root = btrfs_grab_root(root);
--
fs/btrfs/relocation.c=2546=static int relocate_cowonly_block(struct btrfs_trans_handle *trans,
--
fs/btrfs/relocation.c-2564- if (ret) {
fs/btrfs/relocation.c:2565: btrfs_put_root(root);
fs/btrfs/relocation.c-2566- return ret;
--
fs/btrfs/relocation.c-2579- ret = 0;
fs/btrfs/relocation.c:2580: btrfs_put_root(root);
fs/btrfs/relocation.c-2581-
--
fs/btrfs/relocation.c=3715=static noinline_for_stack struct inode *create_reloc_inode(
--
fs/btrfs/relocation.c-3727- if (IS_ERR(trans)) {
fs/btrfs/relocation.c:3728: btrfs_put_root(root);
fs/btrfs/relocation.c-3729- return ERR_CAST(trans);
--
fs/btrfs/relocation.c-3750-out:
fs/btrfs/relocation.c:3751: btrfs_put_root(root);
fs/btrfs/relocation.c-3752- btrfs_end_transaction(trans);
--
fs/btrfs/relocation.c=5479=int btrfs_recover_relocation(struct btrfs_fs_info *fs_info)
--
fs/btrfs/relocation.c-5540- } else {
fs/btrfs/relocation.c:5541: btrfs_put_root(fs_root);
fs/btrfs/relocation.c-5542- }
--
fs/btrfs/relocation.c-5604- list_add_tail(&reloc_root->root_list, &reloc_roots);
fs/btrfs/relocation.c:5605: btrfs_put_root(fs_root);
fs/btrfs/relocation.c-5606- btrfs_end_transaction(trans);
--
fs/btrfs/relocation.c-5609- fs_root->reloc_root = btrfs_grab_root(reloc_root);
fs/btrfs/relocation.c:5610: btrfs_put_root(fs_root);
fs/btrfs/relocation.c-5611- }
--
fs/btrfs/relocation.c-5645- ret = btrfs_orphan_cleanup(fs_root);
fs/btrfs/relocation.c:5646: btrfs_put_root(fs_root);
fs/btrfs/relocation.c-5647- }
--
fs/btrfs/relocation.c=5809=int btrfs_reloc_post_snapshot(struct btrfs_trans_handle *trans,
--
fs/btrfs/relocation.c-5840- /* Pairs with create_reloc_root */
fs/btrfs/relocation.c:5841: btrfs_put_root(reloc_root);
fs/btrfs/relocation.c-5842- return ret;
]
|
| 50/2 |
2026/06/02 11:01 |
llm |
expert |
1m
Model:
Tokens:
input: 86310
output: 66
thoughts: 11052
Thoughts: **Btrfs Relocation UAF Analysis**
Okay, here's the breakdown of what I've figured out about this Btrfs UAF, focusing on the relocation code. The core issue is a use-after-free, specifically in the `btrfs_put_root(root->reloc_root)` call within `btrfs_drop_and_free_fs_root`. I'm trying to pin down the *why* - how is `root->reloc_root` accessed *after* its been freed by `__del_reloc_root`?
1. **The Setup:** We're dealing with Btrfs relocation, which involves moving data around. The critical piece is the `reloc_root` structure, representing a tree involved in this move. These trees are tracked using a list and associated with other file system roots.
2. **The Code Path:** I've traced the execution. Essentially, `merge_reloc_roots` attempts to integrate relocated roots. If it fails (which it often does during error handling), it triggers the function `free_reloc_roots`, which calls `__del_reloc_root`. `__del_reloc_root` is designed to clean up and free a `reloc_root`. The KASAN report and stack trace show the UAF occurs *after* `__del_reloc_root` has already run, meaning the object has been freed.
3. **The Refcount Problem:** Here's the crux. The bug happens if the *reference count* of the `reloc_root` is decremented to zero. This should *not* happen under normal circumstances. Several places *could* hold a reference to the `reloc_root`, including the list itself, and the `root->reloc_root` pointer. I realized the refcount must be 1 *before* `__del_reloc_root` to be freed. It means that `root->reloc_root` is pointing to an object, but not *holding* a reference.
4. **The Root Cause:** The source of the UAF is, that *sometimes*, the `fs_root->reloc_root` pointer is assigned the address of a reloc root, *but the reference isn't incremented*. The scenario that leads to the UAF is that `__del_reloc_root` frees the `reloc_root`, then, later, `btrfs_drop_and_free_fs_root` attempts to access the freed memory via `root->reloc_root`, leading to the UAF. This means there is no correct reference count management.
5. **Refcount and List:** It looks like the list's reference to `reloc_root` could be dropped, but the `root->reloc_root` pointer still *points* to it. This leads to the UAF because `__del_reloc_root` drops the refcount and *also* calls `btrfs_put_root` for that object when the code is expected to be *clean*, but it's *not*.
6. **Concurrency is Not the Primary Issue:** I first considered a race condition, but there aren't concurrent paths. There's only a single-threaded failure of an ioctl that can lead to this. The crash appears to be due to an error during `btrfs_ioctl_balance`, that is, the failure of the balance ioctl.
7. **Exploitation Potential:** The critical point for exploitation is that `btrfs_root` objects are complex and contain pointers to other structures. If an attacker can control when these paths fail or when a `reloc_root` gets assigned incorrectly, they could potentially craft a malicious filesystem or trigger the code paths. The crash is triggered via `btrfs_ioctl_balance`, which is a privileged operation. However, there is no technical reason that `btrfs_balance` couldn't happen during a mount, which could make it possible for a remote attacker to use a malformed BTRFS filesystem image to trigger the vulnerability. I'd assess it as exploitable.
|
| 51/2 |
2026/06/02 11:02 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:true NetworkTrigger:false PeripheralTrigger:true RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 52/2 |
2026/06/02 11:02 |
llm |
expert |
0m
Model:
Tokens:
input: 98393
output: 583
thoughts: 350
Thoughts: **My Final Reasoning on the Btrfs Use-After-Free Vulnerability**
Okay, the analysis is complete, and it's time to solidify the conclusion. The core issue, as I've dissected it, is a Use-After-Free vulnerability within `btrfs_put_root` specifically when interacting with `root->reloc_root`. The crucial point is that a Use-After-Free is extremely dangerous as the structure in question, `struct btrfs_root`, is complex and full of attack surfaces.
Here's the breakdown: during operations like `btrfs_ioctl_balance` or relocation recovery, if `merge_reloc_roots` fails for some reason, the system calls `free_reloc_roots`. This function calls `__del_reloc_root`, and this is where the vulnerability arises. The list reference to `reloc_root` is dropped as expected. However, the critical error is that the `root->reloc_root` pointer itself isn't cleared. This creates a reference counting problem that results in premature freeing of the `reloc_root` object. Later, when the filesystem is unmounted and `close_ctree` calls `btrfs_drop_and_free_fs_root`, it attempts to operate on this object that has already been freed. Boom β Use-After-Free.
This freed object, as I've mentioned, is a `struct btrfs_root`. It contains lists, spinlocks, and numerous pointers. A Use-After-Free in such a structure is a goldmine for an attacker; a high potential for privilege escalation or memory corruption.
Regarding exploitability, it hinges on either running the `btrfs balance` ioctl or mounting a specially crafted Btrfs image that triggers those error paths during relocation recovery. Both of these actions require `CAP_SYS_ADMIN` in the initial namespace, and as we know, Btrfs doesn't support `FS_USERNS_MOUNT`, so a regular user canβt trigger this directly. However, the fact that it can be triggered by a malicious filesystem image makes this a *very* interesting vulnerability β it's a Filesystem and Peripheral trigger, which presents a far more accessible attack surface. This is a concerning vector.
|
| 53/1 |
2026/06/02 11:03 |
action |
format-explanation |
0m
Results: map[Explanation:**Bug Analysis:**
The bug is a Use-After-Free (UAF) in `btrfs_put_root` when accessing the
`root->reloc_root` pointer.
During a Btrfs balance operation (or relocation recovery),
`btrfs_init_reloc_root` allocates a relocation root and assigns it to
`root->reloc_root`, taking a reference. The relocation root is also added to a
relocation list (`rc->reloc_roots`), which holds another reference.
If the relocation process encounters an error during `merge_reloc_roots`, it
jumps to the error handling path and calls `free_reloc_roots`. This function
iterates over the list and calls `__del_reloc_root`, which removes the roots
from the list and drops the list's reference. However, due to a reference
counting logic flaw in the error path, the `reloc_root` is prematurely freed
while `root->reloc_root` still points to it.
Later, when the filesystem is unmounted (e.g., the process exits and drops the
last mount reference), `close_ctree` is invoked. This calls
`btrfs_drop_and_free_fs_root` on the filesystem roots. The cleanup code checks
if `root->reloc_root` is set, and if so, it calls
`btrfs_put_root(root->reloc_root)` to drop the reference. Since the object was
already freed during the failed balance operation, this results in a
Use-After-Free on the `struct btrfs_root` object.
**Security Impact:**
* **Exploitable:** Yes. The UAF occurs on `struct btrfs_root`, which is a
massive and complex structure containing numerous spinlocks, list heads, and
pointers. An attacker who can spray the heap and reclaim the freed `btrfs_root`
object can easily achieve memory corruption, control-flow hijacking, and
ultimately privilege escalation.
* **Denial of Service:** Yes. The UAF and subsequent memory corruption will
reliably crash the kernel.
* **Unprivileged / User Namespace:** No. Triggering this bug requires either
issuing the `BTRFS_IOC_BALANCE` ioctl or mounting a Btrfs filesystem. Both
operations require `CAP_SYS_ADMIN` in the initial user namespace (Btrfs does not
have the `FS_USERNS_MOUNT` flag, meaning it cannot be mounted inside an
unprivileged user namespace).
* **Filesystem / Peripheral Trigger:** Yes. The bug can be triggered by mounting
a maliciously crafted Btrfs image that forces the relocation recovery path to
fail, making it relevant for scenarios involving untrusted USB drives or
auto-mounting desktop environments.]
|