| 0/0 |
2026/07/27 01:15 |
flow |
assessment-security |
38m
Results: map[DenialOfService:true Explanation:**Bug Analysis:**
The bug is a Use-After-Free (UAF) in the XFS filesystem's online repair (scrub)
subsystem, specifically within `xfs_defer_item_unpause`.
When repairing the Reverse Mapping B-tree (rmapbt), the kernel allocates new
blocks and schedules them for automatic reaping (freeing) in case the repair
fails or is aborted. This is done by creating a deferred extent free item
(`xfs_defer_pending` or `dfp`) and pausing it. The pointer to this `dfp` is
saved in a reservation structure (`resv->autoreap.dfp`).
However, during the allocation loop in `xrep_newbt_alloc_ag_blocks`, the code
calls `xrep_defer_finish()`, which processes and finishes all deferred
operations attached to the transaction. Finishing the deferred operations frees
the `dfp` objects. Later, when `xrep_newbt_free()` is called to clean up the
reservations, it attempts to unpause the previously saved `dfp` by calling
`xfs_alloc_commit_autoreap()`, leading to a Use-After-Free when it accesses the
already freed `dfp`.
**Exploitability:**
Yes. The bug is a Use-After-Free on a slab object (`xfs_defer_pending` of size
64). An attacker who can trigger this bug could potentially spray the heap to
reallocate the freed 64-byte object with controlled data before it is accessed
by `xfs_defer_item_unpause()`. This could lead to memory corruption,
control-flow hijacking, or privilege escalation.
**Denial of Service:**
Yes. The UAF can easily cause memory corruption that leads to kernel panics,
crashing the system.
**Access and Triggers:**
* **Unprivileged / User Namespaces:** No. The bug is triggered via the
`XFS_IOC_SCRUB_METADATA` or `XFS_IOC_SCRUBV_METADATA` ioctls. Looking at the
source code for these ioctls (`xfs_ioc_scrub_metadata` and
`xfs_ioc_scrubv_metadata`), they both explicitly check for the `CAP_SYS_ADMIN`
capability using `capable(CAP_SYS_ADMIN)`. The `capable()` function checks
against the initial user namespace, meaning this cannot be bypassed by
unprivileged users creating a new user namespace.
* **Filesystem Trigger:** No. While it is an XFS bug, it requires an active
process to issue specific online repair ioctls; simply mounting a malicious
filesystem is not enough to trigger the repair path automatically.
* **Remote / Network / Peripheral / VM Triggers:** No. The bug is strictly local
and requires root privileges to execute the specific XFS ioctls. Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/07/27 01:15 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/07/27 01:15 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/ea1ec46165a34622b3d5769ce1a7084d96856f42]
|
| 3/1 |
2026/07/27 01:15 |
action |
kernel-builder |
15m
Results: map[KernelObj:/app/workdir/cache/build/efb990c7b5736ea68587554a874e355bc840a171]
|
| 4/1 |
2026/07/27 01:30 |
action |
codesearch-prepare |
20m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/07/27 01:51 |
agent |
expert |
2m
Model:
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false 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, elevated privileges, or an information leak.
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.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
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:
loop0: detected capacity change from 0 to 32768
=======================================================
WARNING: The mand mount option has been deprecated and
and is ignored by this kernel. Remove the mand
option from the mount to silence this warning.
=======================================================
XFS (loop0): Mounting V5 Filesystem bfdc47fc-10d8-4eed-a562-11a831b3f791
XFS (loop0): Ending clean mount
XFS (loop0): Quotacheck needed: Please wait.
XFS (loop0): Quotacheck: Done.
loop0: detected capacity change from 32768 to 0
==================================================================
BUG: KASAN: slab-use-after-free in xfs_defer_item_unpause+0x116/0x250 fs/xfs/libxfs/xfs_defer.c:1242
Read of size 4 at addr ffff8880443214bc by task syz.0.0/5326
CPU: 0 UID: 0 PID: 5326 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
xfs_defer_item_unpause+0x116/0x250 fs/xfs/libxfs/xfs_defer.c:1242
xrep_newbt_free+0x3fe/0x5f0 fs/xfs/scrub/newbt.c:520
xrep_rmap_build_new_tree+0x5b5/0x8c0 fs/xfs/scrub/rmap_repair.c:1447
xrep_rmapbt+0x78/0xb0 fs/xfs/scrub/rmap_repair.c:1722
xrep_attempt+0x184/0x7c0 fs/xfs/scrub/repair.c:78
xfs_scrub_metadata+0xce4/0x1910 fs/xfs/scrub/scrub.c:747
xfs_ioc_scrubv_metadata+0x7ac/0xb70 fs/xfs/scrub/scrub.c:981
xfs_file_ioctl+0x916/0x1590 fs/xfs/xfs_ioctl.c:1308
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
RIP: 0033:0x7f7335b9de99
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f7336b4cfe8 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007f7335e25fa0 RCX: 00007f7335b9de99
RDX: 0000200000000000 RSI: 00000000c0285840 RDI: 0000000000000007
RBP: 00007f7335c33eaf R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f7335e26038 R14: 00007f7335e25fa0 R15: 00007ffde61731e8
</TASK>
Allocated by task 5326:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
unpoison_slab_object mm/kasan/common.c:340 [inline]
__kasan_slab_alloc+0x6c/0x80 mm/kasan/common.c:366
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4584 [inline]
slab_alloc_node mm/slub.c:4917 [inline]
kmem_cache_alloc_noprof+0x2a0/0x5f0 mm/slub.c:4931
xfs_defer_alloc fs/xfs/libxfs/xfs_defer.c:829 [inline]
xfs_defer_add+0x143/0x480 fs/xfs/libxfs/xfs_defer.c:857
xfs_extent_free_defer_add+0x1bf/0x3b0 fs/xfs/xfs_extfree_item.c:507
xfs_defer_extent_free+0x2a8/0x400 fs/xfs/libxfs/xfs_alloc.c:2682
xfs_alloc_schedule_autoreap+0xb0/0x170 fs/xfs/libxfs/xfs_alloc.c:2727
xrep_newbt_add_blocks+0x239/0x410 fs/xfs/scrub/newbt.c:209
xrep_newbt_alloc_ag_blocks+0x81e/0xcc0 fs/xfs/scrub/newbt.c:309
xrep_rmap_try_reserve+0x1f4/0x800 fs/xfs/scrub/rmap_repair.c:1068
xrep_rmap_reserve_space+0x19a/0x470 fs/xfs/scrub/rmap_repair.c:1162
xrep_rmap_build_new_tree+0x3d9/0x8c0 fs/xfs/scrub/rmap_repair.c:1369
xrep_rmapbt+0x78/0xb0 fs/xfs/scrub/rmap_repair.c:1722
xrep_attempt+0x184/0x7c0 fs/xfs/scrub/repair.c:78
xfs_scrub_metadata+0xce4/0x1910 fs/xfs/scrub/scrub.c:747
xfs_ioc_scrubv_metadata+0x7ac/0xb70 fs/xfs/scrub/scrub.c:981
xfs_file_ioctl+0x916/0x1590 fs/xfs/xfs_ioctl.c:1308
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
Freed by task 5326:
kasan_save_stack mm/kasan/common.c:57 [inline]
kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
kasan_save_free_info+0x40/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:2677 [inline]
slab_free mm/slub.c:6377 [inline]
kmem_cache_free+0x182/0x650 mm/slub.c:6504
xfs_defer_cancel_list fs/xfs/libxfs/xfs_defer.c:504 [inline]
xfs_defer_finish_noroll+0xde4/0x1320 fs/xfs/libxfs/xfs_defer.c:723
xfs_defer_finish+0x1c/0x180 fs/xfs/libxfs/xfs_defer.c:741
xrep_defer_finish+0x16e/0x240 fs/xfs/scrub/repair.c:242
xrep_newbt_alloc_ag_blocks+0x86c/0xcc0 fs/xfs/scrub/newbt.c:316
xrep_rmap_try_reserve+0x1f4/0x800 fs/xfs/scrub/rmap_repair.c:1068
xrep_rmap_reserve_space+0x19a/0x470 fs/xfs/scrub/rmap_repair.c:1162
xrep_rmap_build_new_tree+0x3d9/0x8c0 fs/xfs/scrub/rmap_repair.c:1369
xrep_rmapbt+0x78/0xb0 fs/xfs/scrub/rmap_repair.c:1722
xrep_attempt+0x184/0x7c0 fs/xfs/scrub/repair.c:78
xfs_scrub_metadata+0xce4/0x1910 fs/xfs/scrub/scrub.c:747
xfs_ioc_scrubv_metadata+0x7ac/0xb70 fs/xfs/scrub/scrub.c:981
xfs_file_ioctl+0x916/0x1590 fs/xfs/xfs_ioctl.c:1308
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 ffff888044321480
which belongs to the cache xfs_defer_pending of size 64
The buggy address is located 60 bytes inside of
freed 64-byte region [ffff888044321480, ffff8880443214c0)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff8880443216c0 pfn:0x44321
flags: 0x4fff00000000200(workingset|node=1|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 04fff00000000200 ffff888030ba9c80 ffff8880305a9ac8 ffff8880305a9ac8
raw: ffff8880443216c0 00000008002a0012 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0xd2c40(GFP_NOFS|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 12, tgid 12 (kworker/u4:0), ts 94710883881, free_ts 90574972561
set_page_owner include/linux/page_owner.h:32 [inline]
post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1859
prep_new_page mm/page_alloc.c:1867 [inline]
get_page_from_freelist+0x21fa/0x2270 mm/page_alloc.c:3946
__alloc_frozen_pages_noprof+0x18d/0x380 mm/page_alloc.c:5304
alloc_slab_page mm/slub.c:3266 [inline]
allocate_slab+0x79/0x5e0 mm/slub.c:3380
new_slab mm/slub.c:3426 [inline]
refill_objects+0x2d5/0x350 mm/slub.c:7310
refill_sheaf mm/slub.c:2804 [inline]
__pcs_replace_empty_main+0x2bf/0x6b0 mm/slub.c:4675
alloc_from_pcs mm/slub.c:4773 [inline]
slab_alloc_node mm/slub.c:4905 [inline]
kmem_cache_alloc_noprof+0x382/0x5f0 mm/slub.c:4931
xfs_defer_alloc fs/xfs/libxfs/xfs_defer.c:829 [inline]
xfs_defer_add+0x143/0x480 fs/xfs/libxfs/xfs_defer.c:857
xfs_bmap_add_extent_hole_real+0xd61/0x1a40 fs/xfs/libxfs/xfs_bmap.c:2782
xfs_bmapi_allocate+0x248c/0x2f20 fs/xfs/libxfs/xfs_bmap.c:3975
xfs_bmapi_write+0x843/0x1330 fs/xfs/libxfs/xfs_bmap.c:4265
xfs_dquot_disk_alloc+0x4af/0xae0 fs/xfs/xfs_dquot.c:381
xfs_qm_dqread+0xfe/0x520 fs/xfs/xfs_dquot.c:720
xfs_qm_dqget+0x18a/0x3d0 fs/xfs/xfs_dquot.c:922
xfs_qm_quotacheck_dqadjust+0xdd/0x5a0 fs/xfs/xfs_qm.c:1260
xfs_qm_dqusage_adjust+0x4ad/0x670 fs/xfs/xfs_qm.c:1389
page last free pid 5284 tgid 5284 stack trace:
reset_page_owner include/linux/page_owner.h:25 [inline]
__free_pages_prepare mm/page_alloc.c:1406 [inline]
__free_frozen_pages+0xc1e/0xd10 mm/page_alloc.c:2950
__slab_free+0x274/0x2c0 mm/slub.c:5741
qlink_free mm/kasan/quarantine.c:163 [inline]
qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
__kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:350
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4584 [inline]
slab_alloc_node mm/slub.c:4917 [inline]
kmem_cache_alloc_node_noprof+0x355/0x610 mm/slub.c:4989
__alloc_skb+0x1d7/0x7a0 net/core/skbuff.c:704
alloc_skb include/linux/skbuff.h:1384 [inline]
nlmsg_new include/net/netlink.h:1055 [inline]
netlink_ack+0x136/0xb30 net/netlink/af_netlink.c:2493
netlink_rcv_skb+0x2a4/0x4a0 net/netlink/af_netlink.c:2562
netlink_unicast_kernel net/netlink/af_netlink.c:1319 [inline]
netlink_unicast+0x7bb/0x940 net/netlink/af_netlink.c:1345
netlink_sendmsg+0x813/0xb40 net/netlink/af_netlink.c:1900
sock_sendmsg_nosec+0x13a/0x180 net/socket.c:775
__sock_sendmsg net/socket.c:790 [inline]
__sys_sendto+0x408/0x5a0 net/socket.c:2252
__do_sys_sendto net/socket.c:2259 [inline]
__se_sys_sendto net/socket.c:2255 [inline]
__x64_sys_sendto+0xde/0x100 net/socket.c:2255
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
Memory state around the buggy address:
ffff888044321380: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
ffff888044321400: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc
>ffff888044321480: fa fb fb fb fb fb fb fb fc fc fc fc fa fb fb fb
^
ffff888044321500: fb fb fb fb fc fc fc fc fa fb fb fb fb fb fb fb
ffff888044321580: fc fc fc fc fa fb fb fb fb fb fb fb fc fc fc fc
==================================================================
Reply: **Bug Analysis:**
The bug is a Use-After-Free (UAF) in the XFS filesystem's online repair (scrub) subsystem, specifically within `xfs_defer_item_unpause`.
When repairing the Reverse Mapping B-tree (rmapbt), the kernel allocates new blocks and schedules them for automatic reaping (freeing) in case the repair fails or is aborted. This is done by creating a deferred extent free item (`xfs_defer_pending` or `dfp`) and pausing it. The pointer to this `dfp` is saved in a reservation structure (`resv->autoreap.dfp`).
However, during the allocation loop in `xrep_newbt_alloc_ag_blocks`, the code calls `xrep_defer_finish()`, which processes and finishes all deferred operations attached to the transaction. Finishing the deferred operations frees the `dfp` objects. Later, when `xrep_newbt_free()` is called to clean up the reservations, it attempts to unpause the previously saved `dfp` by calling `xfs_alloc_commit_autoreap()`, leading to a Use-After-Free when it accesses the already freed `dfp`.
**Exploitability:**
Yes. The bug is a Use-After-Free on a slab object (`xfs_defer_pending` of size 64). An attacker who can trigger this bug could potentially spray the heap to reallocate the freed 64-byte object with controlled data before it is accessed by `xfs_defer_item_unpause()`. This could lead to memory corruption, control-flow hijacking, or privilege escalation.
**Denial of Service:**
Yes. The UAF can easily cause memory corruption that leads to kernel panics, crashing the system.
**Access and Triggers:**
* **Unprivileged / User Namespaces:** No. The bug is triggered via the `XFS_IOC_SCRUB_METADATA` or `XFS_IOC_SCRUBV_METADATA` ioctls. Looking at the source code for these ioctls (`xfs_ioc_scrub_metadata` and `xfs_ioc_scrubv_metadata`), they both explicitly check for the `CAP_SYS_ADMIN` capability using `capable(CAP_SYS_ADMIN)`. The `capable()` function checks against the initial user namespace, meaning this cannot be bypassed by unprivileged users creating a new user namespace.
* **Filesystem Trigger:** No. While it is an XFS bug, it requires an active process to issue specific online repair ioctls; simply mounting a malicious filesystem is not enough to trigger the repair path automatically.
* **Remote / Network / Peripheral / VM Triggers:** No. The bug is strictly local and requires root privileges to execute the specific XFS ioctls.
|
| 6/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 11321
output: 28
thoughts: 512
Thoughts: **Analyzing a KASAN Slab-Use-After-Free in XFS Scrub Code**
Okay, so I've got a KASAN report pointing me at a slab-use-after-free in `xfs_defer_item_unpause` within the XFS filesystem code. The report specifically flags `fs/xfs/libxfs/xfs_defer.c:1242` as the culprit, and the task involved is `syz.0.0`, indicating this is probably triggered by a syzkaller test case.
The crash occurs deep within the `xfs_ioc_scrubv_metadata` path. Digging into the call stack, it seems like the sequence is something like this: `xfs_ioc_scrubv_metadata` -> `xfs_scrub_metadata` -> `xrep_attempt` -> `xrep_rmapbt` -> `xrep_rmap_build_new_tree` -> `xrep_newbt_free` and finally down to where it goes wrong, `xfs_defer_item_unpause`. So, it's clear the problem is rooted in the XFS scrub/repair subsystem, which is not great news, but at least we can narrow the scope of the investigation.
This suggests that an issue is cropping up during metadata scrubbing. Remember that XFS scrub is initiated via ioctls like `XFS_IOC_SCRUB_METADATA` or `XFS_IOC_SCRUBV_METADATA`. Now, before I dive deeper into the code, I need to know if these ioctls require any special privileges. If so, that could influence how the bug manifests and possibly point to a security angle. Let me check the kernel documentation to verify this first.
|
| 7/2 |
2026/07/27 01:51 |
tool |
codesearch-find-references |
0m
Args: map[Name:xfs_ioc_scrubv_metadata]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:xfs_file_ioctl SourceFile:fs/xfs/xfs_ioctl.c SourceLine:1308]] TruncatedOutput:false]
|
| 8/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 12316
output: 41
thoughts: 26
Thoughts: Here's a summary of my thought process, as requested:
**Diving into `xfs_file_ioctl`**
Okay, time to get my hands dirty. I need to understand what's happening with file IOCTLs in XFS, so I should start by looking directly at the source code. Let's start with `xfs_file_ioctl` function. It's in `fs/xfs/xfs_ioctl.c`, so I'll navigate there. That's the logical starting point for investigating file-specific IOCTLs in the XFS filesystem. Let's see what the implementation details look like, and get a better understanding of how the various commands are handled. I expect to find a switch statement or series of if-else conditions based on the `cmd` field, handling various IOCTL requests. This should give me a good lay of the land, from which I can branch off into more specific areas as needed.
|
| 9/2 |
2026/07/27 01:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/xfs_ioctl.c Name:xfs_file_ioctl]
Results: map[SourceCode:1212: /*
1213: * Note: some of the ioctl's return positive numbers as a
1214: * byte count indicating success, such as readlink_by_handle.
1215: * So we don't "sign flip" like most other routines. This means
1216: * true errors need to be returned as a negative value.
1217: */
1218: long
1219: xfs_file_ioctl(
1220: struct file *filp,
1221: unsigned int cmd,
1222: unsigned long p)
1223: {
1224: struct inode *inode = file_inode(filp);
1225: struct xfs_inode *ip = XFS_I(inode);
1226: struct xfs_mount *mp = ip->i_mount;
1227: void __user *arg = (void __user *)p;
1228: int error;
1229:
1230: trace_xfs_file_ioctl(ip);
1231:
1232: switch (cmd) {
1233: case FITRIM:
1234: return xfs_ioc_trim(mp, arg);
1235: case FS_IOC_GETFSLABEL:
1236: return xfs_ioc_getlabel(mp, arg);
1237: case FS_IOC_SETFSLABEL:
1238: return xfs_ioc_setlabel(filp, mp, arg);
1239: case XFS_IOC_ALLOCSP:
1240: case XFS_IOC_FREESP:
1241: case XFS_IOC_ALLOCSP64:
1242: case XFS_IOC_FREESP64:
1243: xfs_warn_once(mp,
1244: "%s should use fallocate; XFS_IOC_{ALLOC,FREE}SP ioctl unsupported",
1245: current->comm);
1246: return -ENOTTY;
1247: case XFS_IOC_DIOINFO: {
1248: struct kstat st;
1249: struct dioattr da;
1250:
1251: error = vfs_getattr(&filp->f_path, &st, STATX_DIOALIGN, 0);
1252: if (error)
1253: return error;
1254:
1255: /*
1256: * Some userspace directly feeds the return value to
1257: * posix_memalign, which fails for values that are smaller than
1258: * the pointer size. Round up the value to not break userspace.
1259: */
1260: da.d_mem = roundup(st.dio_mem_align, sizeof(void *));
1261: da.d_miniosz = st.dio_offset_align;
1262: da.d_maxiosz = INT_MAX & ~(da.d_miniosz - 1);
1263: if (copy_to_user(arg, &da, sizeof(da)))
1264: return -EFAULT;
1265: return 0;
1266: }
1267:
1268: case XFS_IOC_FSBULKSTAT_SINGLE:
1269: case XFS_IOC_FSBULKSTAT:
1270: case XFS_IOC_FSINUMBERS:
1271: return xfs_ioc_fsbulkstat(filp, cmd, arg);
1272:
1273: case XFS_IOC_BULKSTAT:
1274: return xfs_ioc_bulkstat(filp, cmd, arg);
1275: case XFS_IOC_INUMBERS:
1276: return xfs_ioc_inumbers(mp, cmd, arg);
1277:
1278: case XFS_IOC_FSGEOMETRY_V1:
1279: return xfs_ioc_fsgeometry(mp, arg, 3);
1280: case XFS_IOC_FSGEOMETRY_V4:
1281: return xfs_ioc_fsgeometry(mp, arg, 4);
1282: case XFS_IOC_FSGEOMETRY:
1283: return xfs_ioc_fsgeometry(mp, arg, 5);
1284:
1285: case XFS_IOC_AG_GEOMETRY:
1286: return xfs_ioc_ag_geometry(mp, arg);
1287: case XFS_IOC_RTGROUP_GEOMETRY:
1288: return xfs_ioc_rtgroup_geometry(mp, arg);
1289:
1290: case XFS_IOC_GETVERSION:
1291: return put_user(inode->i_generation, (int __user *)arg);
1292:
1293: case XFS_IOC_FSGETXATTRA:
1294: return xfs_ioc_fsgetxattra(ip, arg);
1295: case XFS_IOC_GETPARENTS:
1296: return xfs_ioc_getparents(filp, arg);
1297: case XFS_IOC_GETPARENTS_BY_HANDLE:
1298: return xfs_ioc_getparents_by_handle(filp, arg);
1299: case XFS_IOC_GETBMAP:
1300: case XFS_IOC_GETBMAPA:
1301: case XFS_IOC_GETBMAPX:
1302: return xfs_ioc_getbmap(filp, cmd, arg);
1303:
1304: case FS_IOC_GETFSMAP:
1305: return xfs_ioc_getfsmap(ip, arg);
1306:
1307: case XFS_IOC_SCRUBV_METADATA:
1308: return xfs_ioc_scrubv_metadata(filp, arg);
1309: case XFS_IOC_SCRUB_METADATA:
1310: return xfs_ioc_scrub_metadata(filp, arg);
1311:
1312: case XFS_IOC_FD_TO_HANDLE:
1313: case XFS_IOC_PATH_TO_HANDLE:
1314: case XFS_IOC_PATH_TO_FSHANDLE: {
1315: xfs_fsop_handlereq_t hreq;
1316:
1317: if (copy_from_user(&hreq, arg, sizeof(hreq)))
1318: return -EFAULT;
1319: return xfs_find_handle(cmd, &hreq);
1320: }
1321: case XFS_IOC_OPEN_BY_HANDLE: {
1322: xfs_fsop_handlereq_t hreq;
1323:
1324: if (copy_from_user(&hreq, arg, sizeof(xfs_fsop_handlereq_t)))
1325: return -EFAULT;
1326: return xfs_open_by_handle(filp, &hreq);
1327: }
1328:
1329: case XFS_IOC_READLINK_BY_HANDLE: {
1330: xfs_fsop_handlereq_t hreq;
1331:
1332: if (copy_from_user(&hreq, arg, sizeof(xfs_fsop_handlereq_t)))
1333: return -EFAULT;
1334: return xfs_readlink_by_handle(filp, &hreq);
1335: }
1336: case XFS_IOC_ATTRLIST_BY_HANDLE:
1337: return xfs_attrlist_by_handle(filp, arg);
1338:
1339: case XFS_IOC_ATTRMULTI_BY_HANDLE:
1340: return xfs_attrmulti_by_handle(filp, arg);
1341:
1342: case XFS_IOC_SWAPEXT: {
1343: struct xfs_swapext sxp;
1344:
1345: if (copy_from_user(&sxp, arg, sizeof(xfs_swapext_t)))
1346: return -EFAULT;
1347: error = mnt_want_write_file(filp);
1348: if (error)
1349: return error;
1350: error = xfs_ioc_swapext(&sxp);
1351: mnt_drop_write_file(filp);
1352: return error;
1353: }
1354:
1355: case XFS_IOC_FSCOUNTS:
1356: return xfs_ioctl_fs_counts(mp, arg);
1357:
1358: case XFS_IOC_SET_RESBLKS:
1359: case XFS_IOC_GET_RESBLKS:
1360: return xfs_ioctl_getset_resblocks(filp, cmd, arg);
1361:
1362: case XFS_IOC_FSGROWFSDATA: {
1363: struct xfs_growfs_data in;
1364:
1365: if (copy_from_user(&in, arg, sizeof(in)))
1366: return -EFAULT;
1367:
1368: error = mnt_want_write_file(filp);
1369: if (error)
1370: return error;
1371: error = xfs_growfs_data(mp, &in);
1372: mnt_drop_write_file(filp);
1373: return error;
1374: }
1375:
1376: case XFS_IOC_FSGROWFSLOG: {
1377: struct xfs_growfs_log in;
1378:
1379: if (copy_from_user(&in, arg, sizeof(in)))
1380: return -EFAULT;
1381:
1382: error = mnt_want_write_file(filp);
1383: if (error)
1384: return error;
1385: error = xfs_growfs_log(mp, &in);
1386: mnt_drop_write_file(filp);
1387: return error;
1388: }
1389:
1390: case XFS_IOC_FSGROWFSRT: {
1391: xfs_growfs_rt_t in;
1392:
1393: if (copy_from_user(&in, arg, sizeof(in)))
1394: return -EFAULT;
1395:
1396: error = mnt_want_write_file(filp);
1397: if (error)
1398: return error;
1399: error = xfs_growfs_rt(mp, &in);
1400: mnt_drop_write_file(filp);
1401: return error;
1402: }
1403:
1404: case XFS_IOC_GOINGDOWN: {
1405: uint32_t in;
1406:
1407: if (!capable(CAP_SYS_ADMIN))
1408: return -EPERM;
1409:
1410: if (get_user(in, (uint32_t __user *)arg))
1411: return -EFAULT;
1412:
1413: return xfs_fs_goingdown(mp, in);
1414: }
1415:
1416: case XFS_IOC_ERROR_INJECTION: {
1417: xfs_error_injection_t in;
1418:
1419: if (!capable(CAP_SYS_ADMIN))
1420: return -EPERM;
1421:
1422: if (copy_from_user(&in, arg, sizeof(in)))
1423: return -EFAULT;
1424:
1425: return xfs_errortag_add(mp, in.errtag);
1426: }
1427:
1428: case XFS_IOC_ERROR_CLEARALL:
1429: if (!capable(CAP_SYS_ADMIN))
1430: return -EPERM;
1431:
1432: return xfs_errortag_clearall(mp);
1433:
1434: case XFS_IOC_FREE_EOFBLOCKS: {
1435: struct xfs_fs_eofblocks eofb;
1436: struct xfs_icwalk icw;
1437:
1438: if (!capable(CAP_SYS_ADMIN))
1439: return -EPERM;
1440:
1441: if (xfs_is_readonly(mp))
1442: return -EROFS;
1443:
1444: if (copy_from_user(&eofb, arg, sizeof(eofb)))
1445: return -EFAULT;
1446:
1447: error = xfs_fs_eofblocks_from_user(&eofb, &icw);
1448: if (error)
1449: return error;
1450:
1451: trace_xfs_ioc_free_eofblocks(mp, &icw, _RET_IP_);
1452:
1453: guard(super_write)(mp->m_super);
1454: return xfs_blockgc_free_space(mp, &icw);
1455: }
1456:
1457: case XFS_IOC_EXCHANGE_RANGE:
1458: return xfs_ioc_exchange_range(filp, arg);
1459: case XFS_IOC_START_COMMIT:
1460: return xfs_ioc_start_commit(filp, arg);
1461: case XFS_IOC_COMMIT_RANGE:
1462: return xfs_ioc_commit_range(filp, arg);
1463:
1464: case XFS_IOC_HEALTH_MONITOR:
1465: return xfs_ioc_health_monitor(filp, arg);
1466: case XFS_IOC_VERIFY_MEDIA:
1467: return xfs_ioc_verify_media(filp, arg);
1468:
1469: default:
1470: return -ENOTTY;
1471: }
1472: }
SourceFile:fs/xfs/xfs_ioctl.c]
|
| 10/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 16155
output: 46
thoughts: 57
Thoughts: Here's my analysis of the situation:
**Investigating a KASAN Slab-Use-After-Free in XFS**
Okay, so we've got a nasty one: a KASAN slab-use-after-free, and it seems to be in `xfs_defer_item_unpause`. My initial thought is to trace the root cause, and that almost certainly means looking closely at how `xfs_ioc_scrubv_metadata` interacts with `xfs_defer_item_unpause`. We need to understand the connection between that ioctl and the deferred item mechanism to see how a use-after-free could possibly manifest. Let's dig into `xfs_ioc_scrubv_metadata` and start unraveling this. I need to figure out where the vulnerability lies within the interaction of these two components.
|
| 11/2 |
2026/07/27 01:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/scrub/scrub.c Name:xfs_ioc_scrubv_metadata]
Results: map[SourceCode: 893: /* Vectored scrub implementation to reduce ioctl calls. */
894: int
895: xfs_ioc_scrubv_metadata(
896: struct file *file,
897: void __user *arg)
898: {
899: struct xfs_scrub_vec_head head;
900: struct xfs_scrub_vec_head __user *uhead = arg;
901: struct xfs_scrub_vec *vectors;
902: struct xfs_scrub_vec __user *uvectors;
903: struct xfs_inode *ip_in = XFS_I(file_inode(file));
904: struct xfs_mount *mp = ip_in->i_mount;
905: struct xfs_inode *handle_ip = NULL;
906: struct xfs_scrub_vec *v;
907: size_t vec_bytes;
908: unsigned int i;
909: int error = 0;
910:
911: if (!capable(CAP_SYS_ADMIN))
912: return -EPERM;
913:
914: if (copy_from_user(&head, uhead, sizeof(head)))
915: return -EFAULT;
916:
917: if (head.svh_reserved)
918: return -EINVAL;
919: if (head.svh_flags & ~XFS_SCRUB_VEC_FLAGS_ALL)
920: return -EINVAL;
921: if (head.svh_nr == 0)
922: return 0;
923:
924: vec_bytes = array_size(head.svh_nr, sizeof(struct xfs_scrub_vec));
925: if (vec_bytes > PAGE_SIZE)
926: return -ENOMEM;
927:
928: uvectors = u64_to_user_ptr(head.svh_vectors);
929: vectors = memdup_user(uvectors, vec_bytes);
930: if (IS_ERR(vectors))
931: return PTR_ERR(vectors);
932:
933: trace_xchk_scrubv_start(ip_in, &head);
934:
935: for (i = 0, v = vectors; i < head.svh_nr; i++, v++) {
936: if (v->sv_reserved) {
937: error = -EINVAL;
938: goto out_free;
939: }
940:
941: if (v->sv_type == XFS_SCRUB_TYPE_BARRIER &&
942: (v->sv_flags & ~XFS_SCRUB_FLAGS_OUT)) {
943: error = -EINVAL;
944: goto out_free;
945: }
946:
947: trace_xchk_scrubv_item(mp, &head, i, v);
948: }
949:
950: /*
951: * If the caller wants us to do a scrub-by-handle and the file used to
952: * call the ioctl is not the same file, load the incore inode and pin
953: * it across all the scrubv actions to avoid repeated UNTRUSTED
954: * lookups. The reference is not passed to deeper layers of scrub
955: * because each scrubber gets to decide its own strategy and return
956: * values for getting an inode.
957: */
958: if (head.svh_ino && head.svh_ino != I_INO(ip_in))
959: handle_ip = xchk_scrubv_open_by_handle(mp, &head);
960:
961: /* Run all the scrubbers. */
962: for (i = 0, v = vectors; i < head.svh_nr; i++, v++) {
963: struct xfs_scrub_metadata sm = {
964: .sm_type = v->sv_type,
965: .sm_flags = v->sv_flags,
966: .sm_ino = head.svh_ino,
967: .sm_gen = head.svh_gen,
968: .sm_agno = head.svh_agno,
969: };
970:
971: if (v->sv_type == XFS_SCRUB_TYPE_BARRIER) {
972: v->sv_ret = xfs_scrubv_check_barrier(mp, vectors, v);
973: if (v->sv_ret) {
974: trace_xchk_scrubv_barrier_fail(mp, &head, i, v);
975: break;
976: }
977:
978: continue;
979: }
980:
981: v->sv_ret = xfs_scrub_metadata(file, &sm);
982: v->sv_flags = sm.sm_flags;
983:
984: trace_xchk_scrubv_outcome(mp, &head, i, v);
985:
986: if (head.svh_rest_us) {
987: ktime_t expires;
988:
989: expires = ktime_add_ns(ktime_get(),
990: head.svh_rest_us * 1000);
991: set_current_state(TASK_KILLABLE);
992: schedule_hrtimeout(&expires, HRTIMER_MODE_ABS);
993: }
994:
995: if (fatal_signal_pending(current)) {
996: error = -EINTR;
997: goto out_free;
998: }
999: }
1000:
1001: if (copy_to_user(uvectors, vectors, vec_bytes) ||
1002: copy_to_user(uhead, &head, sizeof(head))) {
1003: error = -EFAULT;
1004: goto out_free;
1005: }
1006:
1007: out_free:
1008: if (handle_ip)
1009: xfs_irele(handle_ip);
1010: kfree(vectors);
1011: return error;
1012: }
SourceFile:fs/xfs/scrub/scrub.c]
|
| 12/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 18905
output: 45
thoughts: 97
Thoughts: **XFS Scrub Metadata Capability Requirements**
Okay, so I'm diving into this XFS scrub code, specifically looking at `xfs_ioc_scrubv_metadata`. My first thought is, "Aha! They've used `capable(CAP_SYS_ADMIN)` right off the bat." Meaning, this function needs the full `CAP_SYS_ADMIN` capability in the *initial* user namespace to work. No namespace scoping, that's interesting. I wonder if this is consistent across the codebase, or if there is something else at play here. Let me quickly double-check that this is the same case in `xfs_ioc_scrub_metadata` as well, just to be sure. Consistency is key, after all.
|
| 13/2 |
2026/07/27 01:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/scrub/scrub.c Name:xfs_ioc_scrub_metadata]
Results: map[SourceCode: 799: /* Scrub one aspect of one piece of metadata. */
800: int
801: xfs_ioc_scrub_metadata(
802: struct file *file,
803: void __user *arg)
804: {
805: struct xfs_scrub_metadata scrub;
806: int error;
807:
808: if (!capable(CAP_SYS_ADMIN))
809: return -EPERM;
810:
811: if (copy_from_user(&scrub, arg, sizeof(scrub)))
812: return -EFAULT;
813:
814: error = xfs_scrub_metadata(file, &scrub);
815: if (error)
816: return error;
817:
818: if (copy_to_user(arg, &scrub, sizeof(scrub)))
819: return -EFAULT;
820:
821: return 0;
822: }
SourceFile:fs/xfs/scrub/scrub.c]
|
| 14/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 18860
output: 27
thoughts: 308
Thoughts: **Analyzing the XFS Scrub Bug**
Okay, so I'm looking at this KASAN slab-use-after-free bug in `xfs_defer_item_unpause`. Seems like it's triggered via `xfs_ioc_scrubv_metadata`, which then calls `xfs_scrub_metadata`. The immediate red flag is that both `xfs_ioc_scrubv_metadata` and `xfs_scrub_metadata` have a `CAP_SYS_ADMIN` check:
```c
if (!capable(CAP_SYS_ADMIN))
return -EPERM;
```
This *should* mean root or a suitably privileged user in the initial namespace is required. But wait, is it actually the *initial* namespace? I know `capable()` can be tricky. Gotta verify that. Right, `capable()` is defined in `include/linux/capability.h`. It does indeed seem to check `ns_capable(&init_user_ns, cap)`. Good, confirming that it's the initial namespace.
Now, let's think. We know the vulnerability is in `xfs_defer_item_unpause`. That means even if `xfs_scrub_metadata` itself is protected, is there *another* way to get to `xfs_defer_item_unpause`? I need to see all the call sites of `xfs_defer_item_unpause` and determine if there's any other way in. Time to go dig through the code.
|
| 15/2 |
2026/07/27 01:51 |
tool |
codesearch-find-references |
0m
Args: map[Name:xfs_defer_item_unpause]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:xfs_alloc_cancel_autoreap SourceFile:fs/xfs/libxfs/xfs_alloc.c SourceLine:2763] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:xfs_alloc_commit_autoreap SourceFile:fs/xfs/libxfs/xfs_alloc.c SourceLine:2778]] TruncatedOutput:false]
|
| 16/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 19362
output: 48
thoughts: 0
|
| 17/2 |
2026/07/27 01:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/libxfs/xfs_defer.c Name:xfs_defer_item_unpause]
Results: map[SourceCode:1231: /*
1232: * Release a paused deferred work item so that it will be finished during the
1233: * next transaction roll.
1234: */
1235: void
1236: xfs_defer_item_unpause(
1237: struct xfs_trans *tp,
1238: struct xfs_defer_pending *dfp)
1239: {
1240: ASSERT(dfp->dfp_flags & XFS_DEFER_PAUSED);
1241:
1242: dfp->dfp_flags &= ~XFS_DEFER_PAUSED;
1243:
1244: trace_xfs_defer_item_unpause(tp->t_mountp, dfp);
1245: }
SourceFile:fs/xfs/libxfs/xfs_defer.c]
|
| 18/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 19668
output: 43
thoughts: 0
|
| 19/2 |
2026/07/27 01:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/scrub/newbt.c Name:xrep_newbt_free]
Results: map[SourceCode: 471: /* Free all the accounting info and disk space we reserved for a new btree. */
472: STATIC int
473: xrep_newbt_free(
474: struct xrep_newbt *xnr,
475: bool btree_committed)
476: {
477: struct xfs_scrub *sc = xnr->sc;
478: struct xrep_newbt_resv *resv, *n;
479: unsigned int freed = 0;
480: int error = 0;
481:
482: /*
483: * If the filesystem already went down, we can't free the blocks. Skip
484: * ahead to freeing the incore metadata because we can't fix anything.
485: */
486: if (xfs_is_shutdown(sc->mp))
487: goto junkit;
488:
489: list_for_each_entry_safe(resv, n, &xnr->resv_list, list) {
490: int ret;
491:
492: ret = xrep_newbt_free_extent(xnr, resv, btree_committed);
493: list_del(&resv->list);
494: xfs_perag_put(resv->pag);
495: kfree(resv);
496: if (ret < 0) {
497: error = ret;
498: goto junkit;
499: }
500:
501: freed += ret;
502: if (freed >= XREP_MAX_ITRUNCATE_EFIS) {
503: error = xrep_defer_finish(sc);
504: if (error)
505: goto junkit;
506: freed = 0;
507: }
508: }
509:
510: if (freed)
511: error = xrep_defer_finish(sc);
512:
513: junkit:
514: /*
515: * If we still have reservations attached to @newbt, cleanup must have
516: * failed and the filesystem is about to go down. Clean up the incore
517: * reservations and try to commit to freeing the space we used.
518: */
519: list_for_each_entry_safe(resv, n, &xnr->resv_list, list) {
520: xfs_alloc_commit_autoreap(sc->tp, &resv->autoreap);
521: list_del(&resv->list);
522: xfs_perag_put(resv->pag);
523: kfree(resv);
524: }
525:
526: if (sc->ip) {
527: kmem_cache_free(xfs_ifork_cache, xnr->ifake.if_fork);
528: xnr->ifake.if_fork = NULL;
529: }
530:
531: return error;
532: }
SourceFile:fs/xfs/scrub/newbt.c]
|
| 20/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 20669
output: 48
thoughts: 0
|
| 21/2 |
2026/07/27 01:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/libxfs/xfs_alloc.c Name:xfs_alloc_commit_autoreap]
Results: map[SourceCode:2766: /*
2767: * Commit automatic freeing of unwritten space in the filesystem.
2768: *
2769: * This unpauses an earlier _schedule_autoreap and commits to freeing the
2770: * allocated space. Call this if none of the reserved space was used.
2771: */
2772: void
2773: xfs_alloc_commit_autoreap(
2774: struct xfs_trans *tp,
2775: struct xfs_alloc_autoreap *aarp)
2776: {
2777: if (aarp->dfp)
2778: xfs_defer_item_unpause(tp, aarp->dfp);
2779: }
SourceFile:fs/xfs/libxfs/xfs_alloc.c]
|
| 22/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 21962
output: 47
thoughts: 382
Thoughts: **UAF Bug Investigation: Deep Dive into xfs_defer_item_unpause**
Okay, so I've got a Use-After-Free (UAF) on `dfp` within `xfs_defer_item_unpause`. Right away, I see that `dfp` is actually `aarp->dfp`. Let me get this straight, `aarp` is, uh-huh, `&resv->autoreap`, and `resv` is a `struct xrep_newbt_resv`. The core issue seems to be that `dfp` is being freed prematurely.
Let's dissect the free stack trace. Okay, I see `xfs_defer_cancel_list` being called, then `xfs_defer_finish_noroll`, `xfs_defer_finish`, and finally, `xrep_defer_finish`. And look, `xrep_rmap_try_reserve` calls `xrep_newbt_alloc_ag_blocks`.
Hold on... *wait a second*. `xrep_newbt_alloc_ag_blocks` *itself* calls `xrep_defer_finish`! And `xrep_defer_finish` calls `xfs_defer_finish`, which, as we see, cancels the defer list and *freed* `dfp`. This is where it gets interesting... it seems like `xrep_newbt_alloc_ag_blocks` is inadvertently freeing the very memory it needs later on! That's a classic race condition or improper ordering, isn't it? Let's verify that hypothesis. Next stop: I need to take a closer look at `xrep_newbt_alloc_ag_blocks` to confirm this unexpected behavior.
|
| 23/2 |
2026/07/27 01:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/scrub/newbt.c Name:xrep_newbt_alloc_ag_blocks]
Results: map[SourceCode: 262: /* Allocate disk space for a new per-AG btree. */
263: STATIC int
264: xrep_newbt_alloc_ag_blocks(
265: struct xrep_newbt *xnr,
266: uint64_t nr_blocks)
267: {
268: struct xfs_scrub *sc = xnr->sc;
269: struct xfs_mount *mp = sc->mp;
270: int error = 0;
271:
272: ASSERT(sc->sa.pag != NULL);
273: ASSERT(xnr->resv != XFS_AG_RESV_METAFILE);
274:
275: while (nr_blocks > 0) {
276: struct xfs_alloc_arg args = {
277: .tp = sc->tp,
278: .mp = mp,
279: .oinfo = xnr->oinfo,
280: .minlen = 1,
281: .maxlen = nr_blocks,
282: .prod = 1,
283: .resv = xnr->resv,
284: };
285: xfs_agnumber_t agno;
286:
287: xrep_newbt_validate_ag_alloc_hint(xnr);
288:
289: if (xnr->alloc_vextent)
290: error = xnr->alloc_vextent(sc, &args, xnr->alloc_hint);
291: else
292: error = xfs_alloc_vextent_near_bno(&args,
293: xnr->alloc_hint);
294: if (error)
295: return error;
296: if (args.fsbno == NULLFSBLOCK)
297: return -ENOSPC;
298:
299: agno = XFS_FSB_TO_AGNO(mp, args.fsbno);
300: if (agno != pag_agno(sc->sa.pag)) {
301: ASSERT(agno == pag_agno(sc->sa.pag));
302: return -EFSCORRUPTED;
303: }
304:
305: trace_xrep_newbt_alloc_ag_blocks(sc->sa.pag,
306: XFS_FSB_TO_AGBNO(mp, args.fsbno), args.len,
307: xnr->oinfo.oi_owner);
308:
309: error = xrep_newbt_add_blocks(xnr, sc->sa.pag, &args);
310: if (error)
311: return error;
312:
313: nr_blocks -= args.len;
314: xnr->alloc_hint = args.fsbno + args.len;
315:
316: error = xrep_defer_finish(sc);
317: if (error)
318: return error;
319: }
320:
321: return 0;
322: }
SourceFile:fs/xfs/scrub/newbt.c]
|
| 24/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 22348
output: 45
thoughts: 0
|
| 25/2 |
2026/07/27 01:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/scrub/newbt.c Name:xrep_newbt_add_blocks]
Results: map[SourceCode: 182: /*
183: * Designate specific blocks to be used to build our new btree. @pag must be
184: * a passive reference.
185: */
186: STATIC int
187: xrep_newbt_add_blocks(
188: struct xrep_newbt *xnr,
189: struct xfs_perag *pag,
190: const struct xfs_alloc_arg *args)
191: {
192: struct xfs_mount *mp = xnr->sc->mp;
193: struct xrep_newbt_resv *resv;
194: int error;
195:
196: resv = kmalloc_obj(struct xrep_newbt_resv, XCHK_GFP_FLAGS);
197: if (!resv)
198: return -ENOMEM;
199:
200: INIT_LIST_HEAD(&resv->list);
201: resv->agbno = XFS_FSB_TO_AGBNO(mp, args->fsbno);
202: resv->len = args->len;
203: resv->used = 0;
204: resv->pag = xfs_perag_hold(pag);
205:
206: if (args->tp) {
207: ASSERT(xnr->oinfo.oi_offset == 0);
208:
209: error = xfs_alloc_schedule_autoreap(args,
210: XFS_FREE_EXTENT_SKIP_DISCARD, &resv->autoreap);
211: if (error)
212: goto out_pag;
213: }
214:
215: list_add_tail(&resv->list, &xnr->resv_list);
216: return 0;
217: out_pag:
218: xfs_perag_put(resv->pag);
219: kfree(resv);
220: return error;
221: }
SourceFile:fs/xfs/scrub/newbt.c]
|
| 26/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 23038
output: 48
thoughts: 0
|
| 27/2 |
2026/07/27 01:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/libxfs/xfs_alloc.c Name:xfs_alloc_schedule_autoreap]
Results: map[SourceCode:2701: /*
2702: * Set up automatic freeing of unwritten space in the filesystem.
2703: *
2704: * This function attached a paused deferred extent free item to the
2705: * transaction. Pausing means that the EFI will be logged in the next
2706: * transaction commit, but the pending EFI will not be finished until the
2707: * pending item is unpaused.
2708: *
2709: * If the system goes down after the EFI has been persisted to the log but
2710: * before the pending item is unpaused, log recovery will find the EFI, fail to
2711: * find the EFD, and free the space.
2712: *
2713: * If the pending item is unpaused, the next transaction commit will log an EFD
2714: * without freeing the space.
2715: *
2716: * Caller must ensure that the tp, fsbno, len, oinfo, and resv flags of the
2717: * @args structure are set to the relevant values.
2718: */
2719: int
2720: xfs_alloc_schedule_autoreap(
2721: const struct xfs_alloc_arg *args,
2722: unsigned int free_flags,
2723: struct xfs_alloc_autoreap *aarp)
2724: {
2725: int error;
2726:
2727: error = xfs_defer_extent_free(args->tp, args->fsbno, args->len,
2728: &args->oinfo, args->resv, free_flags, &aarp->dfp);
2729: if (error)
2730: return error;
2731:
2732: xfs_defer_item_pause(args->tp, aarp->dfp);
2733: return 0;
2734: }
SourceFile:fs/xfs/libxfs/xfs_alloc.c]
|
| 28/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 25041
output: 48
thoughts: 120
Thoughts: Okay, let me break this down.
**Diving into the XFS Deferred Allocation Process**
Alright, so `xfs_alloc_schedule_autoreap` sets up a deferred extent free item, `aarp->dfp`, and then puts it on hold β pausing it. Now, the interesting bit comes when `xrep_newbt_alloc_ag_blocks` calls `xrep_defer_finish(sc)`. This, in turn, calls `xfs_defer_finish`. Thatβs the critical link. My question is: does `xfs_defer_finish` *actually* handle those paused deferred items? Does it completely bypass the paused items or will it unpause them and finish them?
I need to confirm the behavior of the `xfs_defer_finish` and `xfs_defer_finish_noroll` functions. I need to understand what's happening to the paused items. Is the pause state preserved, or are the paused extents inadvertently reaped by these routines? This is where the devil's in the details. I need to check the code.
|
| 29/2 |
2026/07/27 01:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/libxfs/xfs_defer.c Name:xfs_defer_finish_noroll]
Results: map[SourceCode: 645: /*
646: * Finish all the pending work. This involves logging intent items for
647: * any work items that wandered in since the last transaction roll (if
648: * one has even happened), rolling the transaction, and finishing the
649: * work items in the first item on the logged-and-pending list.
650: *
651: * If an inode is provided, relog it to the new transaction.
652: */
653: int
654: xfs_defer_finish_noroll(
655: struct xfs_trans **tp)
656: {
657: struct xfs_defer_pending *dfp = NULL;
658: int error = 0;
659: LIST_HEAD(dop_pending);
660: LIST_HEAD(dop_paused);
661:
662: ASSERT((*tp)->t_flags & XFS_TRANS_PERM_LOG_RES);
663:
664: trace_xfs_defer_finish(*tp, _RET_IP_);
665:
666: /* Until we run out of pending work to finish... */
667: while (!list_empty(&dop_pending) || !list_empty(&(*tp)->t_dfops)) {
668: /*
669: * Deferred items that are created in the process of finishing
670: * other deferred work items should be queued at the head of
671: * the pending list, which puts them ahead of the deferred work
672: * that was created by the caller. This keeps the number of
673: * pending work items to a minimum, which decreases the amount
674: * of time that any one intent item can stick around in memory,
675: * pinning the log tail.
676: */
677: int has_intents = xfs_defer_create_intents(*tp);
678:
679: xfs_defer_isolate_paused(*tp, &dop_paused);
680:
681: list_splice_init(&(*tp)->t_dfops, &dop_pending);
682:
683: if (has_intents < 0) {
684: error = has_intents;
685: goto out_shutdown;
686: }
687: if (has_intents || dfp) {
688: error = xfs_defer_trans_roll(tp);
689: if (error)
690: goto out_shutdown;
691:
692: /* Relog intent items to keep the log moving. */
693: xfs_defer_relog(tp, &dop_pending);
694: xfs_defer_relog(tp, &dop_paused);
695:
696: if ((*tp)->t_flags & XFS_TRANS_DIRTY) {
697: error = xfs_defer_trans_roll(tp);
698: if (error)
699: goto out_shutdown;
700: }
701: }
702:
703: dfp = list_first_entry_or_null(&dop_pending,
704: struct xfs_defer_pending, dfp_list);
705: if (!dfp)
706: break;
707: error = xfs_defer_finish_one(*tp, dfp);
708: if (error && error != -EAGAIN)
709: goto out_shutdown;
710: }
711:
712: /* Requeue the paused items in the outgoing transaction. */
713: list_splice_tail_init(&dop_paused, &(*tp)->t_dfops);
714:
715: trace_xfs_defer_finish_done(*tp, _RET_IP_);
716: return 0;
717:
718: out_shutdown:
719: list_splice_tail_init(&dop_paused, &dop_pending);
720: xfs_defer_trans_abort(*tp, &dop_pending);
721: xfs_force_shutdown((*tp)->t_mountp, SHUTDOWN_CORRUPT_INCORE);
722: trace_xfs_defer_finish_error(*tp, error);
723: xfs_defer_cancel_list((*tp)->t_mountp, &dop_pending);
724: xfs_defer_cancel(*tp);
725: return error;
726: }
SourceFile:fs/xfs/libxfs/xfs_defer.c]
|
| 30/2 |
2026/07/27 01:51 |
llm |
expert |
0m
Model:
Tokens:
input: 25283
output: 47
thoughts: 0
|
| 31/2 |
2026/07/27 01:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/libxfs/xfs_defer.c Name:xfs_defer_cancel_list]
Results: map[SourceCode: 488: /*
489: * Free up any items left in the list.
490: */
491: static void
492: xfs_defer_cancel_list(
493: struct xfs_mount *mp,
494: struct list_head *dop_list)
495: {
496: struct xfs_defer_pending *dfp;
497: struct xfs_defer_pending *pli;
498:
499: /*
500: * Free the pending items. Caller should already have arranged
501: * for the intent items to be released.
502: */
503: list_for_each_entry_safe(dfp, pli, dop_list, dfp_list)
504: xfs_defer_pending_cancel_work(mp, dfp);
505: }
SourceFile:fs/xfs/libxfs/xfs_defer.c]
|
| 32/2 |
2026/07/27 01:52 |
llm |
expert |
0m
Model:
Tokens:
input: 27127
output: 47
thoughts: 197
Thoughts: **Analyzing a Potential Memory Corruption Issue in XFS Deferral Logic**
Okay, so I'm looking at this `xfs_defer_finish_noroll` function, and it seems like if it encounters `out_shutdown`, it's going to call `xfs_defer_cancel_list` on the `dop_pending` list. Right, and `out_shutdown` itself splices the `dop_paused` list *into* `dop_pending`. So, in a failure scenario within `xfs_defer_finish_noroll`, it's going to cancel *both* pending and paused deferred operations. The implication of this is that the associated `dfp` structure is freed.
The problem, as I see it, is that `aarp->dfp` is *still* pointing to this freed memory after the call to `xfs_defer_finish_noroll` returns. This seems highly suspect. The code then proceeds, and `xrep_newbt_alloc_ag_blocks` returns an error. This is followed by `xrep_rmap_build_new_tree` also returning the same error. Finally, `xrep_newbt_free` is called.
The concern hits me... `xrep_newbt_free` is getting called on the error path! If `aarp->dfp` is stale, and it's being used within the error handling... that's a classic use-after-free vulnerability! I need to trace back into `xrep_rmap_build_new_tree` immediately to see where `aarp->dfp` might be getting used *after* `xfs_defer_finish_noroll` could have freed the underlying memory. This could be nasty.
|
| 33/2 |
2026/07/27 01:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/scrub/rmap_repair.c Name:xrep_rmap_build_new_tree]
Results: map[SourceCode:1325: /*
1326: * Use the collected rmap information to stage a new rmap btree. If this is
1327: * successful we'll return with the new btree root information logged to the
1328: * repair transaction but not yet committed. This implements section (III)
1329: * above.
1330: */
1331: STATIC int
1332: xrep_rmap_build_new_tree(
1333: struct xrep_rmap *rr)
1334: {
1335: struct xfs_scrub *sc = rr->sc;
1336: struct xfs_perag *pag = sc->sa.pag;
1337: struct xfs_agf *agf = sc->sa.agf_bp->b_addr;
1338: struct xfs_btree_cur *rmap_cur;
1339: int error;
1340:
1341: /*
1342: * Preserve the old rmapbt block count so that we can adjust the
1343: * per-AG rmapbt reservation after we commit the new btree root and
1344: * want to dispose of the old btree blocks.
1345: */
1346: rr->old_rmapbt_fsbcount = be32_to_cpu(agf->agf_rmap_blocks);
1347:
1348: /*
1349: * Prepare to construct the new btree by reserving disk space for the
1350: * new btree and setting up all the accounting information we'll need
1351: * to root the new btree while it's under construction and before we
1352: * attach it to the AG header. The new blocks are accounted to the
1353: * rmapbt per-AG reservation, which we will adjust further after
1354: * committing the new btree.
1355: */
1356: xrep_newbt_init_ag(&rr->new_btree, sc, &XFS_RMAP_OINFO_SKIP_UPDATE,
1357: xfs_agbno_to_fsb(pag, XFS_RMAP_BLOCK(sc->mp)),
1358: XFS_AG_RESV_RMAPBT);
1359: rr->new_btree.bload.get_records = xrep_rmap_get_records;
1360: rr->new_btree.bload.claim_block = xrep_rmap_claim_block;
1361: rr->new_btree.alloc_vextent = xrep_rmap_alloc_vextent;
1362: rmap_cur = xfs_rmapbt_init_cursor(sc->mp, NULL, NULL, pag);
1363: xfs_btree_stage_afakeroot(rmap_cur, &rr->new_btree.afake);
1364:
1365: /*
1366: * Initialize @rr->new_btree, reserve space for the new rmapbt,
1367: * and compute OWN_AG rmaps.
1368: */
1369: error = xrep_rmap_reserve_space(rr, rmap_cur);
1370: if (error)
1371: goto err_cur;
1372:
1373: /*
1374: * Count the rmapbt records again, because the space reservation
1375: * for the rmapbt itself probably added more records to the btree.
1376: */
1377: rr->mcur = xfs_rmapbt_mem_cursor(rr->sc->sa.pag, NULL,
1378: &rr->rmap_btree);
1379:
1380: error = xrep_rmap_count_records(rr->mcur, &rr->nr_records);
1381: if (error)
1382: goto err_mcur;
1383:
1384: /*
1385: * Due to btree slack factors, it's possible for a new btree to be one
1386: * level taller than the old btree. Update the incore btree height so
1387: * that we don't trip the verifiers when writing the new btree blocks
1388: * to disk.
1389: */
1390: pag->pagf_repair_rmap_level = rr->new_btree.bload.btree_height;
1391:
1392: /*
1393: * Move the cursor to the left edge of the tree so that the first
1394: * increment in ->get_records positions us at the first record.
1395: */
1396: error = xfs_btree_goto_left_edge(rr->mcur);
1397: if (error)
1398: goto err_level;
1399:
1400: /* Add all observed rmap records. */
1401: error = xfs_btree_bload(rmap_cur, &rr->new_btree.bload, rr);
1402: if (error)
1403: goto err_level;
1404:
1405: /*
1406: * Install the new btree in the AG header. After this point the old
1407: * btree is no longer accessible and the new tree is live.
1408: */
1409: xfs_rmapbt_commit_staged_btree(rmap_cur, sc->tp, sc->sa.agf_bp);
1410: xfs_btree_del_cursor(rmap_cur, 0);
1411: xfs_btree_del_cursor(rr->mcur, 0);
1412: rr->mcur = NULL;
1413:
1414: /*
1415: * Now that we've written the new btree to disk, we don't need to keep
1416: * updating the in-memory btree. Abort the scan to stop live updates.
1417: */
1418: xchk_iscan_abort(&rr->iscan);
1419:
1420: /*
1421: * The newly committed rmap recordset includes mappings for the blocks
1422: * that we reserved to build the new btree. If there is excess space
1423: * reservation to be freed, the corresponding rmap records must also be
1424: * removed.
1425: */
1426: rr->new_btree.oinfo = XFS_RMAP_OINFO_AG;
1427:
1428: /* Reset the AGF counters now that we've changed the btree shape. */
1429: error = xrep_rmap_reset_counters(rr);
1430: if (error)
1431: goto err_newbt;
1432:
1433: /* Dispose of any unused blocks and the accounting information. */
1434: error = xrep_newbt_commit(&rr->new_btree);
1435: if (error)
1436: return error;
1437:
1438: return xrep_roll_ag_trans(sc);
1439:
1440: err_level:
1441: pag->pagf_repair_rmap_level = 0;
1442: err_mcur:
1443: xfs_btree_del_cursor(rr->mcur, error);
1444: err_cur:
1445: xfs_btree_del_cursor(rmap_cur, error);
1446: err_newbt:
1447: xrep_newbt_cancel(&rr->new_btree);
1448: return error;
1449: }
SourceFile:fs/xfs/scrub/rmap_repair.c]
|
| 34/2 |
2026/07/27 01:52 |
llm |
expert |
0m
Model:
Tokens:
input: 28190
output: 42
thoughts: 0
|
| 35/2 |
2026/07/27 01:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/scrub/rmap_repair.c Name:xrep_rmapbt]
Results: map[SourceCode:1701: /* Repair the rmap btree for some AG. */
1702: int
1703: xrep_rmapbt(
1704: struct xfs_scrub *sc)
1705: {
1706: struct xrep_rmap *rr = sc->buf;
1707: int error;
1708:
1709: error = xrep_rmap_setup_scan(rr);
1710: if (error)
1711: return error;
1712:
1713: /*
1714: * Collect rmaps for everything in this AG that isn't space metadata.
1715: * These rmaps won't change even as we try to allocate blocks.
1716: */
1717: error = xrep_rmap_find_rmaps(rr);
1718: if (error)
1719: goto out_records;
1720:
1721: /* Rebuild the rmap information. */
1722: error = xrep_rmap_build_new_tree(rr);
1723: if (error)
1724: goto out_records;
1725:
1726: /* Kill the old tree. */
1727: error = xrep_rmap_remove_old_tree(rr);
1728: if (error)
1729: goto out_records;
1730:
1731: out_records:
1732: xrep_rmap_teardown(rr);
1733: return error;
1734: }
SourceFile:fs/xfs/scrub/rmap_repair.c]
|
| 36/2 |
2026/07/27 01:52 |
llm |
expert |
0m
Model:
Tokens:
input: 28713
output: 39
thoughts: 0
|
| 37/2 |
2026/07/27 01:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/scrub/repair.c Name:xrep_attempt]
Results: map[SourceCode: 56: /*
57: * Attempt to repair some metadata, if the metadata is corrupt and userspace
58: * told us to fix it. This function returns -EAGAIN to mean "re-run scrub",
59: * and will set *fixed to true if it thinks it repaired anything.
60: */
61: int
62: xrep_attempt(
63: struct xfs_scrub *sc,
64: struct xchk_stats_run *run)
65: {
66: u64 repair_start;
67: int error = 0;
68:
69: trace_xrep_attempt(XFS_I(file_inode(sc->file)), sc->sm, error);
70:
71: xchk_ag_btcur_free(&sc->sa);
72: xchk_rtgroup_btcur_free(&sc->sr);
73:
74: /* Repair whatever's broken. */
75: ASSERT(sc->ops->repair);
76: run->repair_attempted = true;
77: repair_start = xchk_stats_now();
78: error = sc->ops->repair(sc);
79: trace_xrep_done(XFS_I(file_inode(sc->file)), sc->sm, error);
80: run->repair_ns += xchk_stats_elapsed_ns(repair_start);
81: switch (error) {
82: case 0:
83: /*
84: * Repair succeeded. Commit the fixes and perform a second
85: * scrub so that we can tell userspace if we fixed the problem.
86: */
87: sc->sm->sm_flags &= ~XFS_SCRUB_FLAGS_OUT;
88: sc->flags |= XREP_ALREADY_FIXED;
89: run->repair_succeeded = true;
90: return -EAGAIN;
91: case -ECHRNG:
92: sc->flags |= XCHK_NEED_DRAIN;
93: run->retries++;
94: return -EAGAIN;
95: case -EDEADLOCK:
96: /* Tell the caller to try again having grabbed all the locks. */
97: if (!(sc->flags & XCHK_TRY_HARDER)) {
98: sc->flags |= XCHK_TRY_HARDER;
99: run->retries++;
100: return -EAGAIN;
101: }
102: /*
103: * We tried harder but still couldn't grab all the resources
104: * we needed to fix it. The corruption has not been fixed,
105: * so exit to userspace with the scan's output flags unchanged.
106: */
107: return 0;
108: default:
109: /*
110: * EAGAIN tells the caller to re-scrub, so we cannot return
111: * that here.
112: */
113: ASSERT(error != -EAGAIN);
114: return error;
115: }
116: }
SourceFile:fs/xfs/scrub/repair.c]
|
| 38/2 |
2026/07/27 01:52 |
llm |
expert |
0m
Model:
Tokens:
input: 29673
output: 43
thoughts: 0
|
| 39/2 |
2026/07/27 01:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/scrub/scrub.c Name:xfs_scrub_metadata]
Results: map[SourceCode: 654: /* Dispatch metadata scrubbing. */
655: STATIC int
656: xfs_scrub_metadata(
657: struct file *file,
658: struct xfs_scrub_metadata *sm)
659: {
660: struct xchk_stats_run run = { };
661: struct xfs_scrub *sc;
662: struct xfs_mount *mp = XFS_I(file_inode(file))->i_mount;
663: u64 check_start;
664: int error = 0;
665:
666: BUILD_BUG_ON(sizeof(meta_scrub_ops) !=
667: (sizeof(struct xchk_meta_ops) * XFS_SCRUB_TYPE_NR));
668:
669: trace_xchk_start(XFS_I(file_inode(file)), sm, error);
670:
671: /* Forbidden if we are shut down or mounted norecovery. */
672: error = -ESHUTDOWN;
673: if (xfs_is_shutdown(mp))
674: goto out;
675: error = -ENOTRECOVERABLE;
676: if (xfs_has_norecovery(mp))
677: goto out;
678:
679: error = xchk_validate_inputs(mp, sm);
680: if (error)
681: goto out;
682:
683: sc = kzalloc_obj(struct xfs_scrub, XCHK_GFP_FLAGS);
684: if (!sc) {
685: error = -ENOMEM;
686: goto out;
687: }
688:
689: sc->mp = mp;
690: sc->file = file;
691: sc->sm = sm;
692: sc->ops = &meta_scrub_ops[sm->sm_type];
693: sc->sick_mask = xchk_health_mask_for_scrub_type(sm->sm_type);
694: sc->relax = INIT_XCHK_RELAX;
695: retry_op:
696: /*
697: * When repairs are allowed, prevent freezing or readonly remount while
698: * scrub is running with a real transaction.
699: */
700: if (sm->sm_flags & XFS_SCRUB_IFLAG_REPAIR) {
701: error = mnt_want_write_file(sc->file);
702: if (error)
703: goto out_sc;
704:
705: sc->flags |= XCHK_HAVE_FREEZE_PROT;
706: }
707:
708: /* Set up for the operation. */
709: error = sc->ops->setup(sc);
710: if (error == -EDEADLOCK && !(sc->flags & XCHK_TRY_HARDER))
711: goto try_harder;
712: if (error == -ECHRNG && !(sc->flags & XCHK_NEED_DRAIN))
713: goto need_drain;
714: if (error)
715: goto out_teardown;
716:
717: /* Scrub for errors. */
718: check_start = xchk_stats_now();
719: if ((sc->flags & XREP_ALREADY_FIXED) && sc->ops->repair_eval != NULL)
720: error = sc->ops->repair_eval(sc);
721: else
722: error = sc->ops->scrub(sc);
723: run.scrub_ns += xchk_stats_elapsed_ns(check_start);
724: if (error == -EDEADLOCK && !(sc->flags & XCHK_TRY_HARDER))
725: goto try_harder;
726: if (error == -ECHRNG && !(sc->flags & XCHK_NEED_DRAIN))
727: goto need_drain;
728: if (error || (sm->sm_flags & XFS_SCRUB_OFLAG_INCOMPLETE))
729: goto out_teardown;
730:
731: xchk_update_health(sc);
732:
733: if (xchk_could_repair(sc)) {
734: /*
735: * If userspace asked for a repair but it wasn't necessary,
736: * report that back to userspace.
737: */
738: if (!xrep_will_attempt(sc)) {
739: sc->sm->sm_flags |= XFS_SCRUB_OFLAG_NO_REPAIR_NEEDED;
740: goto out_nofix;
741: }
742:
743: /*
744: * If it's broken, userspace wants us to fix it, and we haven't
745: * already tried to fix it, then attempt a repair.
746: */
747: error = xrep_attempt(sc, &run);
748: if (error == -EAGAIN) {
749: /*
750: * Either the repair function succeeded or it couldn't
751: * get all the resources it needs; either way, we go
752: * back to the beginning and call the scrub function.
753: */
754: error = xchk_teardown(sc, 0);
755: if (error) {
756: xrep_failure(mp);
757: goto out_sc;
758: }
759: goto retry_op;
760: }
761: }
762:
763: out_nofix:
764: xchk_postmortem(sc);
765: out_teardown:
766: error = xchk_teardown(sc, error);
767: out_sc:
768: if (error != -ENOENT)
769: xchk_stats_merge(mp, sm, &run);
770: kfree(sc);
771: out:
772: trace_xchk_done(XFS_I(file_inode(file)), sm, error);
773: if (error == -EFSCORRUPTED || error == -EFSBADCRC) {
774: sm->sm_flags |= XFS_SCRUB_OFLAG_CORRUPT;
775: error = 0;
776: }
777: return error;
778: need_drain:
779: error = xchk_teardown(sc, 0);
780: if (error)
781: goto out_sc;
782: sc->flags |= XCHK_NEED_DRAIN;
783: run.retries++;
784: goto retry_op;
785: try_harder:
786: /*
787: * Scrubbers return -EDEADLOCK to mean 'try harder'. Tear down
788: * everything we hold, then set up again with preparation for
789: * worst-case scenarios.
790: */
791: error = xchk_teardown(sc, 0);
792: if (error)
793: goto out_sc;
794: sc->flags |= XCHK_TRY_HARDER;
795: run.retries++;
796: goto retry_op;
797: }
SourceFile:fs/xfs/scrub/scrub.c]
|
| 40/2 |
2026/07/27 01:52 |
llm |
expert |
0m
Model:
Tokens:
input: 31945
output: 41
thoughts: 0
|
| 41/2 |
2026/07/27 01:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/scrub/repair.c Name:xrep_defer_finish]
Results: map[SourceCode: 209: /* Finish all deferred work attached to the repair transaction. */
210: int
211: xrep_defer_finish(
212: struct xfs_scrub *sc)
213: {
214: int error;
215:
216: /*
217: * Keep the AG header buffers locked while we complete deferred work
218: * items. Ensure that both AG buffers are dirty and held when we roll
219: * the transaction so that they move forward in the log without losing
220: * the bli (and hence the bli type) when the transaction commits.
221: *
222: * Normal code would never hold clean buffers across a roll, but repair
223: * needs both buffers to maintain a total lock on the AG.
224: */
225: if (sc->sa.agi_bp) {
226: xfs_ialloc_log_agi(sc->tp, sc->sa.agi_bp, XFS_AGI_MAGICNUM);
227: xfs_trans_bhold(sc->tp, sc->sa.agi_bp);
228: }
229:
230: if (sc->sa.agf_bp) {
231: xfs_alloc_log_agf(sc->tp, sc->sa.agf_bp, XFS_AGF_MAGICNUM);
232: xfs_trans_bhold(sc->tp, sc->sa.agf_bp);
233: }
234:
235: /*
236: * Finish all deferred work items. We still hold the AG header buffers
237: * locked regardless of whether or not that succeeds. On failure, the
238: * buffers will be released during teardown on our way out of the
239: * kernel. If successful, join the buffers to the new transaction
240: * and move on.
241: */
242: error = xfs_defer_finish(&sc->tp);
243: if (error)
244: return error;
245:
246: /*
247: * Release the hold that we set above because defer_finish won't do
248: * that for us. The defer roll code redirties held buffers after each
249: * roll, so the AG header buffers should be ready for logging.
250: */
251: if (sc->sa.agi_bp)
252: xfs_trans_bhold_release(sc->tp, sc->sa.agi_bp);
253: if (sc->sa.agf_bp)
254: xfs_trans_bhold_release(sc->tp, sc->sa.agf_bp);
255:
256: return 0;
257: }
SourceFile:fs/xfs/scrub/repair.c]
|
| 42/2 |
2026/07/27 01:52 |
llm |
expert |
0m
Model:
Tokens:
input: 32817
output: 45
thoughts: 0
|
| 43/2 |
2026/07/27 01:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/xfs/libxfs/xfs_defer.c Name:xfs_defer_finish]
Results: map[SourceCode: 728: int
729: xfs_defer_finish(
730: struct xfs_trans **tp)
731: {
732: #ifdef DEBUG
733: struct xfs_defer_pending *dfp;
734: #endif
735: int error;
736:
737: /*
738: * Finish and roll the transaction once more to avoid returning to the
739: * caller with a dirty transaction.
740: */
741: error = xfs_defer_finish_noroll(tp);
742: if (error)
743: return error;
744: if ((*tp)->t_flags & XFS_TRANS_DIRTY) {
745: error = xfs_defer_trans_roll(tp);
746: if (error) {
747: xfs_force_shutdown((*tp)->t_mountp,
748: SHUTDOWN_CORRUPT_INCORE);
749: return error;
750: }
751: }
752:
753: /* Reset LOWMODE now that we've finished all the dfops. */
754: #ifdef DEBUG
755: list_for_each_entry(dfp, &(*tp)->t_dfops, dfp_list)
756: ASSERT(dfp->dfp_flags & XFS_DEFER_PAUSED);
757: #endif
758: (*tp)->t_flags &= ~XFS_TRANS_LOWMODE;
759: return 0;
760: }
SourceFile:fs/xfs/libxfs/xfs_defer.c]
|
| 44/2 |
2026/07/27 01:52 |
llm |
expert |
0m
Model:
Tokens:
input: 33364
output: 27
thoughts: 2920
Thoughts: **Analysis of an XFS KASAN Slab-Use-After-Free Vulnerability**
Okay, so I've got a KASAN slab-use-after-free in the XFS filesystem code, specifically in `xfs_defer_item_unpause`. The crash is happening within the scrub/repair logic, which is triggered by the `xfs_ioc_scrubv_metadata` ioctl. Looking at the trace, it's pretty clear: a deferred item is being unpaused after it's been freed. The allocation and free traces are tightly coupled within the scrub process.
This ioctl requires `CAP_SYS_ADMIN`, which means it can't be exploited from unprivileged processes or user namespaces. The kernel uses the `capable()` function here, which checks the capability within the initial user namespace. That's a key detail. It's not a security boundary bypass in the strictest sense because root already has complete control.
The vulnerability stems from the use-after-free of a `xfs_defer_pending` object. This object is freed during a transaction abort (during shutdown of the filesystem when repair fails). The free occurs in `xfs_defer_finish_noroll`, which is then called back up the call stack, finally calling `xfs_defer_item_unpause` which causes the use-after-free read. The problem is that during the error unwind, after the object is freed, a call to `xfs_alloc_commit_autoreap` within the `xrep_newbt_cancel` function calls `xfs_defer_item_unpause` on this freed object.
The critical operation is a read-modify-write on the `dfp->dfp_flags` field. This UAF could potentially corrupt memory if the slab object gets reallocated. However, because the filesystem is being shut down during the crash, it's very difficult to make the object be reallocated from userspace, even with preemption enabled. There is no user-space interaction possible between the free and the UAF. The attacker can't easily stall the thread and reallocate the object because there aren't any obvious user copies between the free and the UAF.
However, technically speaking, this is still exploitable as memory corruption. Even though the filesystem is shutting down, if an attacker could reallocate the freed slab object *somehow* from another thread, the UAF would cause a bit to be cleared in whatever data that slab now contained, which is a memory corruption.
Because `CAP_SYS_ADMIN` is required to trigger the ioctl, it's not a remote vulnerability, and it's not triggered by just mounting a malicious filesystem image.
|
| 45/2 |
2026/07/27 01:52 |
tool |
codesearch-find-references |
0m
Args: map[Name:xfs_alloc_schedule_autoreap]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:xrep_newbt_add_blocks SourceFile:fs/xfs/scrub/newbt.c SourceLine:209]] TruncatedOutput:false]
|
| 46/2 |
2026/07/27 01:52 |
llm |
expert |
0m
Model:
Tokens:
input: 34043
output: 27
thoughts: 0
|
| 47/2 |
2026/07/27 01:52 |
tool |
codesearch-find-references |
0m
Args: map[Name:xfs_alloc_commit_autoreap]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:xrep_newbt_free SourceFile:fs/xfs/scrub/newbt.c SourceLine:520] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:xrep_newbt_free_extent SourceFile:fs/xfs/scrub/newbt.c SourceLine:436]] TruncatedOutput:false]
|
| 48/2 |
2026/07/27 01:52 |
llm |
expert |
0m
Model:
Tokens:
input: 38823
output: 25
thoughts: 162
Thoughts: **Narrowing the Scope of the XFS Bug**
Okay, so it looks like the `xfs_alloc_schedule_autoreap` and `xfs_alloc_commit_autoreap` functions are *only* used within the `fs/xfs/scrub/newbt.c` file. That's a huge relief because it immediately limits the potential impact of the bug to the XFS scrub and repair code. This is definitely a significant piece of information.
Now, where can this XFS scrub and repair code *actually* be triggered? It appears those functions are accessible exclusively via the `XFS_IOC_SCRUB_METADATA` and `XFS_IOC_SCRUBV_METADATA` ioctls. This is a crucial detail. I need to keep this in mind as I try to understand the severity.
The good news is that both ioctls perform a `capable(CAP_SYS_ADMIN)` check. This means that an attacker needs root privileges to even *potentially* trigger the vulnerable code path. That significantly reduces the risk profile. But it still needs further investigation.
My next step is to verify, with absolute certainty, that `xfs_ioc_scrub_metadata` and `xfs_ioc_scrubv_metadata` are *the only* callers of `xfs_scrub_metadata`, to make sure that these are the only points of entry. This will give me the confidence to move forward.
|
| 49/2 |
2026/07/27 01:52 |
tool |
codesearch-find-references |
0m
Args: map[Name:xfs_scrub_metadata]
Results: map[References:[map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__bpf_trace_xchk_class SourceFile:fs/xfs/scrub/trace.h SourceLine:149] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__do_trace_xchk_deadlock_retry SourceFile:fs/xfs/scrub/trace.h SourceLine:191] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__do_trace_xchk_dirtree_done SourceFile:fs/xfs/scrub/trace.h SourceLine:193] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__do_trace_xchk_dirtree_start SourceFile:fs/xfs/scrub/trace.h SourceLine:192] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__do_trace_xchk_done SourceFile:fs/xfs/scrub/trace.h SourceLine:190] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__do_trace_xchk_start SourceFile:fs/xfs/scrub/trace.h SourceLine:189] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__do_trace_xrep_attempt SourceFile:fs/xfs/scrub/trace.h SourceLine:194] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__do_trace_xrep_done SourceFile:fs/xfs/scrub/trace.h SourceLine:195] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__probestub_xchk_deadlock_retry SourceFile:fs/xfs/scrub/trace.h SourceLine:191] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__probestub_xchk_dirtree_done SourceFile:fs/xfs/scrub/trace.h SourceLine:193] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__probestub_xchk_dirtree_start SourceFile:fs/xfs/scrub/trace.h SourceLine:192] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__probestub_xchk_done SourceFile:fs/xfs/scrub/trace.h SourceLine:190] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__probestub_xchk_start SourceFile:fs/xfs/scrub/trace.h SourceLine:189] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__probestub_xrep_attempt SourceFile:fs/xfs/scrub/trace.h SourceLine:194] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__probestub_xrep_done SourceFile:fs/xfs/scrub/trace.h SourceLine:195] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xchk_deadlock_retry SourceFile:fs/xfs/scrub/trace.h SourceLine:191] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xchk_deadlock_retry SourceFile:fs/xfs/scrub/trace.h SourceLine:191] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xchk_dirtree_done SourceFile:fs/xfs/scrub/trace.h SourceLine:193] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xchk_dirtree_done SourceFile:fs/xfs/scrub/trace.h SourceLine:193] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xchk_dirtree_start SourceFile:fs/xfs/scrub/trace.h SourceLine:192] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xchk_dirtree_start SourceFile:fs/xfs/scrub/trace.h SourceLine:192] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xchk_done SourceFile:fs/xfs/scrub/trace.h SourceLine:190] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xchk_done SourceFile:fs/xfs/scrub/trace.h SourceLine:190] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xchk_start SourceFile:fs/xfs/scrub/trace.h SourceLine:189] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xchk_start SourceFile:fs/xfs/scrub/trace.h SourceLine:189] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xrep_attempt SourceFile:fs/xfs/scrub/trace.h SourceLine:194] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xrep_attempt SourceFile:fs/xfs/scrub/trace.h SourceLine:194] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xrep_done SourceFile:fs/xfs/scrub/trace.h SourceLine:195] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:__traceiter_xrep_done SourceFile:fs/xfs/scrub/trace.h SourceLine:195] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:check_trace_callback_type_xchk_deadlock_retry SourceFile:fs/xfs/scrub/trace.h SourceLine:191] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:check_trace_callback_type_xchk_dirtree_done SourceFile:fs/xfs/scrub/trace.h SourceLine:193] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:check_trace_callback_type_xchk_dirtree_start SourceFile:fs/xfs/scrub/trace.h SourceLine:192] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:check_trace_callback_type_xchk_done SourceFile:fs/xfs/scrub/trace.h SourceLine:190] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:check_trace_callback_type_xchk_start SourceFile:fs/xfs/scrub/trace.h SourceLine:189] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:check_trace_callback_type_xrep_attempt SourceFile:fs/xfs/scrub/trace.h SourceLine:194] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:check_trace_callback_type_xrep_done SourceFile:fs/xfs/scrub/trace.h SourceLine:195] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:do_perf_trace_xchk_class SourceFile:fs/xfs/scrub/trace.h SourceLine:149] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:do_trace_event_raw_event_xchk_class SourceFile:fs/xfs/scrub/trace.h SourceLine:149] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:perf_trace_xchk_class SourceFile:fs/xfs/scrub/trace.h SourceLine:149] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_prio_xchk_deadlock_retry SourceFile:fs/xfs/scrub/trace.h SourceLine:191] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_prio_xchk_dirtree_done SourceFile:fs/xfs/scrub/trace.h SourceLine:193] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_prio_xchk_dirtree_start SourceFile:fs/xfs/scrub/trace.h SourceLine:192] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_prio_xchk_done SourceFile:fs/xfs/scrub/trace.h SourceLine:190] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_prio_xchk_start SourceFile:fs/xfs/scrub/trace.h SourceLine:189] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_prio_xrep_attempt SourceFile:fs/xfs/scrub/trace.h SourceLine:194] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_prio_xrep_done SourceFile:fs/xfs/scrub/trace.h SourceLine:195] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_xchk_deadlock_retry SourceFile:fs/xfs/scrub/trace.h SourceLine:191] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_xchk_dirtree_done SourceFile:fs/xfs/scrub/trace.h SourceLine:193] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_xchk_dirtree_start SourceFile:fs/xfs/scrub/trace.h SourceLine:192] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_xchk_done SourceFile:fs/xfs/scrub/trace.h SourceLine:190] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_xchk_start SourceFile:fs/xfs/scrub/trace.h SourceLine:189] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_xrep_attempt SourceFile:fs/xfs/scrub/trace.h SourceLine:194] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:register_trace_xrep_done SourceFile:fs/xfs/scrub/trace.h SourceLine:195] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_call__xchk_deadlock_retry SourceFile:fs/xfs/scrub/trace.h SourceLine:191] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_call__xchk_dirtree_done SourceFile:fs/xfs/scrub/trace.h SourceLine:193] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_call__xchk_dirtree_start SourceFile:fs/xfs/scrub/trace.h SourceLine:192] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_call__xchk_done SourceFile:fs/xfs/scrub/trace.h SourceLine:190] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_call__xchk_start SourceFile:fs/xfs/scrub/trace.h SourceLine:189] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_call__xrep_attempt SourceFile:fs/xfs/scrub/trace.h SourceLine:194] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_call__xrep_done SourceFile:fs/xfs/scrub/trace.h SourceLine:195] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_event_get_offsets_xchk_class SourceFile:fs/xfs/scrub/trace.h SourceLine:149] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_event_raw_event_xchk_class SourceFile:fs/xfs/scrub/trace.h SourceLine:149] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_xchk_deadlock_retry SourceFile:fs/xfs/scrub/trace.h SourceLine:191] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_xchk_dirtree_done SourceFile:fs/xfs/scrub/trace.h SourceLine:193] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_xchk_dirtree_start SourceFile:fs/xfs/scrub/trace.h SourceLine:192] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_xchk_done SourceFile:fs/xfs/scrub/trace.h SourceLine:190] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_xchk_start SourceFile:fs/xfs/scrub/trace.h SourceLine:189] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_xrep_attempt SourceFile:fs/xfs/scrub/trace.h SourceLine:194] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:trace_xrep_done SourceFile:fs/xfs/scrub/trace.h SourceLine:195] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:unregister_trace_xchk_deadlock_retry SourceFile:fs/xfs/scrub/trace.h SourceLine:191] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:unregister_trace_xchk_dirtree_done SourceFile:fs/xfs/scrub/trace.h SourceLine:193] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:unregister_trace_xchk_dirtree_start SourceFile:fs/xfs/scrub/trace.h SourceLine:192] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:unregister_trace_xchk_done SourceFile:fs/xfs/scrub/trace.h SourceLine:190] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:unregister_trace_xchk_start SourceFile:fs/xfs/scrub/trace.h SourceLine:189] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:unregister_trace_xrep_attempt SourceFile:fs/xfs/scrub/trace.h SourceLine:194] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:unregister_trace_xrep_done SourceFile:fs/xfs/scrub/trace.h SourceLine:195] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:xchk_needs_repair SourceFile:fs/xfs/scrub/common.h SourceLine:232] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:xchk_skip_xref SourceFile:fs/xfs/scrub/common.h SourceLine:222] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:xchk_validate_inputs SourceFile:fs/xfs/scrub/scrub.c SourceLine:496] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:xfs_ioc_scrub_metadata SourceFile:fs/xfs/scrub/scrub.c SourceLine:805] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:xfs_ioc_scrubv_metadata SourceFile:fs/xfs/scrub/scrub.c SourceLine:963] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:xfs_scrub_metadata SourceFile:fs/xfs/scrub/scrub.c SourceLine:658] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:xrep_calc_ag_resblks SourceFile:fs/xfs/scrub/repair.c SourceLine:285] map[ReferenceKind:uses ReferencingEntityKind:function ReferencingEntityName:xrep_calc_rtgroup_resblks SourceFile:fs/xfs/scrub/repair.c SourceLine:400] map[ReferenceKind:uses ReferencingEntityKind:typedef ReferencingEntityName:btf_trace_xchk_deadlock_retry SourceFile:fs/xfs/scrub/trace.h SourceLine:191] map[ReferenceKind:uses ReferencingEntityKind:typedef ReferencingEntityName:btf_trace_xchk_dirtree_done SourceFile:fs/xfs/scrub/trace.h SourceLine:193] map[ReferenceKind:uses ReferencingEntityKind:typedef ReferencingEntityName:btf_trace_xchk_dirtree_start SourceFile:fs/xfs/scrub/trace.h SourceLine:192] map[ReferenceKind:uses ReferencingEntityKind:typedef ReferencingEntityName:btf_trace_xchk_done SourceFile:fs/xfs/scrub/trace.h SourceLine:190] map[ReferenceKind:uses ReferencingEntityKind:typedef ReferencingEntityName:btf_trace_xchk_start SourceFile:fs/xfs/scrub/trace.h SourceLine:189] map[ReferenceKind:uses ReferencingEntityKind:typedef ReferencingEntityName:btf_trace_xrep_attempt SourceFile:fs/xfs/scrub/trace.h SourceLine:194] map[ReferenceKind:uses ReferencingEntityKind:typedef ReferencingEntityName:btf_trace_xrep_done SourceFile:fs/xfs/scrub/trace.h SourceLine:195]] TruncatedOutput:false]
|
| 50/2 |
2026/07/27 01:52 |
llm |
expert |
0m
Model:
Tokens:
input: 44616
output: 66
thoughts: 1772
Thoughts: **XFS Scrub Metadata UAF Analysis**
Okay, let's break this down. From what I'm seeing, the critical functions here are `xfs_scrub_metadata`, `xfs_ioc_scrub_metadata`, and `xfs_ioc_scrubv_metadata`. My initial thought is that both ioctl handlers, which are the only entry points for `xfs_scrub_metadata`, check for `capable(CAP_SYS_ADMIN)`. This immediately restricts the attack surface; this isn't something a regular user, or even someone in an unprivileged user namespace, can trigger.
The core issue seems to be a Use-After-Free (UAF) condition. It centers around a read-modify-write operation on `dfp->dfp_flags`: specifically, clearing the `XFS_DEFER_PAUSED` bit. The problem is, if the object is reallocated *after* it's been freed (or, more precisely, after it becomes logically freed but a reference still exists), this operation could corrupt the reallocated object. The crash dump tells me the object is a slab object of size 64 (`xfs_defer_pending`), which is an indication that it's a dedicated cache for that size.
The question is, *can* an attacker reliably reallocate this object? It *should* be possible by triggering another deferred operation, but that gets complicated by the fact the filesystem is shut down at this point. `xfs_force_shutdown` seems to be the culprit. I'm leaning toward marking this as *potentially* exploitable. It's a memory corruption bug, and those are generally exploitable unless proven otherwise. The fact that the filesystem is shut down might make exploitation difficult, but it's not impossible to reach.
The UAF itself results in a crash, so the Denial of Service is definite here, whether through a KASAN report or general memory corruption.
I've already established the access restrictions: no unprivileged processes or user namespaces can trigger this, no VM guest or host contexts, no network, peripheral, remote, or malicious filesystem triggers either.
I'm double-checking other potential code paths leading to `xfs_defer_item_unpause`, the function causing the UAF by design. The sequence is specifically through a deferred finish failure in `xfs_defer_finish_noroll`, which frees the `dfp`. It then calls `xrep_newbt_free` which in turn calls `xfs_alloc_commit_autoreap` which calls `xfs_defer_item_unpause`. Looking deeper I confirm that `xrep_newbt_free` *can* be called from other functions, but only within the XFS repair code, such as `xrep_rmap_build_new_tree` and `xrep_bmap_build_new_tree` on error. These *also* require `CAP_SYS_ADMIN`.
I need to confirm the other path involving `xfs_alloc_cancel_autoreap` in a similar manner, which also calls `xfs_defer_item_unpause`. Again, this path is limited to XFS scrub/repair code.
Now I'm making sure that io_uring doesn't bypass the credential check. This seems secure as io_uring still runs with the capabilities of the creating process. Likewise, the ioctl cannot be triggered by simply providing a malicious filesystem, because automounting won't issue `XFS_IOC_SCRUB_METADATA`.
The root cause of this UAF is the *failure* of `xfs_defer_finish_noroll`, where it fails to roll back deferred operations and then frees the `dfp`. Normal XFS code isn't designed to maintain lingering pointers to `dfp`s that would trigger this UAF; the exception is the `xfs_alloc_autoreap` struct, *explicitly* for XFS repair and specifically defined to track deferred extents. This struct is exclusively used within `fs/xfs/scrub/newbt.c` and `fs/xfs/libxfs/xfs_alloc.c`, so it's narrow in scope and still requires those capabilities. I'm verifying the callers of `xfs_defer_item_unpause` through cross referencing, and my earlier check stands.
The analysis is complete, therefore:
Exploitable: Yes
DenialOfService: Yes
Unprivileged: No
UserNamespace: No
VMGuestTrigger: No
VMHostTrigger: No
NetworkTrigger: No
RemoteTrigger: No
PeripheralTrigger: No
FilesystemTrigger: No
I'll proceed to set these results and will document the specific reasoning.
|
| 51/2 |
2026/07/27 01:53 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:true FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:false VMHostTrigger:false]
|
| 52/2 |
2026/07/27 01:53 |
llm |
expert |
0m
Model:
Tokens:
input: 40977
output: 604
thoughts: 989
Thoughts: **XFS Scrub UAF Analysis**
Okay, let's break this down. The core issue is a KASAN slab-use-after-free within `xfs_defer_item_unpause`. The call trace tells me it's triggered by the online metadata scrub via the `XFS_IOC_SCRUBV_METADATA` or `XFS_IOC_SCRUB_METADATA` ioctl. It's a classic UAF, and that's usually bad news.
First, the obvious: this requires `CAP_SYS_ADMIN` due to the initial capability check within the ioctl handler. It uses `capable()` which checks the init namespace's capabilities. So, no unprivileged user or user namespace escalation here; the attack surface is limited to privileged processes.
But, a UAF is generally exploitable. Even if it's not a full kernel takeover, it's very likely to cause a denial-of-service, especially within a filesystem context.
Now, let's dig into the memory lifecycle of this deferred item (`dfp`). It seems the bug lies in how the deferral list is handled during online repair. The critical path involves `xrep_newbt_alloc_ag_blocks` allocating a `dfp`, adding it to the transaction, and then calling `xrep_defer_finish`.
Here's the critical sequence:
1. `xrep_newbt_alloc_ag_blocks` calls `xrep_newbt_add_blocks` which adds the `dfp` to the transaction.
2. `xrep_defer_finish` is called.
3. `xfs_defer_finish_noroll` is called and **frees** the `dfp` if the work is finished.
4. But `xrep_newbt_add_blocks` saves the pointer to the `dfp` in `resv->autoreap.dfp` via `xfs_alloc_schedule_autoreap`.
5. Later `xrep_newbt_free` calls `xfs_alloc_commit_autoreap`, which calls `xfs_defer_item_unpause` which dereferences the now-freed `dfp`!
So, `xfs_defer_finish` releases the `dfp`, but a reference to that freed memory still exists in the autoreap structure, and this reference is then used later by `xfs_defer_item_unpause`, hence the UAF.
This is a bug in XFS online repair (scrub), requiring `CAP_SYS_ADMIN`. It's a local privilege escalation if someone has that capability or a DoS. It requires an explicit ioctl trigger to exercise it. This isn't a filesystem trigger; it's a scrub-specific bug. It's not remotely exploitable, not via a network interface, peripheral device, or VM. It's confined to the local system, but still a significant vulnerability due to the UAF potential. Now I can document this properly.
|
| 53/1 |
2026/07/27 01:53 |
action |
format-explanation |
0m
Results: map[Explanation:**Bug Analysis:**
The bug is a Use-After-Free (UAF) in the XFS filesystem's online repair (scrub)
subsystem, specifically within `xfs_defer_item_unpause`.
When repairing the Reverse Mapping B-tree (rmapbt), the kernel allocates new
blocks and schedules them for automatic reaping (freeing) in case the repair
fails or is aborted. This is done by creating a deferred extent free item
(`xfs_defer_pending` or `dfp`) and pausing it. The pointer to this `dfp` is
saved in a reservation structure (`resv->autoreap.dfp`).
However, during the allocation loop in `xrep_newbt_alloc_ag_blocks`, the code
calls `xrep_defer_finish()`, which processes and finishes all deferred
operations attached to the transaction. Finishing the deferred operations frees
the `dfp` objects. Later, when `xrep_newbt_free()` is called to clean up the
reservations, it attempts to unpause the previously saved `dfp` by calling
`xfs_alloc_commit_autoreap()`, leading to a Use-After-Free when it accesses the
already freed `dfp`.
**Exploitability:**
Yes. The bug is a Use-After-Free on a slab object (`xfs_defer_pending` of size
64). An attacker who can trigger this bug could potentially spray the heap to
reallocate the freed 64-byte object with controlled data before it is accessed
by `xfs_defer_item_unpause()`. This could lead to memory corruption,
control-flow hijacking, or privilege escalation.
**Denial of Service:**
Yes. The UAF can easily cause memory corruption that leads to kernel panics,
crashing the system.
**Access and Triggers:**
* **Unprivileged / User Namespaces:** No. The bug is triggered via the
`XFS_IOC_SCRUB_METADATA` or `XFS_IOC_SCRUBV_METADATA` ioctls. Looking at the
source code for these ioctls (`xfs_ioc_scrub_metadata` and
`xfs_ioc_scrubv_metadata`), they both explicitly check for the `CAP_SYS_ADMIN`
capability using `capable(CAP_SYS_ADMIN)`. The `capable()` function checks
against the initial user namespace, meaning this cannot be bypassed by
unprivileged users creating a new user namespace.
* **Filesystem Trigger:** No. While it is an XFS bug, it requires an active
process to issue specific online repair ioctls; simply mounting a malicious
filesystem is not enough to trigger the repair path automatically.
* **Remote / Network / Peripheral / VM Triggers:** No. The bug is strictly local
and requires root privileges to execute the specific XFS ioctls.]
|