| 0/0 |
2026/05/30 10:41 |
flow |
assessment-security |
57m
Results: map[DenialOfService:true Explanation:Based on the analysis of the provided kernel bug report and source code, here is
the assessment of the vulnerability:
### Bug Explanation
The bug is a lock leak in the `userfaultfd` subsystem, specifically in the
`mfill_get_vma()` function in `mm/userfaultfd.c`.
When `mfill_get_vma()` is called (e.g., during a `UFFDIO_COPY` ioctl), it first
locks the destination VMA using `uffd_mfill_lock()` (which acquires the per-VMA
read lock and increments `vma->vm_refcnt`) and then acquires a read lock on
`ctx->map_changing_lock`.
After acquiring these locks, it performs several validation checks. If any of
these checks fail (for example, if `ctx->mmap_changing` is true, or if a
write-protect copy is requested on a VMA without `VM_UFFD_WP`), the code jumps
to the `out_unlock` label to clean up and return an error:
```c
out_unlock:
mfill_put_vma(state);
return err;
```
However, `mfill_put_vma(state)` relies on `state->vma` to know which locks to
release. The problem is that `state->vma` is only assigned at the very end of
`mfill_get_vma()` (at the `out:` label). In the error paths, `state->vma` is
still `NULL`.
When `mfill_put_vma()` sees that `state->vma` is `NULL`, it returns immediately
without doing anything:
```c
static void mfill_put_vma(struct mfill_state *state)
{
if (!state->vma)
return;
// ... releases locks ...
}
```
As a result, both the **per-VMA read lock** and the **`ctx->map_changing_lock`
read lock** are permanently leaked.
### Impact and Deadlock Scenario
If an attacker intentionally triggers this error path (e.g., by racing
`UFFDIO_COPY` with `munmap`, or simply by passing `UFFDIO_COPY_MODE_WP` on a VMA
not registered for write-protection), the locks are leaked.
Subsequently, if the process attempts to unmap or modify the VMA (e.g., via
`munmap()`), the kernel will:
1. Acquire the `mmap_write_lock` for the process.
2. Call `vma_start_write()`, which waits uninterruptibly for `vma->vm_refcnt` to
drop to 0.
3. Call `userfaultfd_unmap_prep()`, which waits uninterruptibly for
`down_write(&ctx->map_changing_lock)`.
Because the read locks were leaked, the `munmap` thread will block forever in
`D` state while holding the `mmap_write_lock`. Any other thread in the process
(or the kernel's OOM reaper) that subsequently tries to acquire `mmap_read_lock`
will also block forever. This perfectly matches the `syz.2.8` task blocked in
`mmap_read_lock` shown in the bug report.
### Security Dimensions
* **Exploitable:** **No**. This is a pure logic bug leading to unreleased locks.
It does not cause memory corruption, use-after-free, or out-of-bounds accesses,
and cannot be used for privilege escalation.
* **Denial Of Service:** **Yes**. An attacker can reliably freeze their own
process in an unkillable `D` state. Because the process cannot be killed (even
by `SIGKILL` or the OOM killer), its memory cannot be reclaimed, leading to a
resource leak. On systems with `kernel.hung_task_panic=1`, this will reliably
panic and reboot the entire system.
* **Accessible From Unprivileged Processes:** **Yes**. While full `userfaultfd`
access is often restricted to root or `CAP_SYS_PTRACE`, unprivileged users can
always create a userfaultfd by passing the `UFFD_USER_MODE_ONLY` flag. This flag
is sufficient to register a VMA and trigger the `UFFDIO_COPY` ioctl bug.
* **Accessible From User Namespaces:** **Yes**. For the same reasons as above.
* **VM Guest / Host / Network / Peripheral / Filesystem Trigger:** **No**. The
bug is strictly tied to the local `userfaultfd` syscall/ioctl interface. Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/30 10:41 |
action |
syz-repro-to-c-repro |
0m
Results:
|
| 2/1 |
2026/05/30 10:41 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/0770c3078e1461ffeb6017e9df8948acca6f8739]
|
| 3/1 |
2026/05/30 10:42 |
action |
kernel-builder |
29m
Results: map[KernelObj:/app/workdir/cache/build/893eb6a028bd4ebe9ba5ddffa18484ae04853164]
|
| 4/1 |
2026/05/30 11:12 |
action |
codesearch-prepare |
18m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/30 11:31 |
agent |
expert |
7m
Model:
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption or elevated privileges.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
INFO: task syz.2.8:5969 blocked for more than 143 seconds.
Tainted: G L syzkaller #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:syz.2.8 state:D stack:27712 pid:5969 tgid:5963 ppid:5846 task_flags:0x400040 flags:0x00080002
Call Trace:
<TASK>
context_switch kernel/sched/core.c:5352 [inline]
__schedule+0x1665/0x5590 kernel/sched/core.c:6964
__schedule_loop kernel/sched/core.c:7047 [inline]
schedule+0x164/0x360 kernel/sched/core.c:7062
schedule_preempt_disabled+0x13/0x30 kernel/sched/core.c:7119
rwsem_down_read_slowpath+0x6d9/0x940 kernel/locking/rwsem.c:1114
__down_read_common kernel/locking/rwsem.c:1291 [inline]
__down_read kernel/locking/rwsem.c:1304 [inline]
down_read+0x99/0x2e0 kernel/locking/rwsem.c:1570
mmap_read_lock include/linux/mmap_lock.h:592 [inline]
uffd_lock_vma+0xb6/0x2d0 mm/userfaultfd.c:135
uffd_mfill_lock mm/userfaultfd.c:154 [inline]
mfill_get_vma+0xc1/0x660 mm/userfaultfd.c:217
mfill_atomic mm/userfaultfd.c:900 [inline]
mfill_atomic_copy+0x1a8/0x1580 mm/userfaultfd.c:950
userfaultfd_copy fs/userfaultfd.c:1642 [inline]
userfaultfd_ioctl+0x2bbe/0x4c70 fs/userfaultfd.c:2059
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+0x14d/0xf80 arch/x86/entry/syscall_64.c:94
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7fa29a59c799
RSP: 002b:00007fa29b48a028 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007fa29a816180 RCX: 00007fa29a59c799
RDX: 0000200000000180 RSI: 00000000c028aa03 RDI: 0000000000000003
RBP: 00007fa29a632c99 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007fa29a816218 R14: 00007fa29a816180 R15: 00007ffdbeea9468
</TASK>
Showing all locks held in the system:
1 lock held by khungtaskd/30:
#0: ffffffff8eb5d6a0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:312 [inline]
#0: ffffffff8eb5d6a0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:850 [inline]
#0: ffffffff8eb5d6a0 (rcu_read_lock){....}-{1:3}, at: debug_show_all_locks+0x2e/0x180 kernel/locking/lockdep.c:6775
2 locks held by getty/5597:
#0: ffff88803778f0a0 (&tty->ldisc_sem){++++}-{0:0}, at: tty_ldisc_ref_wait+0x25/0x70 drivers/tty/tty_ldisc.c:243
#1: ffffc9000322b2e8 (&ldata->atomic_read_lock){+.+.}-{4:4}, at: n_tty_read+0x45c/0x13c0 drivers/tty/n_tty.c:2211
4 locks held by udevd/5836:
#0: ffff888050351e18 (&p->lock){+.+.}-{4:4}, at: seq_read_iter+0xb7/0xe10 fs/seq_file.c:183
#1: ffff8880583ab080 (&of->mutex#2){+.+.}-{4:4}, at: kernfs_seq_start+0x5c/0x420 fs/kernfs/file.c:172
#2: ffff8880754df698 (kn->active#31){.+.+}-{0:0}, at: kernfs_get_active_of fs/kernfs/file.c:80 [inline]
#2: ffff8880754df698 (kn->active#31){.+.+}-{0:0}, at: kernfs_seq_start+0xb2/0x420 fs/kernfs/file.c:173
#3: ffff888021b30190 (&dev->mutex){....}-{4:4}, at: device_lock_interruptible include/linux/device.h:946 [inline]
#3: ffff888021b30190 (&dev->mutex){....}-{4:4}, at: manufacturer_show+0x26/0xa0 drivers/usb/core/sysfs.c:142
3 locks held by kworker/u9:5/5848:
#0: ffff88805794d940 ((wq_completion)hci4){+.+.}-{0:0}, at: process_one_work+0x894/0x1780 kernel/workqueue.c:3261
#1: ffffc90004417c40 ((work_completion)(&hdev->cmd_sync_work)){+.+.}-{0:0}, at: process_one_work+0x8bb/0x1780 kernel/workqueue.c:3262
#2: ffff88803455cea0 (&hdev->req_lock){+.+.}-{4:4}, at: hci_cmd_sync_work+0x1d3/0x400 net/bluetooth/hci_sync.c:331
5 locks held by kworker/0:5/5891:
#0: ffff888021ebf940 ((wq_completion)usb_hub_wq){+.+.}-{0:0}, at: process_one_work+0x894/0x1780 kernel/workqueue.c:3261
#1: ffffc90004d87c40 ((work_completion)(&hub->events)){+.+.}-{0:0}, at: process_one_work+0x8bb/0x1780 kernel/workqueue.c:3262
#2: ffff88802b1f3190 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:941 [inline]
#2: ffff88802b1f3190 (&dev->mutex){....}-{4:4}, at: hub_event+0x17f/0x4f30 drivers/usb/core/hub.c:5899
#3: ffff888021b30190 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:941 [inline]
#3: ffff888021b30190 (&dev->mutex){....}-{4:4}, at: __device_attach+0x88/0x450 drivers/base/dd.c:1008
#4: ffff888029570158 (&dev->mutex){....}-{4:4}, at: device_lock include/linux/device.h:941 [inline]
#4: ffff888029570158 (&dev->mutex){....}-{4:4}, at: __device_attach+0x88/0x450 drivers/base/dd.c:1008
1 lock held by syz.2.8/5969:
#0: ffff88807af95ab8 (&mm->mmap_lock){++++}-{4:4}, at: mmap_read_lock include/linux/mmap_lock.h:592 [inline]
#0: ffff88807af95ab8 (&mm->mmap_lock){++++}-{4:4}, at: uffd_lock_vma+0xb6/0x2d0 mm/userfaultfd.c:135
=============================================
NMI backtrace for cpu 1
CPU: 1 UID: 0 PID: 30 Comm: khungtaskd Tainted: G L syzkaller #0 PREEMPT(full)
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
nmi_cpu_backtrace+0x274/0x2d0 lib/nmi_backtrace.c:113
nmi_trigger_cpumask_backtrace+0x17a/0x300 lib/nmi_backtrace.c:62
trigger_all_cpu_backtrace include/linux/nmi.h:162 [inline]
__sys_info lib/sys_info.c:157 [inline]
sys_info+0x135/0x170 lib/sys_info.c:165
check_hung_uninterruptible_tasks kernel/hung_task.c:353 [inline]
watchdog+0x1002/0x1060 kernel/hung_task.c:561
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x51e/0xb90 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Sending NMI from CPU 1 to CPUs 0:
NMI backtrace for cpu 0
CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Tainted: G L syzkaller #0 PREEMPT(full)
Tainted: [L]=SOFTLOCKUP
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 02/12/2026
RIP: 0010:pv_native_safe_halt+0xf/0x20 arch/x86/kernel/paravirt.c:63
Code: fd 91 02 e9 d3 f1 02 00 cc cc cc 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 f3 0f 1e fa 66 90 0f 00 2d 13 12 2a 00 fb f4 <c3> cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc cc 90 90 90 90 90
RSP: 0018:ffffffff8e807dc0 EFLAGS: 00000242
RAX: 00000000002ae719 RBX: ffffffff819bd2ea RCX: 0000000080000001
RDX: 0000000000000001 RSI: ffffffff8e257836 RDI: ffffffff8c4a6280
RBP: ffffffff8e807eb0 R08: ffff8880b843399b R09: 1ffff11017086733
R10: dffffc0000000000 R11: ffffed1017086734 R12: ffffffff905441f0
R13: 0000000000000000 R14: 0000000000000000 R15: 1ffffffff1d125d8
FS: 0000000000000000(0000) GS:ffff888124de2000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fd5ef1e92f8 CR3: 00000000751fc000 CR4: 00000000003526f0
Call Trace:
<TASK>
arch_safe_halt arch/x86/kernel/process.c:766 [inline]
default_idle+0x9/0x20 arch/x86/kernel/process.c:767
default_idle_call+0x72/0xb0 kernel/sched/idle.c:122
cpuidle_idle_call kernel/sched/idle.c:191 [inline]
do_idle+0x1ba/0x5d0 kernel/sched/idle.c:341
cpu_startup_entry+0x43/0x60 kernel/sched/idle.c:439
rest_init+0x2de/0x300 init/main.c:762
start_kernel+0x38f/0x3e0 init/main.c:1214
x86_64_start_reservations+0x24/0x30 arch/x86/kernel/head64.c:310
x86_64_start_kernel+0x143/0x1c0 arch/x86/kernel/head64.c:291
common_startup_64+0x13e/0x147
</TASK>
Reply: Based on the analysis of the provided kernel bug report and source code, here is the assessment of the vulnerability:
### Bug Explanation
The bug is a lock leak in the `userfaultfd` subsystem, specifically in the `mfill_get_vma()` function in `mm/userfaultfd.c`.
When `mfill_get_vma()` is called (e.g., during a `UFFDIO_COPY` ioctl), it first locks the destination VMA using `uffd_mfill_lock()` (which acquires the per-VMA read lock and increments `vma->vm_refcnt`) and then acquires a read lock on `ctx->map_changing_lock`.
After acquiring these locks, it performs several validation checks. If any of these checks fail (for example, if `ctx->mmap_changing` is true, or if a write-protect copy is requested on a VMA without `VM_UFFD_WP`), the code jumps to the `out_unlock` label to clean up and return an error:
```c
out_unlock:
mfill_put_vma(state);
return err;
```
However, `mfill_put_vma(state)` relies on `state->vma` to know which locks to release. The problem is that `state->vma` is only assigned at the very end of `mfill_get_vma()` (at the `out:` label). In the error paths, `state->vma` is still `NULL`.
When `mfill_put_vma()` sees that `state->vma` is `NULL`, it returns immediately without doing anything:
```c
static void mfill_put_vma(struct mfill_state *state)
{
if (!state->vma)
return;
// ... releases locks ...
}
```
As a result, both the **per-VMA read lock** and the **`ctx->map_changing_lock` read lock** are permanently leaked.
### Impact and Deadlock Scenario
If an attacker intentionally triggers this error path (e.g., by racing `UFFDIO_COPY` with `munmap`, or simply by passing `UFFDIO_COPY_MODE_WP` on a VMA not registered for write-protection), the locks are leaked.
Subsequently, if the process attempts to unmap or modify the VMA (e.g., via `munmap()`), the kernel will:
1. Acquire the `mmap_write_lock` for the process.
2. Call `vma_start_write()`, which waits uninterruptibly for `vma->vm_refcnt` to drop to 0.
3. Call `userfaultfd_unmap_prep()`, which waits uninterruptibly for `down_write(&ctx->map_changing_lock)`.
Because the read locks were leaked, the `munmap` thread will block forever in `D` state while holding the `mmap_write_lock`. Any other thread in the process (or the kernel's OOM reaper) that subsequently tries to acquire `mmap_read_lock` will also block forever. This perfectly matches the `syz.2.8` task blocked in `mmap_read_lock` shown in the bug report.
### Security Dimensions
* **Exploitable:** **No**. This is a pure logic bug leading to unreleased locks. It does not cause memory corruption, use-after-free, or out-of-bounds accesses, and cannot be used for privilege escalation.
* **Denial Of Service:** **Yes**. An attacker can reliably freeze their own process in an unkillable `D` state. Because the process cannot be killed (even by `SIGKILL` or the OOM killer), its memory cannot be reclaimed, leading to a resource leak. On systems with `kernel.hung_task_panic=1`, this will reliably panic and reboot the entire system.
* **Accessible From Unprivileged Processes:** **Yes**. While full `userfaultfd` access is often restricted to root or `CAP_SYS_PTRACE`, unprivileged users can always create a userfaultfd by passing the `UFFD_USER_MODE_ONLY` flag. This flag is sufficient to register a VMA and trigger the `UFFDIO_COPY` ioctl bug.
* **Accessible From User Namespaces:** **Yes**. For the same reasons as above.
* **VM Guest / Host / Network / Peripheral / Filesystem Trigger:** **No**. The bug is strictly tied to the local `userfaultfd` syscall/ioctl interface.
|
| 6/2 |
2026/05/30 11:31 |
llm |
expert |
0m
Model:
Tokens:
input: 9511
output: 21
thoughts: 988
Thoughts: **Analyzing a Hung Task in Userfaultfd**
Okay, so I've got this hung task report. The kernel's telling me that `syz.2.8:5969` has been blocked for over 143 seconds. That's a significant amount of time, clearly something is seriously stuck. Looking at the call trace, I see it's deep in the scheduler, ultimately blocked in `rwsem_down_read_slowpath`, which strongly suggests a lock contention issue.
Specifically, it's getting hung up trying to acquire the `mmap_read_lock` within `uffd_lock_vma`. This immediately triggers my mental alarm bells; deadlocks or starvation are the usual suspects when you see this pattern. I need to figure out exactly what's going on with these locks.
The lockdep output is confusing initially. It claims the task already *holds* the `mmap_read_lock`. However, I know lockdep can sometimes be misleading in these situations, potentially recording a lock as held *before* it's actually acquired, or it could mean the task is blocked while trying to *acquire* the lock. I suspect it is blocked on acquisition of the lock, which makes sense given the call stack.
Ah, and I remember `uffd_lock_vma` does indeed take the `mmap_read_lock`, but it does so on the destination mm, let me confirm. I need to dig into the `mm/userfaultfd.c` code for `uffd_lock_vma` to understand the lock acquisition sequence. I need to check how it interacts with other locks and what other tasks might be doing. That's where the root cause probably lies. This is a classic concurrency problem.
|
| 7/2 |
2026/05/30 11:31 |
tool |
grepper |
0m
Args: map[Expression:uffd_lock_vma]
Results: map[Output:mm/userfaultfd.c=91=struct vm_area_struct *find_vma_and_prepare_anon(struct mm_struct *mm,
--
mm/userfaultfd.c-108-/*
mm/userfaultfd.c:109: * uffd_lock_vma() - Lookup and lock vma corresponding to @address.
mm/userfaultfd.c-110- * @mm: mm to search vma in.
--
mm/userfaultfd.c-117- */
mm/userfaultfd.c:118:static struct vm_area_struct *uffd_lock_vma(struct mm_struct *mm,
mm/userfaultfd.c-119- unsigned long address)
--
mm/userfaultfd.c=148=static struct vm_area_struct *uffd_mfill_lock(struct mm_struct *dst_mm,
--
mm/userfaultfd.c-153-
mm/userfaultfd.c:154: dst_vma = uffd_lock_vma(dst_mm, dst_start);
mm/userfaultfd.c-155- if (IS_ERR(dst_vma) || validate_dst_vma(dst_vma, dst_start + len))
--
mm/userfaultfd.c=1678=static int uffd_move_lock(struct mm_struct *mm,
--
mm/userfaultfd.c-1686-
mm/userfaultfd.c:1687: vma = uffd_lock_vma(mm, dst_start);
mm/userfaultfd.c-1688- if (IS_ERR(vma))
--
mm/userfaultfd.c-1701- /*
mm/userfaultfd.c:1702: * Using uffd_lock_vma() to get src_vma can lead to following deadlock:
mm/userfaultfd.c-1703- *
]
|
| 8/2 |
2026/05/30 11:31 |
llm |
expert |
0m
Model:
Tokens:
input: 11399
output: 35
thoughts: 23
Thoughts: Okay, let's dive into these functions, `uffd_lock_vma` and `uffd_mfill_lock`. I need to understand how these relate to Userfaultfd and memory management. From their names, I'm already guessing they're locking mechanisms, likely related to Virtual Memory Areas (VMAs) and perhaps memory filling operations specifically within the Userfaultfd context. The "vma" suggests addressing protection for memory regions, while "mfill" hints at handling the initial population of data. I suspect `uffd_lock_vma` might be used for protecting access to a VMA before a userfault handler is invoked, and `uffd_mfill_lock` might be for coordinating the fill operation itself. I'll need to read the code to be sure and ascertain any relationships between the functions. I'm especially keen to see how they interact with page faults and how they prevent race conditions when the user-space process and the kernel are cooperating to handle memory access. Let's dig in.
|
| 9/2 |
2026/05/30 11:31 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:118 LineCount:50]
Results: map[Contents: 118: static struct vm_area_struct *uffd_lock_vma(struct mm_struct *mm,
119: unsigned long address)
120: {
121: struct vm_area_struct *vma;
122:
123: vma = lock_vma_under_rcu(mm, address);
124: if (vma) {
125: /*
126: * We know we're going to need to use anon_vma, so check
127: * that early.
128: */
129: if (!(vma->vm_flags & VM_SHARED) && unlikely(!vma->anon_vma))
130: vma_end_read(vma);
131: else
132: return vma;
133: }
134:
135: mmap_read_lock(mm);
136: vma = find_vma_and_prepare_anon(mm, address);
137: if (!IS_ERR(vma)) {
138: bool locked = vma_start_read_locked(vma);
139:
140: if (!locked)
141: vma = ERR_PTR(-EAGAIN);
142: }
143:
144: mmap_read_unlock(mm);
145: return vma;
146: }
147:
148: static struct vm_area_struct *uffd_mfill_lock(struct mm_struct *dst_mm,
149: unsigned long dst_start,
150: unsigned long len)
151: {
152: struct vm_area_struct *dst_vma;
153:
154: dst_vma = uffd_lock_vma(dst_mm, dst_start);
155: if (IS_ERR(dst_vma) || validate_dst_vma(dst_vma, dst_start + len))
156: return dst_vma;
157:
158: vma_end_read(dst_vma);
159: return ERR_PTR(-ENOENT);
160: }
161:
162: static void uffd_mfill_unlock(struct vm_area_struct *vma)
163: {
164: vma_end_read(vma);
165: }
166:
167: #else
]
|
| 10/2 |
2026/05/30 11:31 |
llm |
expert |
0m
Model:
Tokens:
input: 12407
output: 36
thoughts: 1054
Thoughts: **Analyzing a Blocked Reader in Userfaultfd**
Okay, so `uffd_lock_vma` calls `mmap_read_lock` followed by `mmap_read_unlock`. The task is blocked within `mmap_read_lock`, specifically at line 135. It's been blocked for a significant 143 seconds, indicating a potential hung task. This warrants further investigation for a deadlock scenario.
First, let's look at the other tasks. CPU 0 is idle, in `pv_native_safe_halt`, and CPU 1 is in `khungtaskd`. Now, where's the process that is holding the `mmap_write_lock`? I turn to the lockdep output.
The lockdep output indicates a handful of processes holding various locks, but notably, there's no task explicitly holding the `mmap_write_lock`. Hmm, that's odd. If nobody holds the write lock, why would the read lock be blocking?
Ah, right, `rwsem_down_read_slowpath` can block if there's a pending writer. Readers can block to avoid starving the writer. Okay, so where's the pending writer? But the lockdep output only shows *held* locks, not locks *waiting* to be acquired. A waiting writer would not appear in the "locks held" output. However, a writer waiting implies someone else holds a lock the writer needs. If no one holds the lock, the writer should've acquired it. So is it being held by someone not showing in lockdep? That's not right, lockdep should track all held locks.
Hold on, I see something. The `syz.2.8/5969` process is listed as holding the `mmap_lock` and it is blocked in `rwsem_down_read_slowpath`. I know that lockdep says:
1 lock held by syz.2.8/5969:
#0: ffff88807af95ab8 (&mm->mmap_lock){++++}-{4:4}, at: mmap_read_lock include/linux/mmap_lock.h:592 [inline]
#0: ffff88807af95ab8 (&mm->mmap_lock){++++}-{4:4}, at: uffd_lock_vma+0xb6/0x2d0 mm/userfaultfd.c:135
So Lockdep *says* it's held. Why would lockdep think it's held if it's blocked? Because `mmap_read_lock` calls `rwsem_acquire_read` before the actual lock operation. Lockdep registers the lock as acquired before the blocking call. Okay, so it's waiting, but no other task is holding the lock.
If no one else holds the lock, why is it blocking? Could the lock be corrupted? Could a task have exited without releasing it? Calling `mmap_write_unlock` would release it. Let's see if anyone *else* is even holding the read lock. Nope, the overall lock list doesn't show any other task holding `mmap_lock`. What about a task not tracked by lockdep? No, that's not possible, all are tracked. Maybe tracking was disabled for a task? Perhaps a corrupted lock count? Time to look deeper into `userfaultfd_copy`.
|
| 11/2 |
2026/05/30 11:31 |
tool |
read-file |
0m
Args: map[File:fs/userfaultfd.c FirstLine:1630 LineCount:50]
Results: map[Contents:1630: if (ret)
1631: goto out;
1632: ret = validate_range(ctx->mm, uffdio_copy.dst, uffdio_copy.len);
1633: if (ret)
1634: goto out;
1635:
1636: ret = -EINVAL;
1637: if (uffdio_copy.mode & ~(UFFDIO_COPY_MODE_DONTWAKE|UFFDIO_COPY_MODE_WP))
1638: goto out;
1639: if (uffdio_copy.mode & UFFDIO_COPY_MODE_WP)
1640: flags |= MFILL_ATOMIC_WP;
1641: if (mmget_not_zero(ctx->mm)) {
1642: ret = mfill_atomic_copy(ctx, uffdio_copy.dst, uffdio_copy.src,
1643: uffdio_copy.len, flags);
1644: mmput(ctx->mm);
1645: } else {
1646: return -ESRCH;
1647: }
1648: if (unlikely(put_user(ret, &user_uffdio_copy->copy)))
1649: return -EFAULT;
1650: if (ret < 0)
1651: goto out;
1652: VM_WARN_ON_ONCE(!ret);
1653: /* len == 0 would wake all */
1654: range.len = ret;
1655: if (!(uffdio_copy.mode & UFFDIO_COPY_MODE_DONTWAKE)) {
1656: range.start = uffdio_copy.dst;
1657: wake_userfault(ctx, &range);
1658: }
1659: ret = range.len == uffdio_copy.len ? 0 : -EAGAIN;
1660: out:
1661: return ret;
1662: }
1663:
1664: static int userfaultfd_zeropage(struct userfaultfd_ctx *ctx,
1665: unsigned long arg)
1666: {
1667: __s64 ret;
1668: struct uffdio_zeropage uffdio_zeropage;
1669: struct uffdio_zeropage __user *user_uffdio_zeropage;
1670: struct userfaultfd_wake_range range;
1671:
1672: user_uffdio_zeropage = (struct uffdio_zeropage __user *) arg;
1673:
1674: ret = -EAGAIN;
1675: if (unlikely(atomic_read(&ctx->mmap_changing))) {
1676: if (unlikely(put_user(ret, &user_uffdio_zeropage->zeropage)))
1677: return -EFAULT;
1678: goto out;
1679: }
]
|
| 12/2 |
2026/05/30 11:31 |
llm |
expert |
0m
Model:
Tokens:
input: 15079
output: 35
thoughts: 17
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Diving into Memory Filling Optimization**
Right, so I'm looking at `mfill_atomic_copy`. Let's see... ah, it calls `mfill_atomic`. Okay, that's not a surprise. `mfill_atomic_copy` is likely a specialized version built upon a more fundamental atomic fill operation, probably for copy operations specifically. This makes sense from an optimization perspective. You'd expect them to build in layers. `mfill_atomic` is likely the lower-level primitive, dealing directly with atomic writes at the memory level. Then, `mfill_atomic_copy` likely leverages that core function and adds some logic, probably optimizing for things like source and destination address alignment or maybe even pre-fetching in some architectures, to achieve faster memory copies with atomicity guarantees. I should really dig deeper into the implementation details of both functions. I bet the interplay between those two functions is the key to this particular optimization. I'll need to check the data flow and the conditions that cause one to be called over the other. Is there a performance model? Does `mfill_atomic_copy` handle overlapping ranges? What does it do with larger block sizes? I wonder how the architecture affects this.
|
| 13/2 |
2026/05/30 11:31 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:890 LineCount:50]
Results: map[Contents: 890: /*
891: * Sanitize the command parameters:
892: */
893: VM_WARN_ON_ONCE(dst_start & ~PAGE_MASK);
894: VM_WARN_ON_ONCE(len & ~PAGE_MASK);
895:
896: /* Does the address range wrap, or is the span zero-sized? */
897: VM_WARN_ON_ONCE(src_start + len <= src_start);
898: VM_WARN_ON_ONCE(dst_start + len <= dst_start);
899:
900: err = mfill_get_vma(&state);
901: if (err)
902: goto out;
903:
904: /*
905: * If this is a HUGETLB vma, pass off to appropriate routine
906: */
907: if (is_vm_hugetlb_page(state.vma))
908: return mfill_atomic_hugetlb(ctx, state.vma, dst_start,
909: src_start, len, flags);
910:
911: while (state.src_addr < src_start + len) {
912: VM_WARN_ON_ONCE(state.dst_addr >= dst_start + len);
913:
914: err = mfill_get_pmd(&state);
915: if (err)
916: break;
917:
918: /*
919: * For shmem mappings, khugepaged is allowed to remove page
920: * tables under us; pte_offset_map_lock() will deal with that.
921: */
922:
923: err = mfill_atomic_pte(&state);
924: cond_resched();
925:
926: if (!err) {
927: state.dst_addr += PAGE_SIZE;
928: state.src_addr += PAGE_SIZE;
929: copied += PAGE_SIZE;
930:
931: if (fatal_signal_pending(current))
932: err = -EINTR;
933: }
934: if (err)
935: break;
936: }
937:
938: mfill_put_vma(&state);
939: out:
]
|
| 14/2 |
2026/05/30 11:31 |
llm |
expert |
0m
Model:
Tokens:
input: 16127
output: 35
thoughts: 12
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in memory management and the kernel:
**Decoding mfill_get_vma: A Deep Dive**
Alright, so the system is calling `mfill_get_vma`. Immediately, I know this is a pretty low-level function related to memory filling, likely within the context of a memory management subsystem. This likely involves handling a memory fill operation, which could be initiated by system calls related to memory mapping or similar actions. My mind immediately starts running through possible scenarios. Is this a user-space request or a kernel-initiated fill? What kind of memory region are we dealing with? Is this related to demand paging, copy-on-write, or something else entirely?
I'm starting to think about the arguments that are being passed to this function. What kind of Virtual Memory Area (VMA) is it operating on? Is it part of a larger mapping? What about the offset? The size that needs to be filled? The permissions for the memory area? All of these things are critical to understand the larger context of why `mfill_get_vma` is being called and where the system is now. My brain is going through all the possible data structures that interact with VMAs like `mm_struct`, `vm_area_struct`, and maybe even the page table structures.
The term "fill" suggests we need to perform some action on memory. Is this an actual physical page allocation, or are we just setting up the virtual memory map? Is the fill operation simply to zero out a region, or is it populating it with data from a file or some other source? The specific implementation of `mfill_get_vma` will tell the story. I'm already anticipating having to check the return value, because it will tell me whether or not the operation was successful. If the fill fails, why? Was there an allocation issue? A permission problem? And on what side did the fill fail? User-space, kernel-side? This is where I go hunting for the root cause.
This function call is definitely a jumping-off point for more digging. But the information this call provides will let me find out precisely what's happening.
|
| 15/2 |
2026/05/30 11:31 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:200 LineCount:50]
Results: map[Contents: 200: up_read(&state->ctx->map_changing_lock);
201: uffd_mfill_unlock(state->vma);
202: state->vma = NULL;
203: }
204:
205: static int mfill_get_vma(struct mfill_state *state)
206: {
207: struct userfaultfd_ctx *ctx = state->ctx;
208: uffd_flags_t flags = state->flags;
209: struct vm_area_struct *dst_vma;
210: const struct vm_uffd_ops *ops;
211: int err;
212:
213: /*
214: * Make sure the vma is not shared, that the dst range is
215: * both valid and fully within a single existing vma.
216: */
217: dst_vma = uffd_mfill_lock(ctx->mm, state->dst_start, state->len);
218: if (IS_ERR(dst_vma))
219: return PTR_ERR(dst_vma);
220:
221: /*
222: * If memory mappings are changing because of non-cooperative
223: * operation (e.g. mremap) running in parallel, bail out and
224: * request the user to retry later
225: */
226: down_read(&ctx->map_changing_lock);
227: err = -EAGAIN;
228: if (atomic_read(&ctx->mmap_changing))
229: goto out_unlock;
230:
231: err = -EINVAL;
232:
233: /*
234: * shmem_zero_setup is invoked in mmap for MAP_ANONYMOUS|MAP_SHARED but
235: * it will overwrite vm_ops, so vma_is_anonymous must return false.
236: */
237: if (WARN_ON_ONCE(vma_is_anonymous(dst_vma) &&
238: dst_vma->vm_flags & VM_SHARED))
239: goto out_unlock;
240:
241: /*
242: * validate 'mode' now that we know the dst_vma: don't allow
243: * a wrprotect copy if the userfaultfd didn't register as WP.
244: */
245: if ((flags & MFILL_ATOMIC_WP) && !(dst_vma->vm_flags & VM_UFFD_WP))
246: goto out_unlock;
247:
248: if (is_vm_hugetlb_page(dst_vma))
249: goto out;
]
|
| 16/2 |
2026/05/30 11:31 |
llm |
expert |
0m
Model:
Tokens:
input: 17451
output: 23
thoughts: 860
Thoughts: **Analyzing a Persistent Userfaultfd Deadlock**
Okay, so I'm looking at this deadlock and tracing the execution path. It starts with `mfill_get_vma` calling `uffd_mfill_lock`, which in turn calls `uffd_lock_vma`. That function then calls `mmap_read_lock` which is where the problem arises. I know that if `mmap_read_lock` blocks, it means either another process has the `mmap_write_lock`, or there's a pending writer and I need to be careful of recursion.
The question then is, who has the lock? If the *same* task already holds the `mmap_read_lock`, acquiring it again should succeed *unless* there's a pending writer. And if there *is* a pending writer, trying to re-acquire the `mmap_read_lock` *will* deadlock. But if I only have one read lock, it won't be a recursive issue.
Let's check Lockdep's output: it shows the same lock held *twice*, which doesn't make sense. Ah, the first line is the inline call, the second line, the caller. It's the same acquisition. So, it's just one lock. So the function is blocked on its *first* acquisition. If it's the *first* acquisition, then something else *must* be holding the write lock or have a pending write. But I don't see another task holding the lock.
Could a task be blocked in a `D` state? But Lockdep shows all held locks. So, what about a task that *exited* without releasing the lock? That would be a bug! But that seems unlikely. What if the lock is held by a task that's *running*, and not blocked? The NMI backtrace only shows the idle process and `khungtaskd`. So no other task is running.
So, if no other task holds the lock, and no other task is running, *why* is `mmap_read_lock` blocking? Is there some context issue where I'm calling `mmap_read_lock` from a place I shouldn't be? No, it's a normal syscall. Time to dive into `rwsem_down_read_slowpath`. This looks like it's going to be a tough one.
|
| 17/2 |
2026/05/30 11:31 |
tool |
grepper |
0m
Args: map[Expression:rwsem_down_read_slowpath]
Results: map[Output:kernel/locking/rwsem.c=429=static void rwsem_mark_wake(struct rw_semaphore *sem,
--
kernel/locking/rwsem.c-577- * Ensure calling get_task_struct() before setting the reader
kernel/locking/rwsem.c:578: * waiter to nil such that rwsem_down_read_slowpath() cannot
kernel/locking/rwsem.c-579- * race with do_exit() by always holding a reference count
--
kernel/locking/rwsem.c=1016=static struct rw_semaphore __sched *
kernel/locking/rwsem.c:1017:rwsem_down_read_slowpath(struct rw_semaphore *sem, long count, unsigned int state)
kernel/locking/rwsem.c-1018-{
--
kernel/locking/rwsem.c=1284=static __always_inline int __down_read_common(struct rw_semaphore *sem, int state)
--
kernel/locking/rwsem.c-1290- if (!rwsem_read_trylock(sem, &count)) {
kernel/locking/rwsem.c:1291: if (IS_ERR(rwsem_down_read_slowpath(sem, count, state))) {
kernel/locking/rwsem.c-1292- ret = -EINTR;
]
|
| 18/2 |
2026/05/30 11:31 |
llm |
expert |
0m
Model:
Tokens:
input: 19195
output: 35
thoughts: 116
Thoughts: **Investigating a Potential Lock Contention Issue**
Okay, so I'm seeing `rwsem_down_read_slowpath` block, and that's immediately raising a red flag. It's supposed to block when the read lock is contended, right? But if no one *holds* the lock, then why would there *be* contention? That's the core question here. My gut feeling is telling me there's something amiss.
The most likely culprit, given the context, is that the lock count itself is corrupted somehow. It's an obvious starting point. I need to rule it out, or confirm it. I need to dig deeper.
First, I should probably check if there's any documented issue or known bug related to `userfaultfd` where it might call `mmap_read_lock` and then fail to properly release it. That would definitely be a prime suspect if it's there.
If `mmap_read_lock` *is* acquired, but some kind of error happens before the matching unlock, does the code leak that read lock? That's a crucial scenario to investigate. I need to trace the error paths to see if the lock is properly handled. I know I should be looking closely at `uffd_lock_vma`. That's where the locking happens, and it's the most likely place to start.
|
| 19/2 |
2026/05/30 11:31 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:118 LineCount:50]
Results: map[Contents: 118: static struct vm_area_struct *uffd_lock_vma(struct mm_struct *mm,
119: unsigned long address)
120: {
121: struct vm_area_struct *vma;
122:
123: vma = lock_vma_under_rcu(mm, address);
124: if (vma) {
125: /*
126: * We know we're going to need to use anon_vma, so check
127: * that early.
128: */
129: if (!(vma->vm_flags & VM_SHARED) && unlikely(!vma->anon_vma))
130: vma_end_read(vma);
131: else
132: return vma;
133: }
134:
135: mmap_read_lock(mm);
136: vma = find_vma_and_prepare_anon(mm, address);
137: if (!IS_ERR(vma)) {
138: bool locked = vma_start_read_locked(vma);
139:
140: if (!locked)
141: vma = ERR_PTR(-EAGAIN);
142: }
143:
144: mmap_read_unlock(mm);
145: return vma;
146: }
147:
148: static struct vm_area_struct *uffd_mfill_lock(struct mm_struct *dst_mm,
149: unsigned long dst_start,
150: unsigned long len)
151: {
152: struct vm_area_struct *dst_vma;
153:
154: dst_vma = uffd_lock_vma(dst_mm, dst_start);
155: if (IS_ERR(dst_vma) || validate_dst_vma(dst_vma, dst_start + len))
156: return dst_vma;
157:
158: vma_end_read(dst_vma);
159: return ERR_PTR(-ENOENT);
160: }
161:
162: static void uffd_mfill_unlock(struct vm_area_struct *vma)
163: {
164: vma_end_read(vma);
165: }
166:
167: #else
]
|
| 20/2 |
2026/05/30 11:31 |
llm |
expert |
0m
Model:
Tokens:
input: 20372
output: 37
thoughts: 1057
Thoughts: **Analyzing a Potential mmap_lock Contention Issue**
Okay, so I'm looking at this `uffd_lock_vma` function and something's not quite right. It seems like it can return a VMA with the per-VMA lock already held, but it doesn't hold `mmap_read_lock` when returning. That part's clear. But the stack trace is showing the task is *blocked* inside `mmap_read_lock`. The trace looks like: `mmap_read_lock` -> `uffd_lock_vma` -> `uffd_mfill_lock` -> `mfill_get_vma`. It's not holding the lock; it's trying to acquire it.
My initial thought is that some other process is holding `mmap_write_lock`, causing the block. But lockdep says there's no active `mmap_write_lock` holder. Very strange. And if the process itself held the write lock, lockdep *would* show it. I checked the lockdep output. It indicates the task is trying to acquire the read lock.
So the task is blocked, but lockdep doesn't show any other task holding the write lock. How can this be? Is it a lock corruption? I considered the possibility that a task could be holding the lock, but be hidden from lockdep somehow... but that's unlikely, as lockdep is pretty comprehensive. What if a task acquired the write lock and exited without releasing it? Possible, but also unlikely.
I checked the system for other tasks, since I only see CPU 0 and 1 in the NMI backtrace. CPU 0 is idle, CPU 1 is `khungtaskd`. The other tasks are sleeping, which would normally show up in the lockdep output if they were holding a lock. I re-examined the lockdep output, and it shows several tasks are holding different locks, but no one's holding the write lock. It looks like `khungtaskd`, `getty`, `udevd`, `kworker`, and `syz.2.8` are holding their respective locks.
I'm stumped. How can `rwsem_down_read_slowpath` block if there's no holder of the lock? Looks like I need to delve into the `rwsem_down_read_slowpath` implementation to understand exactly what's happening. Something is preventing `mmap_read_lock` from acquiring, but I can't determine the cause from the lockdep and stack information alone.
|
| 21/2 |
2026/05/30 11:32 |
tool |
read-file |
0m
Args: map[File:kernel/locking/rwsem.c FirstLine:1016 LineCount:50]
Results: map[Contents:1016: static struct rw_semaphore __sched *
1017: rwsem_down_read_slowpath(struct rw_semaphore *sem, long count, unsigned int state)
1018: {
1019: long adjustment = -RWSEM_READER_BIAS;
1020: long rcnt = (count >> RWSEM_READER_SHIFT);
1021: struct rwsem_waiter waiter, *first;
1022: DEFINE_WAKE_Q(wake_q);
1023:
1024: /*
1025: * To prevent a constant stream of readers from starving a sleeping
1026: * writer, don't attempt optimistic lock stealing if the lock is
1027: * very likely owned by readers.
1028: */
1029: if ((atomic_long_read(&sem->owner) & RWSEM_READER_OWNED) &&
1030: (rcnt > 1) && !(count & RWSEM_WRITER_LOCKED))
1031: goto queue;
1032:
1033: /*
1034: * Reader optimistic lock stealing.
1035: */
1036: if (!(count & (RWSEM_WRITER_LOCKED | RWSEM_FLAG_HANDOFF))) {
1037: rwsem_set_reader_owned(sem);
1038: lockevent_inc(rwsem_rlock_steal);
1039:
1040: /*
1041: * Wake up other readers in the wait queue if it is
1042: * the first reader.
1043: */
1044: if ((rcnt == 1) && (count & RWSEM_FLAG_WAITERS)) {
1045: raw_spin_lock_irq(&sem->wait_lock);
1046: if (sem->first_waiter)
1047: rwsem_mark_wake(sem, RWSEM_WAKE_READ_OWNED,
1048: &wake_q);
1049: raw_spin_unlock_irq(&sem->wait_lock);
1050: wake_up_q(&wake_q);
1051: }
1052: return sem;
1053: }
1054:
1055: queue:
1056: waiter.task = current;
1057: waiter.type = RWSEM_WAITING_FOR_READ;
1058: waiter.timeout = jiffies + RWSEM_WAIT_TIMEOUT;
1059: waiter.handoff_set = false;
1060:
1061: raw_spin_lock_irq(&sem->wait_lock);
1062: first = sem->first_waiter;
1063: if (!first) {
1064: /*
1065: * In case the wait queue is empty and the lock isn't owned
]
|
| 22/2 |
2026/05/30 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 22812
output: 35
thoughts: 424
Thoughts: **Debugging a Potential Read-Write Semaphore Block**
Okay, so `rwsem_down_read_slowpath` is blocking, which is unusual. The initial thought is that a writer is pending, but that should mean the writer is in the wait queue. If a writer *is* waiting, it implies it called `down_write` and is blocked, right? Now, if it's blocked, it should appear in the lockdep output as attempting to acquire the lock. I need to be sure... does lockdep show tasks that are *trying* to get a lock? Yes! I see "1 lock held by syz.2.8/5969: ... at: mmap_read_lock", which means that task is *trying* to acquire the lock.
If there was a writer attempting the lock, it would also be in the lockdep output, wouldn't it? But there's *nothing* else in the lockdep output. So, this means there's no writer contending for the lock. If no one's writing, and no one *currently* holds the lock, why is `rwsem_down_read_slowpath` blocking?
Could the lock be held by a task not shown in lockdep due to asynchronous context? No, that's not possible, since `mmap_write_lock` is only acquired from process context. Okay, could the `mmap_lock` be corrupted somehow? Perhaps a task acquired `mmap_write_lock`, called `mmap_write_unlock`, and there's a bug preventing the readers from being woken up? Or a task acquired the lock, and then exited without unlocking? If a task exited without unlocking, lockdep would flag a "lock held when returning to user space" or "lock held when exiting" message. I haven't seen that.
Hmm... wait, `userfaultfd` is involved, and it has `mmap_changing`. I should check `mfill_get_vma` again and double-check those code paths.
|
| 23/2 |
2026/05/30 11:32 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:213 LineCount:50]
Results: map[Contents: 213: /*
214: * Make sure the vma is not shared, that the dst range is
215: * both valid and fully within a single existing vma.
216: */
217: dst_vma = uffd_mfill_lock(ctx->mm, state->dst_start, state->len);
218: if (IS_ERR(dst_vma))
219: return PTR_ERR(dst_vma);
220:
221: /*
222: * If memory mappings are changing because of non-cooperative
223: * operation (e.g. mremap) running in parallel, bail out and
224: * request the user to retry later
225: */
226: down_read(&ctx->map_changing_lock);
227: err = -EAGAIN;
228: if (atomic_read(&ctx->mmap_changing))
229: goto out_unlock;
230:
231: err = -EINVAL;
232:
233: /*
234: * shmem_zero_setup is invoked in mmap for MAP_ANONYMOUS|MAP_SHARED but
235: * it will overwrite vm_ops, so vma_is_anonymous must return false.
236: */
237: if (WARN_ON_ONCE(vma_is_anonymous(dst_vma) &&
238: dst_vma->vm_flags & VM_SHARED))
239: goto out_unlock;
240:
241: /*
242: * validate 'mode' now that we know the dst_vma: don't allow
243: * a wrprotect copy if the userfaultfd didn't register as WP.
244: */
245: if ((flags & MFILL_ATOMIC_WP) && !(dst_vma->vm_flags & VM_UFFD_WP))
246: goto out_unlock;
247:
248: if (is_vm_hugetlb_page(dst_vma))
249: goto out;
250:
251: ops = vma_uffd_ops(dst_vma);
252: if (!ops)
253: goto out_unlock;
254:
255: if (uffd_flags_mode_is(flags, MFILL_ATOMIC_CONTINUE) &&
256: !ops->get_folio_noalloc)
257: goto out_unlock;
258:
259: out:
260: state->vma = dst_vma;
261: return 0;
262:
]
|
| 24/2 |
2026/05/30 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 24487
output: 35
thoughts: 480
Thoughts: **Analyzing a Potential Locking Issue in mfill_atomic**
Okay, let's break this down. My initial concern stems from the interaction between `mfill_get_vma`, the per-VMA lock it acquires, and the `ctx->map_changing_lock`. I see `mfill_get_vma` calls `uffd_mfill_lock`, which in turn, I know, returns a VMA with the per-VMA lock already held, likely through `uffd_lock_vma`. That much is straightforward. `uffd_lock_vma`, from what I recall, uses either `vma_start_read_locked` or `lock_vma_under_rcu` -- meaning the VMA is locked for read. Then, *within the same context*, `mfill_get_vma` calls `down_read(&ctx->map_changing_lock)`.
The crucial question is: is it valid to acquire `ctx->map_changing_lock` for read *while* holding a per-VMA read lock? That's what I need to verify. My memory is jogged -- I recall `mfill_get_vma` is actually called inside a loop within `mfill_atomic`. The code confirms my recollection:
```c
err = mfill_get_vma(&state);
if (err)
goto out;
if (is_vm_hugetlb_page(state.vma))
return mfill_atomic_hugetlb(ctx, state.vma, dst_start,
src_start, len, flags);
while (state.src_addr < src_start + len) {
err = mfill_get_pmd(&state);
if (err)
break;
err = mfill_atomic_pte(&state);
cond_resched();
if (!err) {
state.dst_addr += PAGE_SIZE;
state.src_addr += PAGE_SIZE;
copied += PAGE_SIZE;
if (fatal_signal_pending(current))
err = -EINTR;
}
if (err)
break;
}
mfill_put_vma(&state);
```
The loop structure raises another important question: does `mfill_atomic_pte` *release* the VMA lock? If it does, the locking order might be correct. If it doesn't, this looks like a potential deadlock waiting to happen, or at least a significant performance bottleneck. I need to examine `mfill_atomic_pte` more closely to confirm.
|
| 25/2 |
2026/05/30 11:32 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:800 LineCount:50]
Results: map[Contents: 800: hugetlb_vma_unlock_read(dst_vma);
801: mutex_unlock(&hugetlb_fault_mutex_table[hash]);
802:
803: cond_resched();
804:
805: if (unlikely(err == -ENOENT)) {
806: up_read(&ctx->map_changing_lock);
807: uffd_mfill_unlock(dst_vma);
808: VM_WARN_ON_ONCE(!folio);
809:
810: err = copy_folio_from_user(folio,
811: (const void __user *)src_addr, true);
812: if (unlikely(err)) {
813: err = -EFAULT;
814: goto out;
815: }
816:
817: dst_vma = NULL;
818: goto retry;
819: } else
820: VM_WARN_ON_ONCE(folio);
821:
822: if (!err) {
823: dst_addr += vma_hpagesize;
824: src_addr += vma_hpagesize;
825: copied += vma_hpagesize;
826:
827: if (fatal_signal_pending(current))
828: err = -EINTR;
829: }
830: if (err)
831: break;
832: }
833:
834: out_unlock:
835: up_read(&ctx->map_changing_lock);
836: out_unlock_vma:
837: uffd_mfill_unlock(dst_vma);
838: out:
839: if (folio)
840: folio_put(folio);
841: VM_WARN_ON_ONCE(copied < 0);
842: VM_WARN_ON_ONCE(err > 0);
843: VM_WARN_ON_ONCE(!copied && !err);
844: return copied ? copied : err;
845: }
846: #else /* !CONFIG_HUGETLB_PAGE */
847: /* fail at build time if gcc attempts to use this */
848: extern ssize_t mfill_atomic_hugetlb(struct userfaultfd_ctx *ctx,
849: struct vm_area_struct *dst_vma,
]
|
| 26/2 |
2026/05/30 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 26320
output: 20
thoughts: 22
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Missing Piece Alert: Hunting for `mfill_atomic_pte`**
Okay, hold on a second. This code snippet… something's off. I'm staring at this and realizing I don't see `mfill_atomic_pte` anywhere in the context provided. That's a key function in this area, isn't it? My brain immediately jumps to "where is it?" This absence throws a flag. I need to track it down. Where is it defined? Where is it being used in relation to this code? I'm going to have to go hunting for that function definition now. It's fundamental that I understand how this interacts with the rest of the memory management setup. The logical next step is to locate its place in the bigger picture.
|
| 27/2 |
2026/05/30 11:32 |
tool |
grepper |
0m
Args: map[Expression:mfill_atomic_pte]
Results: map[Output:include/linux/hugetlb.h=140=vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
--
include/linux/hugetlb.h-142-#ifdef CONFIG_USERFAULTFD
include/linux/hugetlb.h:143:int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
include/linux/hugetlb.h-144- struct vm_area_struct *dst_vma,
--
include/linux/hugetlb.h=390=static inline int is_hugepage_only_range(struct mm_struct *mm,
--
include/linux/hugetlb.h-396-#ifdef CONFIG_USERFAULTFD
include/linux/hugetlb.h:397:static inline int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
include/linux/hugetlb.h-398- struct vm_area_struct *dst_vma,
--
mm/hugetlb.c=5961=vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
--
mm/hugetlb.c-6153-/*
mm/hugetlb.c:6154: * Can probably be eliminated, but still used by hugetlb_mfill_atomic_pte().
mm/hugetlb.c-6155- */
mm/hugetlb.c=6156=static struct folio *alloc_hugetlb_folio_vma(struct hstate *h,
--
mm/hugetlb.c-6179-/*
mm/hugetlb.c:6180: * Used by userfaultfd UFFDIO_* ioctls. Based on userfaultfd's mfill_atomic_pte
mm/hugetlb.c-6181- * with modifications for hugetlb pages.
mm/hugetlb.c-6182- */
mm/hugetlb.c:6183:int hugetlb_mfill_atomic_pte(pte_t *dst_pte,
mm/hugetlb.c-6184- struct vm_area_struct *dst_vma,
--
mm/khugepaged.c=1488=static enum scan_result try_collapse_pte_mapped_thp(struct mm_struct *mm, unsigned long addr,
--
mm/khugepaged.c-1595- * page_table_lock) ptl nests inside pml. The less time we hold pml,
mm/khugepaged.c:1596: * the better; but userfaultfd's mfill_atomic_pte() on a private VMA
mm/khugepaged.c-1597- * inserts a valid as-if-COWed PTE without even looking up page cache.
--
mm/userfaultfd.c=448=static int mfill_copy_folio_retry(struct mfill_state *state, struct folio *folio)
--
mm/userfaultfd.c-476-
mm/userfaultfd.c:477:static int __mfill_atomic_pte(struct mfill_state *state,
mm/userfaultfd.c-478- const struct vm_uffd_ops *ops)
--
mm/userfaultfd.c-540-
mm/userfaultfd.c:541:static int mfill_atomic_pte_copy(struct mfill_state *state)
mm/userfaultfd.c-542-{
--
mm/userfaultfd.c-556-
mm/userfaultfd.c:557: return __mfill_atomic_pte(state, ops);
mm/userfaultfd.c-558-}
mm/userfaultfd.c-559-
mm/userfaultfd.c:560:static int mfill_atomic_pte_zeroed_folio(struct mfill_state *state)
mm/userfaultfd.c-561-{
--
mm/userfaultfd.c-563-
mm/userfaultfd.c:564: return __mfill_atomic_pte(state, ops);
mm/userfaultfd.c-565-}
mm/userfaultfd.c-566-
mm/userfaultfd.c:567:static int mfill_atomic_pte_zeropage(struct mfill_state *state)
mm/userfaultfd.c-568-{
--
mm/userfaultfd.c-577- (dst_vma->vm_flags & VM_SHARED))
mm/userfaultfd.c:578: return mfill_atomic_pte_zeroed_folio(state);
mm/userfaultfd.c-579-
--
mm/userfaultfd.c-603-/* Handles UFFDIO_CONTINUE for all shmem VMAs (shared or private). */
mm/userfaultfd.c:604:static int mfill_atomic_pte_continue(struct mfill_state *state)
mm/userfaultfd.c-605-{
--
mm/userfaultfd.c-644-/* Handles UFFDIO_POISON for all non-hugetlb VMAs. */
mm/userfaultfd.c:645:static int mfill_atomic_pte_poison(struct mfill_state *state)
mm/userfaultfd.c-646-{
--
mm/userfaultfd.c=688=static __always_inline ssize_t mfill_atomic_hugetlb(
--
mm/userfaultfd.c-796-
mm/userfaultfd.c:797: err = hugetlb_mfill_atomic_pte(dst_pte, dst_vma, dst_addr,
mm/userfaultfd.c-798- src_addr, flags, &folio);
--
mm/userfaultfd.c=848=extern ssize_t mfill_atomic_hugetlb(struct userfaultfd_ctx *ctx,
--
mm/userfaultfd.c-855-
mm/userfaultfd.c:856:static __always_inline ssize_t mfill_atomic_pte(struct mfill_state *state)
mm/userfaultfd.c-857-{
--
mm/userfaultfd.c-860- if (uffd_flags_mode_is(flags, MFILL_ATOMIC_CONTINUE))
mm/userfaultfd.c:861: return mfill_atomic_pte_continue(state);
mm/userfaultfd.c-862- if (uffd_flags_mode_is(flags, MFILL_ATOMIC_POISON))
mm/userfaultfd.c:863: return mfill_atomic_pte_poison(state);
mm/userfaultfd.c-864- if (uffd_flags_mode_is(flags, MFILL_ATOMIC_COPY))
mm/userfaultfd.c:865: return mfill_atomic_pte_copy(state);
mm/userfaultfd.c-866- if (uffd_flags_mode_is(flags, MFILL_ATOMIC_ZEROPAGE))
mm/userfaultfd.c:867: return mfill_atomic_pte_zeropage(state);
mm/userfaultfd.c-868-
--
mm/userfaultfd.c=872=static __always_inline ssize_t mfill_atomic(struct userfaultfd_ctx *ctx,
--
mm/userfaultfd.c-922-
mm/userfaultfd.c:923: err = mfill_atomic_pte(&state);
mm/userfaultfd.c-924- cond_resched();
]
|
| 28/2 |
2026/05/30 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 28477
output: 35
thoughts: 47
Thoughts: Okay, here's my interpretation of that thought process, from an expert's perspective:
**Diving into the mfill_atomic_pte Abstraction**
Alright, hang on. I'm seeing that `mfill_atomic_pte` doesn't do the heavy lifting directly. It looks like it's a wrapper, calling `mfill_atomic_pte_copy`. And *that* in turn calls `__mfill_atomic_pte`. It's a nice layered approach, but before I get too deep in the weeds, I need to get a clear understanding of what's going on.
So, let's back up a step. Right now, my immediate focus is on that middle function, `mfill_atomic_pte_copy`. That's where I need to start. I need to understand what it's doing, what its parameters are, and how it relates to the broader memory fill mechanism. I'll check its implementation. The name makes it sound like a copy, but I want to verify if that is the core action it is performing. Let's see...
|
| 29/2 |
2026/05/30 11:32 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:540 LineCount:50]
Results: map[Contents: 540:
541: static int mfill_atomic_pte_copy(struct mfill_state *state)
542: {
543: const struct vm_uffd_ops *ops = vma_uffd_ops(state->vma);
544:
545: /*
546: * The normal page fault path for a MAP_PRIVATE mapping in a
547: * file-backed VMA will invoke the fault, fill the hole in the file and
548: * COW it right away. The result generates plain anonymous memory.
549: * So when we are asked to fill a hole in a MAP_PRIVATE mapping, we'll
550: * generate anonymous memory directly without actually filling the
551: * hole. For the MAP_PRIVATE case the robustness check only happens in
552: * the pagetable (to verify it's still none) and not in the page cache.
553: */
554: if (!(state->vma->vm_flags & VM_SHARED))
555: ops = &anon_uffd_ops;
556:
557: return __mfill_atomic_pte(state, ops);
558: }
559:
560: static int mfill_atomic_pte_zeroed_folio(struct mfill_state *state)
561: {
562: const struct vm_uffd_ops *ops = vma_uffd_ops(state->vma);
563:
564: return __mfill_atomic_pte(state, ops);
565: }
566:
567: static int mfill_atomic_pte_zeropage(struct mfill_state *state)
568: {
569: struct vm_area_struct *dst_vma = state->vma;
570: unsigned long dst_addr = state->dst_addr;
571: pmd_t *dst_pmd = state->pmd;
572: pte_t _dst_pte, *dst_pte;
573: spinlock_t *ptl;
574: int ret;
575:
576: if (mm_forbids_zeropage(dst_vma->vm_mm) ||
577: (dst_vma->vm_flags & VM_SHARED))
578: return mfill_atomic_pte_zeroed_folio(state);
579:
580: _dst_pte = pte_mkspecial(pfn_pte(zero_pfn(dst_addr),
581: dst_vma->vm_page_prot));
582: ret = -EAGAIN;
583: dst_pte = pte_offset_map_lock(dst_vma->vm_mm, dst_pmd, dst_addr, &ptl);
584: if (!dst_pte)
585: goto out;
586: if (mfill_file_over_size(dst_vma, dst_addr)) {
587: ret = -EFAULT;
588: goto out_unlock;
589: }
]
|
| 30/2 |
2026/05/30 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 29674
output: 35
thoughts: 13
Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to a fellow expert:
**Investigating `__mfill_atomic_pte`**
Alright, let's dive into this. The mental checklist starts with `__mfill_atomic_pte`. It's where I need to focus my attention. It's a key function in this code path, and I'm assuming it's involved in, well, atomic PTE operations, obviously. But the specific context is what I'm interested in; what's happening just *around* this function call, and what's the broader architectural implication. I'm going to want to understand its purpose, its implementation, any side effects, and how it handles concurrency. I'm already imagining potential areas for optimization or, conversely, areas that might be introducing subtle bugs. Time to dig in and unravel the details. I expect there is much going on there, likely hidden in assembly...
|
| 31/2 |
2026/05/30 11:32 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:477 LineCount:50]
Results: map[Contents: 477: static int __mfill_atomic_pte(struct mfill_state *state,
478: const struct vm_uffd_ops *ops)
479: {
480: unsigned long dst_addr = state->dst_addr;
481: unsigned long src_addr = state->src_addr;
482: uffd_flags_t flags = state->flags;
483: struct folio *folio;
484: int ret;
485:
486: folio = ops->alloc_folio(state->vma, state->dst_addr);
487: if (!folio)
488: return -ENOMEM;
489:
490: if (uffd_flags_mode_is(flags, MFILL_ATOMIC_COPY)) {
491: ret = mfill_copy_folio_locked(folio, src_addr);
492: /*
493: * Fallback to copy_from_user outside mmap_lock.
494: * If retry is successful, mfill_copy_folio_locked() returns
495: * with locks retaken by mfill_get_vma().
496: * If there was an error, we must mfill_put_vma() anyway and it
497: * will take care of unlocking if needed.
498: */
499: if (unlikely(ret)) {
500: ret = mfill_copy_folio_retry(state, folio);
501: if (ret)
502: goto err_folio_put;
503: }
504: } else if (uffd_flags_mode_is(flags, MFILL_ATOMIC_ZEROPAGE)) {
505: clear_user_highpage(&folio->page, state->dst_addr);
506: } else {
507: VM_WARN_ONCE(1, "unknown UFFDIO operation");
508: }
509:
510: /*
511: * The memory barrier inside __folio_mark_uptodate makes sure that
512: * preceding stores to the page contents become visible before
513: * the set_pte_at() write.
514: */
515: __folio_mark_uptodate(folio);
516:
517: if (ops->filemap_add) {
518: ret = ops->filemap_add(folio, state->vma, state->dst_addr);
519: if (ret)
520: goto err_folio_put;
521: }
522:
523: ret = mfill_atomic_install_pte(state->pmd, state->vma, dst_addr,
524: &folio->page, flags);
525: if (ret)
526: goto err_filemap_remove;
]
|
| 32/2 |
2026/05/30 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 30746
output: 35
thoughts: 15
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**My Brain's Initial Reaction to `mfill_copy_folio_retry`**
Ah, the appearance of `mfill_copy_folio_retry`! Immediately, the cogs start turning. This signals a potential bottleneck or, at least, a delicate point in the folio copy process. My mind jumps to possibilities: is this retrying after an allocation failure, a checksum mismatch, or perhaps a transient network issue during a distributed copy? The fact that it's *retrying* is key. It implies a known failure mode that's recoverable, which is usually a good thing – resilience is critical. But it also raises questions about the failure criteria. How many retries? What's the backoff strategy? The specific implementation details matter. Are we talking synchronous or asynchronous retries? And which function is it called from? My thoughts immediately starts trying to recall the surrounding code context, it is probably a shared memory situation, so memory allocation will be in the mix. I need to understand why this retry mechanism is necessary, and I better double check the error handling here!
|
| 33/2 |
2026/05/30 11:32 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:448 LineCount:50]
Results: map[Contents: 448: static int mfill_copy_folio_retry(struct mfill_state *state, struct folio *folio)
449: {
450: unsigned long src_addr = state->src_addr;
451: void *kaddr;
452: int err;
453:
454: /* retry copying with mm_lock dropped */
455: mfill_put_vma(state);
456:
457: kaddr = kmap_local_folio(folio, 0);
458: err = copy_from_user(kaddr, (const void __user *) src_addr, PAGE_SIZE);
459: kunmap_local(kaddr);
460: if (unlikely(err))
461: return -EFAULT;
462:
463: flush_dcache_folio(folio);
464:
465: /* reget VMA and PMD, they could change underneath us */
466: err = mfill_get_vma(state);
467: if (err)
468: return err;
469:
470: err = mfill_get_pmd(state);
471: if (err)
472: return err;
473:
474: return 0;
475: }
476:
477: static int __mfill_atomic_pte(struct mfill_state *state,
478: const struct vm_uffd_ops *ops)
479: {
480: unsigned long dst_addr = state->dst_addr;
481: unsigned long src_addr = state->src_addr;
482: uffd_flags_t flags = state->flags;
483: struct folio *folio;
484: int ret;
485:
486: folio = ops->alloc_folio(state->vma, state->dst_addr);
487: if (!folio)
488: return -ENOMEM;
489:
490: if (uffd_flags_mode_is(flags, MFILL_ATOMIC_COPY)) {
491: ret = mfill_copy_folio_locked(folio, src_addr);
492: /*
493: * Fallback to copy_from_user outside mmap_lock.
494: * If retry is successful, mfill_copy_folio_locked() returns
495: * with locks retaken by mfill_get_vma().
496: * If there was an error, we must mfill_put_vma() anyway and it
497: * will take care of unlocking if needed.
]
|
| 34/2 |
2026/05/30 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 31819
output: 35
thoughts: 247
Thoughts: **Double-Checking the Memory Filling Logic**
Okay, so I'm tracing through this `mfill_copy_folio_retry` function. It looks like it calls `mfill_put_vma(state)` after `mfill_put_vma(state)`. It then makes a user space copy, and then *again* calls `mfill_get_vma(state)`.
Alright, if `mfill_get_vma(state)` fails, it propagates the error, which makes sense. Crucially, that error then returns through `__mfill_atomic_pte`, right? And that propagates the failure.
Now, let's look at how that integrates into `mfill_atomic`. It looks like this:
```c
err = mfill_atomic_pte(&state);
cond_resched();
if (!err) {
...
}
if (err)
break;
}
mfill_put_vma(&state);
```
Hang on a second… If `mfill_get_vma` fails *inside* `mfill_copy_folio_retry`, that error *will* be returned from `mfill_copy_folio_retry`. Then, `mfill_atomic` will break out of its loop due to the error. And it's calling `mfill_put_vma(&state)` *again* after the loop.
That means `mfill_put_vma(&state)` is being called twice, when the VMA has already been released due to the error, and that's likely a double-free, and probably a bug! Let me go check `mfill_put_vma` to be sure.
|
| 35/2 |
2026/05/30 11:32 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:195 LineCount:50]
Results: map[Contents: 195: static void mfill_put_vma(struct mfill_state *state)
196: {
197: if (!state->vma)
198: return;
199:
200: up_read(&state->ctx->map_changing_lock);
201: uffd_mfill_unlock(state->vma);
202: state->vma = NULL;
203: }
204:
205: static int mfill_get_vma(struct mfill_state *state)
206: {
207: struct userfaultfd_ctx *ctx = state->ctx;
208: uffd_flags_t flags = state->flags;
209: struct vm_area_struct *dst_vma;
210: const struct vm_uffd_ops *ops;
211: int err;
212:
213: /*
214: * Make sure the vma is not shared, that the dst range is
215: * both valid and fully within a single existing vma.
216: */
217: dst_vma = uffd_mfill_lock(ctx->mm, state->dst_start, state->len);
218: if (IS_ERR(dst_vma))
219: return PTR_ERR(dst_vma);
220:
221: /*
222: * If memory mappings are changing because of non-cooperative
223: * operation (e.g. mremap) running in parallel, bail out and
224: * request the user to retry later
225: */
226: down_read(&ctx->map_changing_lock);
227: err = -EAGAIN;
228: if (atomic_read(&ctx->mmap_changing))
229: goto out_unlock;
230:
231: err = -EINVAL;
232:
233: /*
234: * shmem_zero_setup is invoked in mmap for MAP_ANONYMOUS|MAP_SHARED but
235: * it will overwrite vm_ops, so vma_is_anonymous must return false.
236: */
237: if (WARN_ON_ONCE(vma_is_anonymous(dst_vma) &&
238: dst_vma->vm_flags & VM_SHARED))
239: goto out_unlock;
240:
241: /*
242: * validate 'mode' now that we know the dst_vma: don't allow
243: * a wrprotect copy if the userfaultfd didn't register as WP.
244: */
]
|
| 36/2 |
2026/05/30 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 33251
output: 35
thoughts: 59
Thoughts: **Analyzing Potential Null Pointer Dereference in Memory Filling**
Okay, so I'm digging into this `mfill_put_vma` function and immediately, my spider senses are tingling. I see this `if (!state->vma) return;` check at the beginning. That's good defensive programming, but it got me thinking. If `state->vma` *is* null, that implies that `mfill_get_vma` failed at some point. It's time to follow the breadcrumbs. I need to understand what `mfill_get_vma` does when it *doesn't* succeed in getting a valid VMA. The error paths are the most important part here. Let's see where that function can fail and what it does in those failure scenarios. That's where I need to focus my attention to determine if this potential null check is warranted.
|
| 37/2 |
2026/05/30 11:32 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:245 LineCount:50]
Results: map[Contents: 245: if ((flags & MFILL_ATOMIC_WP) && !(dst_vma->vm_flags & VM_UFFD_WP))
246: goto out_unlock;
247:
248: if (is_vm_hugetlb_page(dst_vma))
249: goto out;
250:
251: ops = vma_uffd_ops(dst_vma);
252: if (!ops)
253: goto out_unlock;
254:
255: if (uffd_flags_mode_is(flags, MFILL_ATOMIC_CONTINUE) &&
256: !ops->get_folio_noalloc)
257: goto out_unlock;
258:
259: out:
260: state->vma = dst_vma;
261: return 0;
262:
263: out_unlock:
264: mfill_put_vma(state);
265: return err;
266: }
267:
268: static pmd_t *mm_alloc_pmd(struct mm_struct *mm, unsigned long address)
269: {
270: pgd_t *pgd;
271: p4d_t *p4d;
272: pud_t *pud;
273:
274: pgd = pgd_offset(mm, address);
275: p4d = p4d_alloc(mm, pgd, address);
276: if (!p4d)
277: return NULL;
278: pud = pud_alloc(mm, p4d, address);
279: if (!pud)
280: return NULL;
281: /*
282: * Note that we didn't run this because the pmd was
283: * missing, the *pmd may be already established and in
284: * turn it may also be a trans_huge_pmd.
285: */
286: return pmd_alloc(mm, pud, address);
287: }
288:
289: static int mfill_get_pmd(struct mfill_state *state)
290: {
291: struct mm_struct *dst_mm = state->ctx->mm;
292: pmd_t *dst_pmd;
293: pmd_t dst_pmdval;
294:
]
|
| 38/2 |
2026/05/30 11:32 |
llm |
expert |
0m
Model:
Tokens:
input: 34272
output: 35
thoughts: 491
Thoughts: **Analyzing a Potential Resource Leak in `mfill_get_vma`**
Okay, so I'm looking at this `mfill_get_vma` function, and something's not quite right. Specifically, I'm focusing on the `out_unlock` path. It seems like the code structure has a subtle flaw. Let's break it down.
First, I see the `out_unlock` label calls `mfill_put_vma(state)`. That seems reasonable. Looking into `mfill_put_vma`, I see it contains the following:
```c
static void mfill_put_vma(struct mfill_state *state)
{
if (!state->vma)
return;
up_read(&state->ctx->map_changing_lock);
uffd_mfill_unlock(state->vma);
state->vma = NULL;
}
```
My brain is screaming "WAIT!" Because, if `mfill_get_vma` fails before setting `state->vma`, then `state->vma` will be NULL by the time `mfill_put_vma` is called, and `mfill_put_vma` will just return. The problem is that `dst_vma` and the read lock `ctx->map_changing_lock` are acquired *before* `state->vma` is actually set. I can see that clearly:
```c
dst_vma = uffd_mfill_lock(ctx->mm, state->dst_start, state->len);
if (IS_ERR(dst_vma))
return PTR_ERR(dst_vma);
down_read(&ctx->map_changing_lock);
err = -EAGAIN;
if (atomic_read(&ctx->mmap_changing))
goto out_unlock;
```
If execution flows to `out_unlock` due to a failure, then `state->vma` is definitely going to be NULL. Which means `mfill_put_vma(state)` will just skip the `uffd_mfill_unlock(state->vma)` and `up_read(&state->ctx->map_changing_lock)` calls, and it won't free the resources (e.g. `dst_vma` and `map_changing_lock` ) that were acquired *before* this check.
In other words, the `uffd_mfill_lock` and the `down_read` calls will leak their resources when `mmap_changing` is true.
So, I think I've found a potential resource leak in this logic. It's time to check `mfill_atomic` now and see if I missed anything obvious.
|
| 39/2 |
2026/05/30 11:33 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:872 LineCount:50]
Results: map[Contents: 872: static __always_inline ssize_t mfill_atomic(struct userfaultfd_ctx *ctx,
873: unsigned long dst_start,
874: unsigned long src_start,
875: unsigned long len,
876: uffd_flags_t flags)
877: {
878: struct mfill_state state = (struct mfill_state){
879: .ctx = ctx,
880: .dst_start = dst_start,
881: .src_start = src_start,
882: .flags = flags,
883:
884: .src_addr = src_start,
885: .dst_addr = dst_start,
886: };
887: long copied = 0;
888: ssize_t err;
889:
890: /*
891: * Sanitize the command parameters:
892: */
893: VM_WARN_ON_ONCE(dst_start & ~PAGE_MASK);
894: VM_WARN_ON_ONCE(len & ~PAGE_MASK);
895:
896: /* Does the address range wrap, or is the span zero-sized? */
897: VM_WARN_ON_ONCE(src_start + len <= src_start);
898: VM_WARN_ON_ONCE(dst_start + len <= dst_start);
899:
900: err = mfill_get_vma(&state);
901: if (err)
902: goto out;
903:
904: /*
905: * If this is a HUGETLB vma, pass off to appropriate routine
906: */
907: if (is_vm_hugetlb_page(state.vma))
908: return mfill_atomic_hugetlb(ctx, state.vma, dst_start,
909: src_start, len, flags);
910:
911: while (state.src_addr < src_start + len) {
912: VM_WARN_ON_ONCE(state.dst_addr >= dst_start + len);
913:
914: err = mfill_get_pmd(&state);
915: if (err)
916: break;
917:
918: /*
919: * For shmem mappings, khugepaged is allowed to remove page
920: * tables under us; pte_offset_map_lock() will deal with that.
921: */
]
|
| 40/2 |
2026/05/30 11:33 |
llm |
expert |
0m
Model:
Tokens:
input: 36171
output: 23
thoughts: 720
Thoughts: **Debugging a Userfaultfd Lock Contention**
Okay, so I'm looking at this code and a potential deadlock situation. Let's see... Initially, `state.vma` is set to NULL, as expected. Then, `mfill_get_vma` is called. If this function fails, it jumps to `out_unlock`. Inside `out_unlock`, `mfill_put_vma` is called, but because `state->vma` is NULL, it returns immediately. This means `dst_vma` and `ctx->map_changing_lock` are *not* unlocked. But hang on, `dst_vma` is locked using `uffd_mfill_lock`, which uses `uffd_lock_vma`.
`uffd_lock_vma` returns with the per-VMA lock held. Ah, I see, that per-VMA lock is a reader lock. But wait, `uffd_lock_vma` *doesn't* hold `mmap_read_lock`. However, if `uffd_lock_vma` fails to get the per-VMA lock, it *does* try to get `mmap_read_lock`, acquire `vma`, and then release `mmap_read_lock`. So, `uffd_lock_vma` always returns with `mmap_read_lock` unlocked, only with the per-VMA lock held!
Now, if it's only holding the per-VMA lock, why is `mmap_read_lock` being blocked in the hung task? Let's trace it back. The hung task is blocked in `mmap_read_lock` which is called within `uffd_lock_vma`, which is in turn, called by `mfill_get_vma`. So, the task *is* trying to acquire `mmap_read_lock`. This means that someone else must be holding `mmap_write_lock`! But, who? Could the task that is holding `mmap_write_lock` be blocked on `map_changing_lock`? I need to check where `map_changing_lock` is acquired for write to confirm this. That's the key.
|
| 41/2 |
2026/05/30 11:33 |
tool |
grepper |
0m
Args: map[Expression:down_write.*map_changing_lock]
Results: map[Output:fs/userfaultfd.c=635=int dup_userfaultfd(struct vm_area_struct *vma, struct list_head *fcs)
--
fs/userfaultfd.c-675- userfaultfd_ctx_get(octx);
fs/userfaultfd.c:676: down_write(&octx->map_changing_lock);
fs/userfaultfd.c-677- atomic_inc(&octx->mmap_changing);
--
fs/userfaultfd.c=740=void mremap_userfaultfd_prep(struct vm_area_struct *vma,
--
fs/userfaultfd.c-752- userfaultfd_ctx_get(ctx);
fs/userfaultfd.c:753: down_write(&ctx->map_changing_lock);
fs/userfaultfd.c-754- atomic_inc(&ctx->mmap_changing);
--
fs/userfaultfd.c=792=bool userfaultfd_remove(struct vm_area_struct *vma,
--
fs/userfaultfd.c-803- userfaultfd_ctx_get(ctx);
fs/userfaultfd.c:804: down_write(&ctx->map_changing_lock);
fs/userfaultfd.c-805- atomic_inc(&ctx->mmap_changing);
--
fs/userfaultfd.c=833=int userfaultfd_unmap_prep(struct vm_area_struct *vma, unsigned long start,
--
fs/userfaultfd.c-847- userfaultfd_ctx_get(ctx);
fs/userfaultfd.c:848: down_write(&ctx->map_changing_lock);
fs/userfaultfd.c-849- atomic_inc(&ctx->mmap_changing);
]
|
| 42/2 |
2026/05/30 11:33 |
llm |
expert |
0m
Model:
Tokens:
input: 37870
output: 35
thoughts: 1923
Thoughts: **Deadlock Analysis in Userfaultfd - A Deep Dive**
Okay, so I've been tracing through the userfaultfd code, and I think I've found a critical deadlock scenario. It hinges on the interaction between `mremap`, `munmap`, and `UFFDIO_COPY` (or similar operations involving userfaultfd). Here's the sequence of events and the problem as I see it:
The key is in the lock acquisition order and potential leaks. Functions like `mremap_userfaultfd_prep`, `userfaultfd_remove`, and `userfaultfd_unmap_prep` (used by `mremap` and `munmap`) all take `down_write(&ctx->map_changing_lock)`. They are also called during the main mmap/munmap path which already holds the `mmap_write_lock`.
Here's the scenario:
**Task A** (e.g., `munmap` or `mremap`):
1. Acquires `mmap_write_lock` (this is standard for memory map changes).
2. Calls functions like `userfaultfd_unmap_prep` which, in turn, calls `down_write(&ctx->map_changing_lock)`.
3. Acquires `down_write(&ctx->map_changing_lock)`
**Task B** (e.g., a `UFFDIO_COPY` or other userfaultfd operation):
1. Calls `mfill_get_vma`.
2. Calls `uffd_mfill_lock` -> `uffd_lock_vma`. This tries to acquire the `mmap_read_lock`.
3. If Task A is holding the write lock, Task B correctly blocks on the `mmap_read_lock`. So far, so good.
4. But... inside `mfill_get_vma`, *after* the initial `uffd_mfill_lock`, the function calls `down_read(&ctx->map_changing_lock)`.
5. Now, the problem arises when `atomic_read(&ctx->mmap_changing)` is true *within* `mfill_get_vma`. This indicates that a memory map change is happening.
6. When `atomic_read(&ctx->mmap_changing)` *is* true, `mfill_get_vma` jumps to `out_unlock`. This is where the leak happens.
7. `out_unlock` calls `mfill_put_vma(state)`, but crucially, *inside `mfill_get_vma`, `state->vma` is NULL.* This value is not set until after the check.
8. Since `state->vma` is NULL, the call to `mfill_put_vma(state)` essentially does nothing (bypasses the `up_read` call).
9. This means Task B *leaks* `down_read(&ctx->map_changing_lock)`! Also, the VMA read lock is leaked as well.
10. The leaked `down_read` causes `munmap` to block on `down_write`. Since Task A holds `mmap_write_lock`, everything comes to a standstill.
11. Task B returns `-EAGAIN` but the lock is already leaked and will not be released by either path.
Essentially, Task B acquires the read lock, detects a change happening in memory layout from Task A, and then *fails* to release the lock, creating a potential deadlock.
Let's verify this, and the issue is:
- `mfill_get_vma` leaks `down_read(&ctx->map_changing_lock)` under the condition that `atomic_read(&ctx->mmap_changing)` returns true and `state->vma` is `NULL`.
A quick check on the `userfaultfd_unmap_prep` shows it acquires the `down_write` _before_ incrementing `mmap_changing`, so it is not a part of the deadlock as I initially thought.
So, in the end, it means: `down_read(&ctx->map_changing_lock)` is leaked by Task B and will block Task A. Any subsequent task trying to acquire `mmap_read_lock` will also be blocked.
The root cause: `mfill_get_vma` doesn't properly handle the `mmap_changing` state when an error occurs.
|
| 43/2 |
2026/05/30 11:33 |
tool |
read-file |
0m
Args: map[File:fs/userfaultfd.c FirstLine:833 LineCount:50]
Results: map[Contents: 833: int userfaultfd_unmap_prep(struct vm_area_struct *vma, unsigned long start,
834: unsigned long end, struct list_head *unmaps)
835: {
836: struct userfaultfd_unmap_ctx *unmap_ctx;
837: struct userfaultfd_ctx *ctx = vma->vm_userfaultfd_ctx.ctx;
838:
839: if (!ctx || !(ctx->features & UFFD_FEATURE_EVENT_UNMAP) ||
840: has_unmap_ctx(ctx, unmaps, start, end))
841: return 0;
842:
843: unmap_ctx = kzalloc_obj(*unmap_ctx);
844: if (!unmap_ctx)
845: return -ENOMEM;
846:
847: userfaultfd_ctx_get(ctx);
848: down_write(&ctx->map_changing_lock);
849: atomic_inc(&ctx->mmap_changing);
850: up_write(&ctx->map_changing_lock);
851: unmap_ctx->ctx = ctx;
852: unmap_ctx->start = start;
853: unmap_ctx->end = end;
854: list_add_tail(&unmap_ctx->list, unmaps);
855:
856: return 0;
857: }
858:
859: void userfaultfd_unmap_complete(struct mm_struct *mm, struct list_head *uf)
860: {
861: struct userfaultfd_unmap_ctx *ctx, *n;
862: struct userfaultfd_wait_queue ewq;
863:
864: list_for_each_entry_safe(ctx, n, uf, list) {
865: msg_init(&ewq.msg);
866:
867: ewq.msg.event = UFFD_EVENT_UNMAP;
868: ewq.msg.arg.remove.start = ctx->start;
869: ewq.msg.arg.remove.end = ctx->end;
870:
871: userfaultfd_event_wait_completion(ctx->ctx, &ewq);
872:
873: list_del(&ctx->list);
874: kfree(ctx);
875: }
876: }
877:
878: static int userfaultfd_release(struct inode *inode, struct file *file)
879: {
880: struct userfaultfd_ctx *ctx = file->private_data;
881: struct mm_struct *mm = ctx->mm;
882: /* len == 0 means wake all */
]
|
| 44/2 |
2026/05/30 11:33 |
llm |
expert |
0m
Model:
Tokens:
input: 41626
output: 35
thoughts: 73
Thoughts: **Unraveling the Userfaultfd Unmap Process**
Okay, let's see... `userfaultfd_unmap_prep` increments `mmap_changing`. Got it. Then, `userfaultfd_unmap_complete` calls `userfaultfd_event_wait_completion`. Hmm, a logical sequence, but something's nagging at me. Where, exactly, is `mmap_changing` decremented? It's crucial for correct synchronization. We need to be absolutely certain this isn't leading to a race condition or incorrect memory management. Right, time to dive into `userfaultfd_event_wait_completion` and figure this out. I need to understand this decrement to be confident this is well-behaved.
|
| 45/2 |
2026/05/30 11:33 |
tool |
read-file |
0m
Args: map[File:fs/userfaultfd.c FirstLine:500 LineCount:50]
Results: map[Contents: 500: * through poll/read().
501: */
502: __add_wait_queue(&ctx->fault_pending_wqh, &uwq.wq);
503: /*
504: * The smp_mb() after __set_current_state prevents the reads
505: * following the spin_unlock to happen before the list_add in
506: * __add_wait_queue.
507: */
508: set_current_state(blocking_state);
509: spin_unlock_irq(&ctx->fault_pending_wqh.lock);
510:
511: if (is_vm_hugetlb_page(vma)) {
512: must_wait = userfaultfd_huge_must_wait(ctx, vmf, reason);
513: hugetlb_vma_unlock_read(vma);
514: } else {
515: must_wait = userfaultfd_must_wait(ctx, vmf, reason);
516: }
517:
518: release_fault_lock(vmf);
519:
520: if (likely(must_wait && !READ_ONCE(ctx->released))) {
521: wake_up_poll(&ctx->fd_wqh, EPOLLIN);
522: schedule();
523: }
524:
525: __set_current_state(TASK_RUNNING);
526:
527: /*
528: * Here we race with the list_del; list_add in
529: * userfaultfd_ctx_read(), however because we don't ever run
530: * list_del_init() to refile across the two lists, the prev
531: * and next pointers will never point to self. list_add also
532: * would never let any of the two pointers to point to
533: * self. So list_empty_careful won't risk to see both pointers
534: * pointing to self at any time during the list refile. The
535: * only case where list_del_init() is called is the full
536: * removal in the wake function and there we don't re-list_add
537: * and it's fine not to block on the spinlock. The uwq on this
538: * kernel stack can be released after the list_del_init.
539: */
540: if (!list_empty_careful(&uwq.wq.entry)) {
541: spin_lock_irq(&ctx->fault_pending_wqh.lock);
542: /*
543: * No need of list_del_init(), the uwq on the stack
544: * will be freed shortly anyway.
545: */
546: list_del(&uwq.wq.entry);
547: spin_unlock_irq(&ctx->fault_pending_wqh.lock);
548: }
549:
]
|
| 46/2 |
2026/05/30 11:33 |
llm |
expert |
0m
Model:
Tokens:
input: 42750
output: 22
thoughts: 17
Thoughts: Here's my thought process, summarized as you requested:
**Tracking Down a Decrement**
Okay, so I'm trying to figure out something related to `mmap_changing`. Right now, I need to figure out *where* this variable is being decremented. It's a key piece of the puzzle I'm working on, and knowing the decrement location is crucial to understanding its overall behavior and impact. I need to trace this down quickly. Let's see how I can go about doing that. Time to get to work.
|
| 47/2 |
2026/05/30 11:33 |
tool |
grepper |
0m
Args: map[Expression:atomic_dec.*mmap_changing]
Results: map[Output:fs/userfaultfd.c=560=static void userfaultfd_event_wait_completion(struct userfaultfd_ctx *ctx,
--
fs/userfaultfd.c-621-out:
fs/userfaultfd.c:622: atomic_dec(&ctx->mmap_changing);
fs/userfaultfd.c-623- VM_WARN_ON_ONCE(atomic_read(&ctx->mmap_changing) < 0);
--
fs/userfaultfd.c=712=void dup_userfaultfd_fail(struct list_head *fcs)
--
fs/userfaultfd.c-729-
fs/userfaultfd.c:730: atomic_dec(&octx->mmap_changing);
fs/userfaultfd.c-731- VM_WARN_ON_ONCE(atomic_read(&octx->mmap_changing) < 0);
]
|
| 48/2 |
2026/05/30 11:33 |
llm |
expert |
0m
Model:
Tokens:
input: 43142
output: 35
thoughts: 41
Thoughts: Okay, here's my interpretation of those thoughts, framed as an expert's internal monologue:
**Investigating `userfaultfd_event_wait_completion`**
Right, so I'm looking at `userfaultfd_event_wait_completion`... interesting. It appears this function is the key mechanism for handling completion of events related to userfaultfd. I see it decrements `mmap_changing`. That's important. This suggests a direct link between this function and the overall state of memory mapping modifications. This likely means that a 'changing' flag is tracked, and this function marks that the change is now *complete*, allowing the system to proceed.
Furthermore, I note that it acquires `down_write(&ctx->map_changing_lock)`. This suggests that there's a writer lock protecting some shared context, `ctx`, that is related to the memory map changes. The fact it's a "down_write" implies a shared/exclusive lock, so while one thing is in process, others may have to wait their turn. That lock almost certainly ties into the overall memory management strategy, likely ensuring that concurrent access to memory regions undergoing changes is properly synchronized. The acquisition of this lock, and its subsequent release, is essential for maintaining memory integrity. This whole interaction is critical to understanding how the system handles the userfaultfd events and keeps mmap changes coherent and correct.
|
| 49/2 |
2026/05/30 11:33 |
tool |
read-file |
0m
Args: map[File:fs/userfaultfd.c FirstLine:600 LineCount:50]
Results: map[Contents: 600: }
601:
602: spin_unlock_irq(&ctx->event_wqh.lock);
603:
604: wake_up_poll(&ctx->fd_wqh, EPOLLIN);
605: schedule();
606:
607: spin_lock_irq(&ctx->event_wqh.lock);
608: }
609: __set_current_state(TASK_RUNNING);
610: spin_unlock_irq(&ctx->event_wqh.lock);
611:
612: if (release_new_ctx) {
613: userfaultfd_release_new(release_new_ctx);
614: userfaultfd_ctx_put(release_new_ctx);
615: }
616:
617: /*
618: * ctx may go away after this if the userfault pseudo fd is
619: * already released.
620: */
621: out:
622: atomic_dec(&ctx->mmap_changing);
623: VM_WARN_ON_ONCE(atomic_read(&ctx->mmap_changing) < 0);
624: userfaultfd_ctx_put(ctx);
625: }
626:
627: static void userfaultfd_event_complete(struct userfaultfd_ctx *ctx,
628: struct userfaultfd_wait_queue *ewq)
629: {
630: ewq->msg.event = 0;
631: wake_up_locked(&ctx->event_wqh);
632: __remove_wait_queue(&ctx->event_wqh, &ewq->wq);
633: }
634:
635: int dup_userfaultfd(struct vm_area_struct *vma, struct list_head *fcs)
636: {
637: struct userfaultfd_ctx *ctx = NULL, *octx;
638: struct userfaultfd_fork_ctx *fctx;
639:
640: octx = vma->vm_userfaultfd_ctx.ctx;
641: if (!octx)
642: return 0;
643:
644: if (!(octx->features & UFFD_FEATURE_EVENT_FORK)) {
645: userfaultfd_reset_ctx(vma);
646: return 0;
647: }
648:
649: list_for_each_entry(fctx, fcs, list)
]
|
| 50/2 |
2026/05/30 11:33 |
llm |
expert |
0m
Model:
Tokens:
input: 44267
output: 20
thoughts: 1858
Thoughts: **A Deep Dive into a Potential Kernel Deadlock**
Okay, so I've been tracing the execution flow of `userfaultfd_event_wait_completion` and I think I've spotted a significant flaw. My initial understanding was that this function would acquire a write lock, but it doesn't. It just decrements `mmap_changing`. Here's the sequence I've worked out:
1. `userfaultfd_unmap_prep` acquires `down_write`, increments `mmap_changing`, and releases `down_write`.
2. `userfaultfd_unmap_complete` calls `userfaultfd_event_wait_completion`.
3. `userfaultfd_event_wait_completion` waits for userspace to read the event.
4. After userspace reads the event, it decrements `mmap_changing`.
The problem is, if `userfaultfd_unmap_prep` *releases* the write lock before `userfaultfd_event_wait_completion` completes, that means the write lock is *not* held during the wait! The only thing is incrementing that `mmap_changing` counter. This is important to note as `mremap_userfaultfd_complete` also calls `userfaultfd_event_wait_completion`.
Now, if `mmap_changing` is just a counter, then when `mmap_changing` is greater than 0, `mfill_get_vma` returns `-EAGAIN`. Let's step through this in detail. Essentially, the code checks:
```c
down_read(&ctx->map_changing_lock);
err = -EAGAIN;
if (atomic_read(&ctx->mmap_changing))
goto out_unlock;
```
If `mmap_changing` is greater than 0, it goes to `out_unlock`. Then:
```c
out_unlock:
mfill_put_vma(state);
return err;
```
Here's the problem: `mfill_put_vma` does NOTHING because `state->vma` is NULL! This means that `down_read(&ctx->map_changing_lock)` is LEAKED, and `uffd_mfill_lock` is LEAKED! This causes a returned `-EAGAIN`.
Now, the lock on the VMA itself is leaked, *and* the read lock is leaked. The userspace can just retry, but the read lock is still leaked! The very next time someone tries to acquire the write lock, it will BLOCK. Forever.
So, who tries to acquire `down_write(&ctx->map_changing_lock)`? Functions like `userfaultfd_unmap_prep`, `mremap_userfaultfd_prep`, `userfaultfd_remove`, `dup_userfaultfd` and they are called from `munmap`, `mremap`, `exit_mmap`, `fork`. These, importantly, are all called with `mmap_write_lock` already HELD.
So, consider Task A (e.g., `munmap`) that holds the `mmap_write_lock`. It calls `userfaultfd_unmap_prep`, and then it tries to acquire `down_write(&ctx->map_changing_lock)`. But it *blocks forever* because Task B leaked the read lock! Then, Task A is blocked holding `mmap_write_lock`. Any other task (e.g., Task C) that then tries to acquire `mmap_read_lock` *also* blocks forever! This perfectly matches the hung task reports I've been seeing!
Let's double check `mfill_put_vma`. It should release resources, but if `state->vma` is NULL, it returns immediately!
```c
static void mfill_put_vma(struct mfill_state *state)
{
if (!state->vma)
return;
up_read(&state->ctx->map_changing_lock);
uffd_mfill_unlock(state->vma);
state->vma = NULL;
}
```
Indeed, it returns immediately! The VMA is only ever set if `mfill_get_vma` succeeds and sets it in the `out:` label, thus if `mfill_get_vma` fails before setting it at `out:` label, then `state->vma` is set to NULL. And in the subsequent calls to `mfill_get_vma` after `mfill_copy_folio_retry`, `state->vma` is *also*
|
| 51/2 |
2026/05/30 11:34 |
tool |
grepper |
0m
Args: map[Expression:unprivileged_userfaultfd]
Results: map[Output:Documentation/admin-guide/mm/userfaultfd.rst=61=userfaultfd(2) syscall. Access to this is controlled in several ways:
--
Documentation/admin-guide/mm/userfaultfd.rst-68- process needs the CAP_SYS_PTRACE capability, or the system must have
Documentation/admin-guide/mm/userfaultfd.rst:69: vm.unprivileged_userfaultfd set to 1. By default, vm.unprivileged_userfaultfd
Documentation/admin-guide/mm/userfaultfd.rst-70- is set to 0.
--
Documentation/admin-guide/mm/userfaultfd.rst=80=to /dev/userfaultfd can always create userfaultfds that trap kernel page faults;
Documentation/admin-guide/mm/userfaultfd.rst:81:vm.unprivileged_userfaultfd is not considered.
Documentation/admin-guide/mm/userfaultfd.rst-82-
--
Documentation/admin-guide/sysctl/vm.rst=25=Currently, these files are in /proc/sys/vm:
--
Documentation/admin-guide/sysctl/vm.rst-74-- swappiness
Documentation/admin-guide/sysctl/vm.rst:75:- unprivileged_userfaultfd
Documentation/admin-guide/sysctl/vm.rst-76-- user_reserve_kbytes
--
Documentation/admin-guide/sysctl/vm.rst=1007=file-backed pages is less than the high watermark in a zone.
--
Documentation/admin-guide/sysctl/vm.rst-1009-
Documentation/admin-guide/sysctl/vm.rst:1010:unprivileged_userfaultfd
Documentation/admin-guide/sysctl/vm.rst-1011-========================
--
fs/userfaultfd.c-35-
fs/userfaultfd.c:36:static int sysctl_unprivileged_userfaultfd __read_mostly;
fs/userfaultfd.c-37-
--
fs/userfaultfd.c=39=static const struct ctl_table vm_userfaultfd_table[] = {
fs/userfaultfd.c-40- {
fs/userfaultfd.c:41: .procname = "unprivileged_userfaultfd",
fs/userfaultfd.c:42: .data = &sysctl_unprivileged_userfaultfd,
fs/userfaultfd.c:43: .maxlen = sizeof(sysctl_unprivileged_userfaultfd),
fs/userfaultfd.c-44- .mode = 0644,
--
fs/userfaultfd.c=2169=static inline bool userfaultfd_syscall_allowed(int flags)
--
fs/userfaultfd.c-2182- /* Otherwise, access to kernel fault handling is sysctl controlled. */
fs/userfaultfd.c:2183: return sysctl_unprivileged_userfaultfd;
fs/userfaultfd.c-2184-}
]
|
| 52/2 |
2026/05/30 11:34 |
llm |
expert |
0m
Model:
Tokens:
input: 47917
output: 20
thoughts: 131
Thoughts: **Unraveling Userfaultfd Security**
Okay, hold on a second. I need to clarify something about `vm.unprivileged_userfaultfd`. It defaults to zero, right? But wait, I seem to remember that `userfaultfd` isn't *solely* accessed through the `vm` setting. There's also `/dev/userfaultfd`. I should check the documentation to make sure I'm not missing anything. Let's see... Ah, yes, `Documentation/admin-guide/mm/userfaultfd.rst` states, "to `/dev/userfaultfd` can always create userfaultfds that trap kernel page faults; `vm.unprivileged_userfaultfd` is not considered."
So, that’s key. It overrides the `vm` setting. The real question is whether an unprivileged user can access `/dev/userfaultfd` or not! To nail this down, I'll need to go directly to the source code. Let's dig into `fs/userfaultfd.c` and confirm exactly how permissions and access are managed for `/dev/userfaultfd`. This is critical for understanding the security implications.
|
| 53/2 |
2026/05/30 11:34 |
tool |
grepper |
0m
Args: map[Expression:/dev/userfaultfd]
Results: map[Output:Documentation/admin-guide/mm/userfaultfd.rst=72=The second way, added to the kernel more recently, is by opening
Documentation/admin-guide/mm/userfaultfd.rst:73:/dev/userfaultfd and issuing a USERFAULTFD_IOC_NEW ioctl to it. This method
Documentation/admin-guide/mm/userfaultfd.rst-74-yields equivalent userfaultfds to the userfaultfd(2) syscall.
Documentation/admin-guide/mm/userfaultfd.rst-75-
Documentation/admin-guide/mm/userfaultfd.rst:76:Unlike userfaultfd(2), access to /dev/userfaultfd is controlled via normal
Documentation/admin-guide/mm/userfaultfd.rst-77-filesystem permissions (user/group/mode), which gives fine grained access to
--
Documentation/admin-guide/mm/userfaultfd.rst=79=the same time (as e.g. granting CAP_SYS_PTRACE would do). Users who have access
Documentation/admin-guide/mm/userfaultfd.rst:80:to /dev/userfaultfd can always create userfaultfds that trap kernel page faults;
Documentation/admin-guide/mm/userfaultfd.rst-81-vm.unprivileged_userfaultfd is not considered.
--
Documentation/admin-guide/sysctl/vm.rst=1025=Another way to control permissions for userfaultfd is to use
Documentation/admin-guide/sysctl/vm.rst:1026:/dev/userfaultfd instead of userfaultfd(2). See
Documentation/admin-guide/sysctl/vm.rst-1027-Documentation/admin-guide/mm/userfaultfd.rst.
--
include/uapi/linux/userfaultfd.h-14-
include/uapi/linux/userfaultfd.h:15:/* ioctls for /dev/userfaultfd */
include/uapi/linux/userfaultfd.h-16-#define USERFAULTFD_IOC 0xAA
--
tools/include/uapi/linux/userfaultfd.h-14-
tools/include/uapi/linux/userfaultfd.h:15:/* ioctls for /dev/userfaultfd */
tools/include/uapi/linux/userfaultfd.h-16-#define USERFAULTFD_IOC 0xAA
--
tools/testing/selftests/mm/uffd-common.c=688=int uffd_open_dev(unsigned int flags)
--
tools/testing/selftests/mm/uffd-common.c-691-
tools/testing/selftests/mm/uffd-common.c:692: fd = open("/dev/userfaultfd", O_RDWR | O_CLOEXEC);
tools/testing/selftests/mm/uffd-common.c-693- if (fd < 0)
--
tools/testing/selftests/mm/uffd-unit-tests.c=129=static int test_uffd_api(bool use_dev)
--
tools/testing/selftests/mm/uffd-unit-tests.c-134- uffd_test_start("UFFDIO_API (with %s)",
tools/testing/selftests/mm/uffd-unit-tests.c:135: use_dev ? "/dev/userfaultfd" : "syscall");
tools/testing/selftests/mm/uffd-unit-tests.c-136-
]
|
| 54/2 |
2026/05/30 11:34 |
llm |
expert |
0m
Model:
Tokens:
input: 49170
output: 36
thoughts: 68
Thoughts: Here's my summary, as the expert:
**Delving into `/dev/userfaultfd` and its Permissions**
Okay, so I'm thinking about `/dev/userfaultfd` and how the kernel handles access. It's pretty standard that its permissions are managed via the filesystem – typically root access or maybe a specific group, nothing too surprising there. But then a thought pops up: wait a minute... aren't there situations where an unprivileged user can leverage `userfaultfd(2)`?
The key here seems to be whether kernel-initiated page faults are being requested. If they *aren't*, then maybe the usual restrictions don't apply. I should probably dive into the code and see how the kernel determines if an unprivileged user's `userfaultfd(2)` use is allowed. Time to check out the `userfaultfd_syscall_allowed` function. That seems like the right place to begin.
|
| 55/2 |
2026/05/30 11:34 |
tool |
read-file |
0m
Args: map[File:fs/userfaultfd.c FirstLine:2169 LineCount:50]
Results: map[Contents:2169: static inline bool userfaultfd_syscall_allowed(int flags)
2170: {
2171: /* Userspace-only page faults are always allowed */
2172: if (flags & UFFD_USER_MODE_ONLY)
2173: return true;
2174:
2175: /*
2176: * The user is requesting a userfaultfd which can handle kernel faults.
2177: * Privileged users are always allowed to do this.
2178: */
2179: if (capable(CAP_SYS_PTRACE))
2180: return true;
2181:
2182: /* Otherwise, access to kernel fault handling is sysctl controlled. */
2183: return sysctl_unprivileged_userfaultfd;
2184: }
2185:
2186: SYSCALL_DEFINE1(userfaultfd, int, flags)
2187: {
2188: if (!userfaultfd_syscall_allowed(flags))
2189: return -EPERM;
2190:
2191: return new_userfaultfd(flags);
2192: }
2193:
2194: static long userfaultfd_dev_ioctl(struct file *file, unsigned int cmd, unsigned long flags)
2195: {
2196: if (cmd != USERFAULTFD_IOC_NEW)
2197: return -EINVAL;
2198:
2199: return new_userfaultfd(flags);
2200: }
2201:
2202: static const struct file_operations userfaultfd_dev_fops = {
2203: .unlocked_ioctl = userfaultfd_dev_ioctl,
2204: .compat_ioctl = userfaultfd_dev_ioctl,
2205: .owner = THIS_MODULE,
2206: .llseek = noop_llseek,
2207: };
2208:
2209: static struct miscdevice userfaultfd_misc = {
2210: .minor = MISC_DYNAMIC_MINOR,
2211: .name = "userfaultfd",
2212: .fops = &userfaultfd_dev_fops
2213: };
2214:
2215: static int __init userfaultfd_init(void)
2216: {
2217: int ret;
2218:
]
|
| 56/2 |
2026/05/30 11:34 |
llm |
expert |
0m
Model:
Tokens:
input: 50162
output: 22
thoughts: 539
Thoughts: **My Analysis of the Userfaultfd (UFFD) Vulnerability**
Okay, so `UFFD_USER_MODE_ONLY` is always permitted for unprivileged users. And, it's *precisely* the key to exploiting this bug. The issue lies within `UFFDIO_COPY` (and potentially `UFFDIO_ZEROPAGE`, etc.) – those ioctls on the userfaultfd file descriptor.
The exploit unfolds like this:
1. I create a userfaultfd with `UFFD_USER_MODE_ONLY`.
2. I register a Virtual Memory Area (VMA) with this userfaultfd.
3. In a separate thread, `mremap` or `munmap` is called on that same VMA, which sets `mmap_changing` to 1.
4. Then, in the original thread, I call `UFFDIO_COPY` on the VMA.
5. `UFFDIO_COPY` calls `mfill_atomic`, which in turn calls `mfill_get_vma`.
6. `mfill_get_vma` detects `mmap_changing == 1`. It goes to `out_unlock`, leaking the `down_read(&ctx->map_changing_lock)` and the per-VMA lock!
7. The `mremap`/`munmap` thread, blocked on `down_write(&ctx->map_changing_lock)`, is now going to block FOREVER!
8. Since the `mremap`/`munmap` thread is holding `mmap_write_lock`, any other thread that attempts to acquire `mmap_read_lock` on the same `mm` will block FOREVER!
Wait, this is a local Denial of Service (DoS)!
But, is it limited to threads within the *same* process? If it were, it's not a full system-wide DoS.
Ah, `mmap_write_lock` is per-memory map (per-mm). So, it's confined to the process itself.
But...what if the process shares its `mm` with other processes, like via `clone(CLONE_VM)`? No, it would still be just the same process group.
What if this affects a system service? An unprivileged user shouldn't be able to force this on a system service.
Wait, what if an unprivileged user triggers this, and then the Out-Of-Memory (OOM) killer tries to kill the process?
The OOM killer would need to get `mmap_read_lock` to reap the process!
I need to check if the OOM killer does indeed acquire `mmap_read_lock`.
|
| 57/2 |
2026/05/30 11:34 |
tool |
grepper |
0m
Error:
Args: map[Expression:mmap_read_lock.*oom]
Results:
|
| 58/2 |
2026/05/30 11:34 |
llm |
expert |
0m
Model:
Tokens:
input: 51328
output: 22
thoughts: 14
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:
**Diving into `oom_reap_task_mm`**
Alright, time to dig in. Let's start by looking at `oom_reap_task_mm`. I need to refresh my understanding of how this function fits into the overall OOM (Out of Memory) handling process within the kernel. Specifically, I'm thinking about its role in reclaiming resources, likely memory, from a specific task's memory management structure (MM). My concern is how it interacts with the rest of the OOM killer logic, and the implications for workload performance or stability. I'm wondering if there are any specific scenarios, perhaps involving highly contended memory or unusual memory allocation patterns, where this function might become a bottleneck or introduce unexpected behavior. I also want to make sure I understand any potential race conditions or synchronization issues involved, particularly in a multi-threaded or multi-process environment. Essentially, I'm aiming to pinpoint potential areas for optimization, or any subtle bugs, that could be hiding within this core part of the kernel's memory management.
|
| 59/2 |
2026/05/30 11:34 |
tool |
grepper |
0m
Args: map[Expression:oom_reap_task_mm]
Results: map[Output:mm/oom_kill.c=511=static DEFINE_SPINLOCK(oom_reaper_lock);
mm/oom_kill.c-512-
mm/oom_kill.c:513:static bool __oom_reap_task_mm(struct mm_struct *mm)
mm/oom_kill.c-514-{
--
mm/oom_kill.c-561- */
mm/oom_kill.c:562:static bool oom_reap_task_mm(struct task_struct *tsk, struct mm_struct *mm)
mm/oom_kill.c-563-{
--
mm/oom_kill.c-584- /* failed to reap part of the address space. Try again later */
mm/oom_kill.c:585: ret = __oom_reap_task_mm(mm);
mm/oom_kill.c-586- if (!ret)
--
mm/oom_kill.c=603=static void oom_reap_task(struct task_struct *tsk)
--
mm/oom_kill.c-608- /* Retry the mmap_read_trylock(mm) a few times */
mm/oom_kill.c:609: while (attempts++ < MAX_OOM_REAP_RETRIES && !oom_reap_task_mm(tsk, mm))
mm/oom_kill.c-610- schedule_timeout_idle(HZ/10);
--
mm/oom_kill.c=1195=SYSCALL_DEFINE2(process_mrelease, int, pidfd, unsigned int, flags)
--
mm/oom_kill.c-1244- */
mm/oom_kill.c:1245: if (!mm_flags_test(MMF_OOM_SKIP, mm) && !__oom_reap_task_mm(mm))
mm/oom_kill.c-1246- ret = -EAGAIN;
]
|
| 60/2 |
2026/05/30 11:34 |
llm |
expert |
0m
Model:
Tokens:
input: 52107
output: 25
thoughts: 130
Thoughts: **Analyzing the `oom_reap_task_mm` Process**
Okay, so `oom_reap_task_mm` is using `mmap_read_trylock(mm)`. I see, it's attempting to read the memory map safely. If the lock acquisition fails initially, it retries a few times. That makes sense; we want to be robust in the face of contention. But then, if it *still* fails, it just gives up. That's where things get interesting. It doesn't reap the memory, but it *does* kill the task. Hmm, let me think that through... the task is killed, which means it will inevitably exit.
Now, during exit, `exit_mmap` is called. Ah, hold on a second. `exit_mmap`... wait, `exit_mmap` acquires `mmap_write_lock`? That seems potentially problematic in this scenario. If the initial trylock failed, and the task is killed, could we be introducing a lock inversion situation? This seems like a potential issue, given that we are trying to clear up the memory maps of a killed task. I need to dive deeper into `exit_mmap` to fully understand what's happening. I need to make sure this doesn't create a deadlock scenario. Let me go check that out.
|
| 61/2 |
2026/05/30 11:34 |
tool |
grepper |
0m
Args: map[Expression:mmap_write_lock.*exit_mmap]
Results: map[Output:mm/oom_kill.c=562=static bool oom_reap_task_mm(struct task_struct *tsk, struct mm_struct *mm)
--
mm/oom_kill.c-574- * under mmap_lock for reading because it serializes against the
mm/oom_kill.c:575: * mmap_write_lock();mmap_write_unlock() cycle in exit_mmap().
mm/oom_kill.c-576- */
]
|
| 62/2 |
2026/05/30 11:34 |
llm |
expert |
0m
Model:
Tokens:
input: 52688
output: 23
thoughts: 212
Thoughts: **Oh Crap, Memory Management Deadlock!**
Okay, so *exit\_mmap* grabs the *mmap\_write\_lock*. Right, makes sense. But hang on, if the process gets the rug pulled out from under it with a signal, it'll call *exit\_mmap*. And *exit\_mmap* will try to get the *mmap\_write\_lock*. But the *munmap* thread *already* holds that lock! This is trouble.
Wait a second... if the *munmap* thread has *mmap\_write\_lock*, and it's blocked waiting on `down_write(&ctx->map_changing_lock)`, it's never going to release *mmap\_write\_lock*! This means *exit\_mmap* will just sit there, blocking forever on `mmap_write_lock`! That's a classic deadlock situation.
But, *exit\_mmap* is only called when the *last* thread exits. Okay, so if the *munmap* thread is the last thread and is exiting, it will be interrupted by the kill signal, I need to consider that.
Wait! Aarrgghhh! `down_write` is uninterruptible! We can't let the process get killed in the middle of a `down_write` call. I need to make sure `down_write` *is* uninterruptible!
|
| 63/2 |
2026/05/30 11:34 |
tool |
grepper |
0m
Args: map[Expression:down_write.*map_changing_lock]
Results: map[Output:fs/userfaultfd.c=635=int dup_userfaultfd(struct vm_area_struct *vma, struct list_head *fcs)
--
fs/userfaultfd.c-675- userfaultfd_ctx_get(octx);
fs/userfaultfd.c:676: down_write(&octx->map_changing_lock);
fs/userfaultfd.c-677- atomic_inc(&octx->mmap_changing);
--
fs/userfaultfd.c=740=void mremap_userfaultfd_prep(struct vm_area_struct *vma,
--
fs/userfaultfd.c-752- userfaultfd_ctx_get(ctx);
fs/userfaultfd.c:753: down_write(&ctx->map_changing_lock);
fs/userfaultfd.c-754- atomic_inc(&ctx->mmap_changing);
--
fs/userfaultfd.c=792=bool userfaultfd_remove(struct vm_area_struct *vma,
--
fs/userfaultfd.c-803- userfaultfd_ctx_get(ctx);
fs/userfaultfd.c:804: down_write(&ctx->map_changing_lock);
fs/userfaultfd.c-805- atomic_inc(&ctx->mmap_changing);
--
fs/userfaultfd.c=833=int userfaultfd_unmap_prep(struct vm_area_struct *vma, unsigned long start,
--
fs/userfaultfd.c-847- userfaultfd_ctx_get(ctx);
fs/userfaultfd.c:848: down_write(&ctx->map_changing_lock);
fs/userfaultfd.c-849- atomic_inc(&ctx->mmap_changing);
]
|
| 64/2 |
2026/05/30 11:34 |
llm |
expert |
0m
Model:
Tokens:
input: 53695
output: 35
thoughts: 316
Thoughts: **My Assessment of the `down_write` Issue**
Okay, so I've confirmed it: `down_write` is truly uninterruptible. That's the crux of the problem. If a `munmap` thread gets caught in this state, it's going to be stuck in the `D` state – the dreaded uninterruptible sleep – *forever*. And that means it's essentially unkillable. Even a `SIGKILL` won't budge it. This is bad news, *very* bad news.
This zombie process, which can't be reaped, is going to hang around, perpetually holding onto the `mmap_write_lock`. Furthermore, it's holding a reference to the `mm_struct`, which means the memory allocated to that process is *never* going to be freed. This is a classic resource leak, and a really nasty one at that.
An attacker could exploit this vulnerability to create a whole host of these processes and systematically exhaust the system's memory, leading to a system-wide Denial of Service (DoS) situation – the dreaded OOM (Out Of Memory) condition. And it doesn't end there: If the `kernel.hung_task_panic` setting is enabled, which is pretty common in production systems for automated reboot on hung tasks, the hung task watchdog will be triggered and the system will *panic*. So, not just a DoS, but a *reliable* system-wide DoS! This is a serious flaw.
Now, I need to make sure I haven't missed anything. Are there any other leaked locks here? My mind is on `uffd_mfill_lock` because it returns a VMA with the per-VMA lock held. If that per-VMA lock gets leaked, that's another critical issue. But, in reality, I believe the core issue is the `down_read(&ctx->map_changing_lock)` call.
Let me just verify that `mfill_put_vma` does nothing when `state->vma` is NULL. I need to make sure my assumptions are valid.
|
| 65/2 |
2026/05/30 11:35 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:195 LineCount:10]
Results: map[Contents: 195: static void mfill_put_vma(struct mfill_state *state)
196: {
197: if (!state->vma)
198: return;
199:
200: up_read(&state->ctx->map_changing_lock);
201: uffd_mfill_unlock(state->vma);
202: state->vma = NULL;
203: }
204:
]
|
| 66/2 |
2026/05/30 11:35 |
llm |
expert |
0m
Model:
Tokens:
input: 54642
output: 35
thoughts: 18
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing the Code - A Quick Dissection**
Right, so I'm looking at this bit of code, and my initial reaction is... *it does absolutely nothing, as it stands*. It's like a function that promises to do something but just sits there. The real question that pops into my head immediately is where on earth is `state->vma` actually being populated? It's the lynchpin, the crucial piece. Without knowing how and where that pointer gets set, the purpose and efficacy of this whole operation is completely opaque. I need to trace back the execution path to understand the context and see what pre-conditions are in place. It seems pointless without that crucial step.
|
| 67/2 |
2026/05/30 11:35 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:255 LineCount:15]
Results: map[Contents: 255: if (uffd_flags_mode_is(flags, MFILL_ATOMIC_CONTINUE) &&
256: !ops->get_folio_noalloc)
257: goto out_unlock;
258:
259: out:
260: state->vma = dst_vma;
261: return 0;
262:
263: out_unlock:
264: mfill_put_vma(state);
265: return err;
266: }
267:
268: static pmd_t *mm_alloc_pmd(struct mm_struct *mm, unsigned long address)
269: {
]
|
| 68/2 |
2026/05/30 11:35 |
llm |
expert |
0m
Model:
Tokens:
input: 55074
output: 17
thoughts: 164
Thoughts: **Analyzing a Potential Kernel Lock Leak**
Okay, so I'm digging into this code and it's pretty clear: `state->vma` is *only* assigned the `out:` label, and if execution jumps to `out_unlock`, `state->vma` will be NULL. That means `mfill_put_vma(state)` will just bail out immediately. Bingo, the locks are leaked! This is interesting, and it's got me thinking... is this exploitable? Could this lead to privilege escalation?
It looks like we're leaking a read lock (`down_read`) and a per-VMA lock. The read lock is the main concern – it's incremented, but never decremented. So, hypothetically, if an attacker could trigger this leak, say, $2^{31}$ or $2^{63}$ times, could that overflow the lock counter? That's what I need to find out.
Now, `map_changing_lock` is an `rw_semaphore`, so my next step is to deep dive the `rw_semaphore` counter overflow to determine where the exploit lies. I'll need to research that specifically. Let's see what happens.
|
| 69/2 |
2026/05/30 11:35 |
tool |
grepper |
0m
Args: map[Expression:rw_semaphore]
Results: map[Output:
Full output is too long, showing 500 out of 2653 lines.
Use more precise expression if possible.
Documentation/RCU/checklist.rst=12=over a rather long period of time, but improvements are always welcome!
--
Documentation/RCU/checklist.rst-444- being globally amortized as they are for other forms of RCU.
Documentation/RCU/checklist.rst:445: Therefore, SRCU should be used in preference to rw_semaphore
Documentation/RCU/checklist.rst-446- only in extremely read-intensive situations, or in situations
Documentation/RCU/checklist.rst-447- requiring SRCU's read-side deadlock immunity or low read-side
Documentation/RCU/checklist.rst:448: realtime latency. You should also consider percpu_rw_semaphore
Documentation/RCU/checklist.rst-449- when you need lightweight readers.
--
Documentation/arch/s390/vfio-ap-locking.rst=97=The PQAP Hook Lock (arch/s390/include/asm/kvm_host.h)
--
Documentation/arch/s390/vfio-ap-locking.rst-105- ...
Documentation/arch/s390/vfio-ap-locking.rst:106: struct rw_semaphore pqap_hook_rwsem;
Documentation/arch/s390/vfio-ap-locking.rst-107- crypto_hook *pqap_hook;
--
Documentation/dev-tools/context-analysis.rst=81=Currently the following synchronization primitives are supported:
Documentation/dev-tools/context-analysis.rst-82-`raw_spinlock_t`, `spinlock_t`, `rwlock_t`, `mutex`, `seqlock_t`,
Documentation/dev-tools/context-analysis.rst:83:`bit_spinlock`, RCU, SRCU (`srcu_struct`), `rw_semaphore`, `local_lock_t`,
Documentation/dev-tools/context-analysis.rst-84-`ww_mutex`.
--
Documentation/filesystems/xfs/xfs-online-fsck-design.rst=3874=old kernels.
--
Documentation/filesystems/xfs/xfs-online-fsck-design.rst-3895-| The log coordinates access to incompatible features through the use of |
Documentation/filesystems/xfs/xfs-online-fsck-design.rst:3896:| one ``struct rw_semaphore`` for each feature. |
Documentation/filesystems/xfs/xfs-online-fsck-design.rst-3897-| The log cleaning code tries to take this rwsem in exclusive mode to |
--
Documentation/kernel-hacking/false-sharing.rst=47=There are many real-world cases of performance regressions caused by
Documentation/kernel-hacking/false-sharing.rst:48:false sharing. One of these is a rw_semaphore 'mmap_lock' inside
Documentation/kernel-hacking/false-sharing.rst-49-mm_struct struct, whose cache line layout change triggered a
--
Documentation/kernel-hacking/locking.rst=1052=Both spinlocks and mutexes have read/write variants: ``rwlock_t`` and
Documentation/kernel-hacking/locking.rst:1053::c:type:`struct rw_semaphore <rw_semaphore>`. These divide
Documentation/kernel-hacking/locking.rst-1054-users into two classes: the readers and the writers. If you are only
--
Documentation/locking/locktypes.rst=37=Sleeping lock types:
--
Documentation/locking/locktypes.rst-41- - semaphore
Documentation/locking/locktypes.rst:42: - rw_semaphore
Documentation/locking/locktypes.rst-43- - ww_mutex
Documentation/locking/locktypes.rst:44: - percpu_rw_semaphore
Documentation/locking/locktypes.rst-45-
--
Documentation/locking/locktypes.rst=90=semantics:
--
Documentation/locking/locktypes.rst-93-
Documentation/locking/locktypes.rst:94:rw_semaphores have a special interface which allows non-owner release for
Documentation/locking/locktypes.rst-95-readers.
--
Documentation/locking/locktypes.rst=129=result in priority inversion.
--
Documentation/locking/locktypes.rst-131-
Documentation/locking/locktypes.rst:132:rw_semaphore
Documentation/locking/locktypes.rst-133-============
Documentation/locking/locktypes.rst-134-
Documentation/locking/locktypes.rst:135:rw_semaphore is a multiple readers and single writer lock mechanism.
Documentation/locking/locktypes.rst-136-
--
Documentation/locking/locktypes.rst=138=writer starvation.
Documentation/locking/locktypes.rst-139-
Documentation/locking/locktypes.rst:140:rw_semaphore complies by default with the strict owner semantics, but there
Documentation/locking/locktypes.rst-141-exist special-purpose interfaces that allow non-owner release for readers.
Documentation/locking/locktypes.rst=142=These interfaces work independent of the kernel configuration.
Documentation/locking/locktypes.rst-143-
Documentation/locking/locktypes.rst:144:rw_semaphore and PREEMPT_RT
Documentation/locking/locktypes.rst-145----------------------------
Documentation/locking/locktypes.rst-146-
Documentation/locking/locktypes.rst:147:PREEMPT_RT kernels map rw_semaphore to a separate rt_mutex-based
Documentation/locking/locktypes.rst-148-implementation, thus changing the fairness:
Documentation/locking/locktypes.rst-149-
Documentation/locking/locktypes.rst:150: Because an rw_semaphore writer cannot grant its priority to multiple
Documentation/locking/locktypes.rst-151- readers, a preempted low-priority reader will continue holding its lock,
--
Documentation/locking/mutex-design.rst=161=locks in the kernel. E.g: on x86-64 it is 32 bytes, where 'struct semaphore'
Documentation/locking/mutex-design.rst:162:is 24 bytes and rw_semaphore is 40 bytes. Larger structure sizes mean more CPU
Documentation/locking/mutex-design.rst-163-cache and memory footprint.
--
Documentation/locking/percpu-rw-semaphore.rst=16=hundreds of milliseconds.
Documentation/locking/percpu-rw-semaphore.rst-17-
Documentation/locking/percpu-rw-semaphore.rst:18:The lock is declared with "struct percpu_rw_semaphore" type.
Documentation/locking/percpu-rw-semaphore.rst-19-The lock is initialized with percpu_init_rwsem, it returns 0 on success
--
Documentation/memory-barriers.txt=2379=the semaphore's list of waiting processes:
Documentation/memory-barriers.txt-2380-
Documentation/memory-barriers.txt:2381: struct rw_semaphore {
Documentation/memory-barriers.txt-2382- ...
--
Documentation/translations/it_IT/kernel-hacking/locking.rst=1079=Sia gli spinlock che i mutex hanno una variante per la lettura/scrittura
Documentation/translations/it_IT/kernel-hacking/locking.rst:1080:(read/write): ``rwlock_t`` e :c:type:`struct rw_semaphore <rw_semaphore>`.
Documentation/translations/it_IT/kernel-hacking/locking.rst-1081-Queste dividono gli utenti in due categorie: i lettori e gli scrittori.
--
Documentation/translations/it_IT/locking/locktypes.rst=39=In questa categoria troviamo:
--
Documentation/translations/it_IT/locking/locktypes.rst-43- - semaphore
Documentation/translations/it_IT/locking/locktypes.rst:44: - rw_semaphore
Documentation/translations/it_IT/locking/locktypes.rst-45- - ww_mutex
Documentation/translations/it_IT/locking/locktypes.rst:46: - percpu_rw_semaphore
Documentation/translations/it_IT/locking/locktypes.rst-47-
--
Documentation/translations/it_IT/locking/locktypes.rst=91=molto stringente riguardo al proprietario di un blocco:
--
Documentation/translations/it_IT/locking/locktypes.rst-94-
Documentation/translations/it_IT/locking/locktypes.rst:95:I semafori rw_semaphores hanno un'interfaccia speciale che permette anche ai non
Documentation/translations/it_IT/locking/locktypes.rst-96-proprietari del blocco di rilasciarlo per i lettori.
--
Documentation/translations/it_IT/locking/locktypes.rst=130=all'inversione di priorità.
--
Documentation/translations/it_IT/locking/locktypes.rst-132-
Documentation/translations/it_IT/locking/locktypes.rst:133:rw_semaphore
Documentation/translations/it_IT/locking/locktypes.rst-134-============
Documentation/translations/it_IT/locking/locktypes.rst-135-
Documentation/translations/it_IT/locking/locktypes.rst:136:Il blocco rw_semaphore è un meccanismo che permette più lettori ma un solo scrittore.
Documentation/translations/it_IT/locking/locktypes.rst-137-
--
Documentation/translations/it_IT/locking/locktypes.rst=144=dalla configurazione del kernel.
Documentation/translations/it_IT/locking/locktypes.rst-145-
Documentation/translations/it_IT/locking/locktypes.rst:146:rw_semaphore e PREEMPT_RT
Documentation/translations/it_IT/locking/locktypes.rst-147--------------------------
Documentation/translations/it_IT/locking/locktypes.rst-148-
Documentation/translations/it_IT/locking/locktypes.rst:149:I kernel PREEMPT_RT sostituiscono i rw_semaphore con un'implementazione basata
Documentation/translations/it_IT/locking/locktypes.rst-150-su rt_mutex, e questo ne modifica l'imparzialità:
Documentation/translations/it_IT/locking/locktypes.rst-151-
Documentation/translations/it_IT/locking/locktypes.rst:152: Dato che uno scrittore rw_semaphore non può assicurare la propria priorità ai
Documentation/translations/it_IT/locking/locktypes.rst-153- suoi lettori, un lettore con priorità più bassa che ha subito la prelazione
--
Documentation/translations/it_IT/locking/locktypes.rst=317=sull'uso di rt_mutex. Questo ne modifica il significato:
--
Documentation/translations/it_IT/locking/locktypes.rst-320-
Documentation/translations/it_IT/locking/locktypes.rst:321: - Dato che uno scrittore rw_semaphore non può assicurare la propria priorità ai
Documentation/translations/it_IT/locking/locktypes.rst-322- suoi lettori, un lettore con priorità più bassa che ha subito la prelazione
--
Documentation/translations/sp_SP/memory-barriers.txt=2478=vinculada a la lista de procesos en espera del semáforo:
Documentation/translations/sp_SP/memory-barriers.txt-2479-
Documentation/translations/sp_SP/memory-barriers.txt:2480: struct rw_semaphore {
Documentation/translations/sp_SP/memory-barriers.txt-2481- ...
--
Documentation/translations/zh_CN/locking/mutex-design.rst=37=kernel/locking/mutex.c中实现。这些锁使用一个原子变量(->owner)来跟踪
--
Documentation/translations/zh_CN/locking/mutex-design.rst-136-与它最初的设计和目的不同,'struct mutex' 是内核中最大的锁之一。例如:在
Documentation/translations/zh_CN/locking/mutex-design.rst:137:x86-64上它是32字节,而 'struct semaphore' 是24字节,rw_semaphore是
Documentation/translations/zh_CN/locking/mutex-design.rst-138-40字节。更大的结构体大小意味着更多的CPU缓存和内存占用。
--
arch/powerpc/include/asm/dtl.h=37=extern struct kmem_cache *dtl_cache;
arch/powerpc/include/asm/dtl.h:38:extern struct rw_semaphore dtl_access_lock;
arch/powerpc/include/asm/dtl.h-39-
--
arch/s390/include/asm/kvm_host.h=509=struct kvm_s390_crypto {
arch/s390/include/asm/kvm_host.h-510- struct kvm_s390_crypto_cb *crycb;
arch/s390/include/asm/kvm_host.h:511: struct rw_semaphore pqap_hook_rwsem;
arch/s390/include/asm/kvm_host.h-512- crypto_hook *pqap_hook;
--
arch/x86/include/asm/kvm_host.h=1400=struct kvm_arch {
--
arch/x86/include/asm/kvm_host.h-1456- */
arch/x86/include/asm/kvm_host.h:1457: struct rw_semaphore apicv_update_lock;
arch/x86/include/asm/kvm_host.h-1458- atomic_t apicv_nr_irq_window_req;
--
arch/x86/include/asm/mmu.h=25=typedef struct {
--
arch/x86/include/asm/mmu.h-44-#ifdef CONFIG_MODIFY_LDT_SYSCALL
arch/x86/include/asm/mmu.h:45: struct rw_semaphore ldt_usr_sem;
arch/x86/include/asm/mmu.h-46- struct ldt_struct *ldt;
--
crypto/internal.h=52=enum {
--
crypto/internal.h-63-
crypto/internal.h:64:extern struct rw_semaphore crypto_alg_sem;
crypto/internal.h-65-extern struct list_head crypto_alg_list __guarded_by(&crypto_alg_sem);
--
drivers/accel/amdxdna/amdxdna_pci_drv.h=96=struct amdxdna_dev {
--
drivers/accel/amdxdna/amdxdna_pci_drv.h-104- struct amdxdna_fw_ver fw_ver;
drivers/accel/amdxdna/amdxdna_pci_drv.h:105: struct rw_semaphore notifier_lock; /* for mmu notifier*/
drivers/accel/amdxdna/amdxdna_pci_drv.h-106- struct workqueue_struct *notifier_wq;
--
drivers/accel/habanalabs/common/habanalabs.h=2415=struct hl_dbg_device_entry {
--
drivers/accel/habanalabs/common/habanalabs.h-2433- char *state_dump[HL_STATE_DUMP_HIST_LEN];
drivers/accel/habanalabs/common/habanalabs.h:2434: struct rw_semaphore state_dump_sem;
drivers/accel/habanalabs/common/habanalabs.h-2435- u64 addr;
--
drivers/accel/ivpu/ivpu_pm.h=14=struct ivpu_pm_info {
--
drivers/accel/ivpu/ivpu_pm.h-17- struct work_struct recovery_work;
drivers/accel/ivpu/ivpu_pm.h:18: struct rw_semaphore reset_lock;
drivers/accel/ivpu/ivpu_pm.h-19- atomic_t reset_counter;
--
drivers/acpi/cppc_acpi.c=48=struct cppc_pcc_data {
--
drivers/acpi/cppc_acpi.c-71- */
drivers/acpi/cppc_acpi.c:72: struct rw_semaphore pcc_lock;
drivers/acpi/cppc_acpi.c-73-
--
drivers/block/drbd/drbd_int.h=1519=struct drbd_device {
--
drivers/block/drbd/drbd_int.h-1617- u64 next_exposed_data_uuid;
drivers/block/drbd/drbd_int.h:1618: struct rw_semaphore uuid_sem;
drivers/block/drbd/drbd_int.h-1619- atomic_t rs_sect_ev; /* for submitted resync data rate, both */
--
drivers/block/rbd.c=381=struct rbd_device {
--
drivers/block/rbd.c-411-
drivers/block/rbd.c:412: struct rw_semaphore lock_rwsem;
drivers/block/rbd.c-413- enum rbd_lock_state lock_state;
--
drivers/block/rbd.c-442- /* protects updating the header */
drivers/block/rbd.c:443: struct rw_semaphore header_rwsem;
drivers/block/rbd.c-444-
--
drivers/block/zram/zram_drv.h=108=struct zram {
--
drivers/block/zram/zram_drv.h-114- /* Locks the device either in exclusive or in shared mode */
drivers/block/zram/zram_drv.h:115: struct rw_semaphore dev_lock;
drivers/block/zram/zram_drv.h-116- /*
--
drivers/bluetooth/hci_uart.h=64=struct hci_uart {
--
drivers/bluetooth/hci_uart.h-74- const struct hci_uart_proto *proto;
drivers/bluetooth/hci_uart.h:75: struct percpu_rw_semaphore proto_lock; /* Stop work for proto close */
drivers/bluetooth/hci_uart.h-76- void *priv;
--
drivers/crypto/intel/qat/qat_common/adf_accel_devices.h=443=struct adf_sysfs {
drivers/crypto/intel/qat/qat_common/adf_accel_devices.h-444- int ring_num;
drivers/crypto/intel/qat/qat_common/adf_accel_devices.h:445: struct rw_semaphore lock; /* protects access to the fields in this struct */
drivers/crypto/intel/qat/qat_common/adf_accel_devices.h-446-};
--
drivers/crypto/intel/qat/qat_common/adf_cfg.h=26=struct adf_cfg_device_data {
--
drivers/crypto/intel/qat/qat_common/adf_cfg.h-28- struct dentry *debug;
drivers/crypto/intel/qat/qat_common/adf_cfg.h:29: struct rw_semaphore lock;
drivers/crypto/intel/qat/qat_common/adf_cfg.h-30-};
--
drivers/crypto/intel/qat/qat_common/adf_rl.h=74=struct adf_rl_interface_data {
--
drivers/crypto/intel/qat/qat_common/adf_rl.h-76- enum adf_base_services cap_rem_srv;
drivers/crypto/intel/qat/qat_common/adf_rl.h:77: struct rw_semaphore lock;
drivers/crypto/intel/qat/qat_common/adf_rl.h-78- bool sysfs_added;
--
drivers/cxl/core/core.h=121=struct cxl_rwsem {
--
drivers/cxl/core/core.h-125- */
drivers/cxl/core/core.h:126: struct rw_semaphore region;
drivers/cxl/core/core.h-127- /*
--
drivers/cxl/core/core.h-130- */
drivers/cxl/core/core.h:131: struct rw_semaphore dpa;
drivers/cxl/core/core.h-132-};
--
drivers/firewire/core.h=138=void fw_cdev_handle_phy_packet(struct fw_card *card, struct fw_packet *p);
--
drivers/firewire/core.h-142-
drivers/firewire/core.h:143:extern struct rw_semaphore fw_device_rwsem;
drivers/firewire/core.h-144-extern struct xarray fw_device_xa;
--
drivers/gpu/drm/amd/amdgpu/amdgpu_reset.h=95=struct amdgpu_reset_domain {
--
drivers/gpu/drm/amd/amdgpu/amdgpu_reset.h-98- enum amdgpu_reset_domain_type type;
drivers/gpu/drm/amd/amdgpu/amdgpu_reset.h:99: struct rw_semaphore sem;
drivers/gpu/drm/amd/amdgpu/amdgpu_reset.h-100- atomic_t in_gpu_reset;
--
drivers/gpu/drm/gma500/mmu.h=11=struct psb_mmu_driver {
--
drivers/gpu/drm/gma500/mmu.h-14- */
drivers/gpu/drm/gma500/mmu.h:15: struct rw_semaphore sem;
drivers/gpu/drm/gma500/mmu.h-16-
--
drivers/gpu/drm/imagination/pvr_device.h=76=struct pvr_device {
--
drivers/gpu/drm/imagination/pvr_device.h-325- */
drivers/gpu/drm/imagination/pvr_device.h:326: struct rw_semaphore reset_sem;
drivers/gpu/drm/imagination/pvr_device.h-327-
--
drivers/gpu/drm/panthor/panthor_heap.c=83=struct panthor_heap_pool {
--
drivers/gpu/drm/panthor/panthor_heap.c-93- /** @lock: Lock protecting access to @xa. */
drivers/gpu/drm/panthor/panthor_heap.c:94: struct rw_semaphore lock;
drivers/gpu/drm/panthor/panthor_heap.c-95-
--
drivers/gpu/drm/radeon/radeon.h=1583=struct radeon_pm {
--
drivers/gpu/drm/radeon/radeon.h-1585- /* write locked while reprogramming mclk */
drivers/gpu/drm/radeon/radeon.h:1586: struct rw_semaphore mclk_lock;
drivers/gpu/drm/radeon/radeon.h-1587- u32 active_crtcs;
--
drivers/gpu/drm/radeon/radeon.h=2297=struct radeon_device {
--
drivers/gpu/drm/radeon/radeon.h-2304- struct radeon_agp_head *agp;
drivers/gpu/drm/radeon/radeon.h:2305: struct rw_semaphore exclusive_lock;
drivers/gpu/drm/radeon/radeon.h-2306- /* ASIC */
--
drivers/gpu/drm/vmwgfx/vmwgfx_drv.h=242=struct vmw_fifo_state {
--
drivers/gpu/drm/vmwgfx/vmwgfx_drv.h-249- struct mutex fifo_mutex;
drivers/gpu/drm/vmwgfx/vmwgfx_drv.h:250: struct rw_semaphore rwsem;
drivers/gpu/drm/vmwgfx/vmwgfx_drv.h-251-};
--
drivers/gpu/drm/xe/xe_device_types.h=88=struct xe_device {
--
drivers/gpu/drm/xe/xe_device_types.h-302- /** @usm.lock: protects UM state */
drivers/gpu/drm/xe/xe_device_types.h:303: struct rw_semaphore lock;
drivers/gpu/drm/xe/xe_device_types.h-304- /** @usm.pf_wq: page fault work queue, unbound, high priority */
--
drivers/gpu/drm/xe/xe_guard.h-16- * incompatible features, where we can't follow the strict owner semantics
drivers/gpu/drm/xe/xe_guard.h:17: * required by the &rw_semaphore.
drivers/gpu/drm/xe/xe_guard.h-18- *
drivers/gpu/drm/xe/xe_guard.h:19: * NOTE! It shouldn't be used to protect a data, use &rw_semaphore instead.
drivers/gpu/drm/xe/xe_guard.h-20- */
--
drivers/gpu/drm/xe/xe_hw_engine_group_types.h=31=struct xe_hw_engine_group {
--
drivers/gpu/drm/xe/xe_hw_engine_group_types.h-45- */
drivers/gpu/drm/xe/xe_hw_engine_group_types.h:46: struct rw_semaphore mode_sem;
drivers/gpu/drm/xe/xe_hw_engine_group_types.h-47- /** @cur_mode: current execution mode of this hw engine group */
--
drivers/gpu/drm/xe/xe_validation.h=79=struct xe_validation_device {
drivers/gpu/drm/xe/xe_validation.h:80: struct rw_semaphore lock;
drivers/gpu/drm/xe/xe_validation.h-81-};
--
drivers/gpu/drm/xe/xe_vm_types.h=179=struct xe_vm {
--
drivers/gpu/drm/xe/xe_vm_types.h-242- */
drivers/gpu/drm/xe/xe_vm_types.h:243: struct rw_semaphore lock;
drivers/gpu/drm/xe/xe_vm_types.h-244- /**
--
drivers/gpu/drm/xe/xe_vm_types.h-315- /** @exec_queues.lock: lock to protect exec_queues list */
drivers/gpu/drm/xe/xe_vm_types.h:316: struct rw_semaphore lock;
drivers/gpu/drm/xe/xe_vm_types.h-317- } exec_queues;
--
drivers/i2c/i2c-core.h=9=struct i2c_devinfo {
--
drivers/i2c/i2c-core.h-17- */
drivers/i2c/i2c-core.h:18:extern struct rw_semaphore __i2c_board_lock;
drivers/i2c/i2c-core.h-19-extern struct list_head __i2c_board_list;
--
drivers/infiniband/core/netlink.c=45=static struct {
--
drivers/infiniband/core/netlink.c-49- */
drivers/infiniband/core/netlink.c:50: struct rw_semaphore sem;
drivers/infiniband/core/netlink.c-51-} rdma_nl_types[RDMA_NL_NUM_CLIENTS];
--
drivers/infiniband/hw/erdma/erdma_verbs.h=296=struct erdma_qp {
--
drivers/infiniband/hw/erdma/erdma_verbs.h-301- struct erdma_cep *cep;
drivers/infiniband/hw/erdma/erdma_verbs.h:302: struct rw_semaphore state_lock;
drivers/infiniband/hw/erdma/erdma_verbs.h-303-
--
drivers/infiniband/sw/siw/siw.h=416=struct siw_qp {
--
drivers/infiniband/sw/siw/siw.h-425- struct siw_cep *cep;
drivers/infiniband/sw/siw/siw.h:426: struct rw_semaphore state_lock;
drivers/infiniband/sw/siw/siw.h-427-
--
drivers/iommu/iommufd/iommufd_private.h=43=struct iommufd_ctx {
--
drivers/iommu/iommufd/iommufd_private.h-47- wait_queue_head_t destroy_wait;
drivers/iommu/iommufd/iommufd_private.h:48: struct rw_semaphore ioas_creation_lock;
drivers/iommu/iommufd/iommufd_private.h-49- struct maple_tree mt_mmap;
--
drivers/iommu/iommufd/iommufd_private.h=83=struct io_pagetable {
drivers/iommu/iommufd/iommufd_private.h:84: struct rw_semaphore domains_rwsem;
drivers/iommu/iommufd/iommufd_private.h-85- struct xarray domains;
--
drivers/iommu/iommufd/iommufd_private.h-88-
drivers/iommu/iommufd/iommufd_private.h:89: struct rw_semaphore iova_rwsem;
drivers/iommu/iommufd/iommufd_private.h-90- struct rb_root_cached area_itree;
--
drivers/iommu/iommufd/selftest.c=176=struct mock_dev {
--
drivers/iommu/iommufd/selftest.c-178- struct mock_viommu *viommu;
drivers/iommu/iommufd/selftest.c:179: struct rw_semaphore viommu_rwsem;
drivers/iommu/iommufd/selftest.c-180- unsigned long flags;
--
drivers/leds/leds-bd2802.c=67=struct bd2802_led {
--
drivers/leds/leds-bd2802.c-70- struct gpio_desc *reset;
drivers/leds/leds-bd2802.c:71: struct rw_semaphore rwsem;
drivers/leds/leds-bd2802.c-72-
--
drivers/leds/leds.h=27=ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
--
drivers/leds/leds.h-30-
drivers/leds/leds.h:31:extern struct rw_semaphore leds_list_lock;
drivers/leds/leds.h-32-extern struct list_head leds_list;
--
drivers/md/bcache/bcache.h=302=struct cached_dev {
--
drivers/md/bcache/bcache.h-328- */
drivers/md/bcache/bcache.h:329: struct rw_semaphore writeback_lock;
drivers/md/bcache/bcache.h-330-
--
drivers/md/bcache/btree.h=117=struct btree {
--
drivers/md/bcache/btree.h-124- unsigned long seq;
drivers/md/bcache/btree.h:125: struct rw_semaphore lock;
drivers/md/bcache/btree.h-126- struct cache_set *c;
--
drivers/md/dm-bufio.c=378=struct buffer_tree {
drivers/md/dm-bufio.c-379- union {
drivers/md/dm-bufio.c:380: struct rw_semaphore lock;
drivers/md/dm-bufio.c-381- rwlock_t spinlock;
--
drivers/md/dm-cache-metadata.c=104=struct dm_cache_metadata {
--
drivers/md/dm-cache-metadata.c-117-
drivers/md/dm-cache-metadata.c:118: struct rw_semaphore root_lock;
drivers/md/dm-cache-metadata.c-119- unsigned long flags;
--
drivers/md/dm-cache-target.c=297=struct cache {
--
drivers/md/dm-cache-target.c-352-
drivers/md/dm-cache-target.c:353: struct rw_semaphore quiesce_lock;
drivers/md/dm-cache-target.c-354-
--
drivers/md/dm-cache-target.c-398-
drivers/md/dm-cache-target.c:399: struct rw_semaphore background_work_lock;
drivers/md/dm-cache-target.c-400-
--
drivers/md/dm-clone-metadata.c=116=struct dm_clone_metadata {
--
drivers/md/dm-clone-metadata.c-145-
drivers/md/dm-clone-metadata.c:146: struct rw_semaphore lock;
drivers/md/dm-clone-metadata.c-147-
--
drivers/md/dm-snap.c=54=struct dm_snapshot {
drivers/md/dm-snap.c:55: struct rw_semaphore lock;
drivers/md/dm-snap.c-56-
--
drivers/md/dm-snap.c=346=static struct list_head *_dm_origins;
drivers/md/dm-snap.c:347:static struct rw_semaphore _origins_lock;
drivers/md/dm-snap.c-348-
--
drivers/md/dm-thin-metadata.c=148=struct dm_pool_metadata {
--
drivers/md/dm-thin-metadata.c-184-
drivers/md/dm-thin-metadata.c:185: struct rw_semaphore root_lock;
drivers/md/dm-thin-metadata.c-186- uint32_t time;
--
drivers/md/dm-thin.c=148=struct throttle {
drivers/md/dm-thin.c:149: struct rw_semaphore lock;
drivers/md/dm-thin.c-150- unsigned long threshold;
--
drivers/md/dm-zoned-metadata.c=143=struct dmz_metadata {
--
drivers/md/dm-zoned-metadata.c-183- atomic_t nr_mblks;
drivers/md/dm-zoned-metadata.c:184: struct rw_semaphore mblk_sem;
drivers/md/dm-zoned-metadata.c-185- struct mutex mblk_flush_lock;
--
drivers/misc/mei/mei_dev.h=559=struct mei_device {
--
drivers/misc/mei/mei_dev.h-630-
drivers/misc/mei/mei_dev.h:631: struct rw_semaphore me_clients_rwsem;
drivers/misc/mei/mei_dev.h-632- struct list_head me_clients;
--
drivers/misc/sgi-gru/grutables.h=445=struct gru_blade_state {
--
drivers/misc/sgi-gru/grutables.h-449- reserved DSR */
drivers/misc/sgi-gru/grutables.h:450: struct rw_semaphore bs_kgts_sema; /* lock for kgts */
drivers/misc/sgi-gru/grutables.h-451- struct gru_thread_state *bs_kgts; /* GTS for kernel use */
--
drivers/misc/vmw_balloon.c=262=struct vmballoon {
--
drivers/misc/vmw_balloon.c-375- */
drivers/misc/vmw_balloon.c:376: struct rw_semaphore conf_sem;
drivers/misc/vmw_balloon.c-377-
--
drivers/mtd/ubi/ubi.h=194=struct ubi_ltree_entry {
--
drivers/mtd/ubi/ubi.h-198- int users;
drivers/mtd/ubi/ubi.h:199: struct rw_semaphore mutex;
drivers/mtd/ubi/ubi.h-200-};
--
drivers/mtd/ubi/ubi.h=558=struct ubi_device {
--
drivers/mtd/ubi/ubi.h-596- struct ubi_fm_pool fm_wl_pool;
drivers/mtd/ubi/ubi.h:597: struct rw_semaphore fm_eba_sem;
drivers/mtd/ubi/ubi.h:598: struct rw_semaphore fm_protect;
drivers/mtd/ubi/ubi.h-599- void *fm_buf;
--
drivers/mtd/ubi/ubi.h-617- struct mutex move_mutex;
drivers/mtd/ubi/ubi.h:618: struct rw_semaphore work_sem;
drivers/mtd/ubi/ubi.h-619- int wl_scheduled;
--
drivers/net/ethernet/mellanox/mlx4/mlx4.h=630=struct mlx4_cmd {
--
drivers/net/ethernet/mellanox/mlx4/mlx4.h-635- struct semaphore event_sem;
drivers/net/ethernet/mellanox/mlx4/mlx4.h:636: struct rw_semaphore switch_sem;
drivers/net/ethernet/mellanox/mlx4/mlx4.h-637- int max_cmds;
--
drivers/net/ethernet/mellanox/mlx5/core/en/rx_res.c=8=struct mlx5e_rx_res {
--
drivers/net/ethernet/mellanox/mlx5/core/en/rx_res.c-14- struct mlx5e_packet_merge_param pkt_merge_param;
drivers/net/ethernet/mellanox/mlx5/core/en/rx_res.c:15: struct rw_semaphore pkt_merge_param_sem;
drivers/net/ethernet/mellanox/mlx5/core/en/rx_res.c-16-
--
drivers/net/ethernet/mellanox/mlx5/core/eswitch.h=364=struct mlx5_eswitch {
--
drivers/net/ethernet/mellanox/mlx5/core/eswitch.h-385- */
drivers/net/ethernet/mellanox/mlx5/core/eswitch.h:386: struct rw_semaphore mode_lock;
drivers/net/ethernet/mellanox/mlx5/core/eswitch.h-387- atomic64_t user_count;
--
drivers/net/ethernet/mellanox/mlx5/core/fs_core.h=150=struct fs_node {
]
|
| 70/2 |
2026/05/30 11:35 |
llm |
expert |
0m
Model:
Tokens:
input: 65399
output: 20
thoughts: 270
Thoughts: **My Analysis of a Potential Locking Issue**
Okay, so I'm looking at this `mmap_changing_lock` and it's an `rw_semaphore`. Right, and if a task leaks a `down_read` call, that counter gets incremented. The immediate worry is that after $2^{31}$ increments, the counter could overflow. But then I realize: each of these leaks requires an `ioctl` call, right? $2^{31}$ of those would take forever. The real problem here is the `mmap_write_lock` being blocked, causing a local DoS.
Hmm, but can I escalate this? Is there a way to make this a use-after-free or cause some memory corruption? If I leak a `down_read`, then a `down_write` will block indefinitely. And then, there's `mfill_put_vma`. If that function doesn't actually *do* anything, then `uffd_mfill_unlock(state->vma)` isn't called! So, `uffd_mfill_unlock` resolves to `vma_end_read(vma)`.
Wait a minute! `vma_end_read(vma)` releases the per-VMA lock. Now, if *that* per-VMA lock is leaked... what actually happens? I need to know. It's an `rw_semaphore`, right, and that's within the `struct vm_area_struct`. Okay, I've got to check `vma_end_read` and see exactly what's going on there.
|
| 71/2 |
2026/05/30 11:35 |
tool |
grepper |
0m
Args: map[Expression:vma_end_read]
Results: map[Output:Documentation/mm/process_addrs.rst=42=Terminology
--
Documentation/mm/process_addrs.rst-49- as a read/write semaphore in practice. A VMA read lock is obtained via
Documentation/mm/process_addrs.rst:50: :c:func:`!lock_vma_under_rcu` (and unlocked via :c:func:`!vma_end_read`) and a
Documentation/mm/process_addrs.rst-51- write lock via vma_start_write() or vma_start_write_killable()
--
Documentation/mm/process_addrs.rst=768=duration and the caller of :c:func:`!lock_vma_under_rcu` must drop it via
Documentation/mm/process_addrs.rst:769::c:func:`!vma_end_read`.
Documentation/mm/process_addrs.rst-770-
--
Documentation/mm/process_addrs.rst=793=operation, i.e. it tries to acquire a read lock but returns false if it is
Documentation/mm/process_addrs.rst:794:unable to do so. At the end of the read operation, :c:func:`!vma_end_read` is
Documentation/mm/process_addrs.rst-795-called to release the VMA read lock.
--
arch/arm/mm/fault.c=293=do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)
--
arch/arm/mm/fault.c-356- if (!(vma->vm_flags & vm_flags)) {
arch/arm/mm/fault.c:357: vma_end_read(vma);
arch/arm/mm/fault.c-358- count_vm_vma_lock_event(VMA_LOCK_SUCCESS);
--
arch/arm/mm/fault.c-364- if (!(fault & (VM_FAULT_RETRY | VM_FAULT_COMPLETED)))
arch/arm/mm/fault.c:365: vma_end_read(vma);
arch/arm/mm/fault.c-366-
--
arch/arm64/mm/fault.c=556=static int __kprobes do_page_fault(unsigned long far, unsigned long esr,
--
arch/arm64/mm/fault.c-633- if (is_invalid_gcs_access(vma, esr)) {
arch/arm64/mm/fault.c:634: vma_end_read(vma);
arch/arm64/mm/fault.c-635- fault = 0;
--
arch/arm64/mm/fault.c-640- if (!(vma->vm_flags & vm_flags)) {
arch/arm64/mm/fault.c:641: vma_end_read(vma);
arch/arm64/mm/fault.c-642- fault = 0;
--
arch/arm64/mm/fault.c-649- pkey = vma_pkey(vma);
arch/arm64/mm/fault.c:650: vma_end_read(vma);
arch/arm64/mm/fault.c-651- fault = 0;
--
arch/arm64/mm/fault.c-658- if (!(fault & (VM_FAULT_RETRY | VM_FAULT_COMPLETED)))
arch/arm64/mm/fault.c:659: vma_end_read(vma);
arch/arm64/mm/fault.c-660-
--
arch/loongarch/mm/fault.c=175=static void __kprobes __do_page_fault(struct pt_regs *regs,
--
arch/loongarch/mm/fault.c-228- if (!(vma->vm_flags & VM_WRITE)) {
arch/loongarch/mm/fault.c:229: vma_end_read(vma);
arch/loongarch/mm/fault.c-230- si_code = SEGV_ACCERR;
--
arch/loongarch/mm/fault.c-235- if (!(vma->vm_flags & VM_EXEC) && address == exception_era(regs)) {
arch/loongarch/mm/fault.c:236: vma_end_read(vma);
arch/loongarch/mm/fault.c-237- si_code = SEGV_ACCERR;
--
arch/loongarch/mm/fault.c-241- if (!(vma->vm_flags & (VM_READ | VM_WRITE)) && address != exception_era(regs)) {
arch/loongarch/mm/fault.c:242: vma_end_read(vma);
arch/loongarch/mm/fault.c-243- si_code = SEGV_ACCERR;
--
arch/loongarch/mm/fault.c-250- if (!(fault & (VM_FAULT_RETRY | VM_FAULT_COMPLETED)))
arch/loongarch/mm/fault.c:251: vma_end_read(vma);
arch/loongarch/mm/fault.c-252-
--
arch/powerpc/mm/fault.c=75=static int __bad_area(struct pt_regs *regs, unsigned long address, int si_code,
--
arch/powerpc/mm/fault.c-85- else
arch/powerpc/mm/fault.c:86: vma_end_read(vma);
arch/powerpc/mm/fault.c-87-
--
arch/powerpc/mm/fault.c=91=static noinline int bad_access_pkey(struct pt_regs *regs, unsigned long address,
--
arch/powerpc/mm/fault.c-118- else
arch/powerpc/mm/fault.c:119: vma_end_read(vma);
arch/powerpc/mm/fault.c-120-
--
arch/powerpc/mm/fault.c=414=static int ___do_page_fault(struct pt_regs *regs, unsigned long address,
--
arch/powerpc/mm/fault.c-506- if (!(fault & (VM_FAULT_RETRY | VM_FAULT_COMPLETED)))
arch/powerpc/mm/fault.c:507: vma_end_read(vma);
arch/powerpc/mm/fault.c-508-
--
arch/riscv/mm/fault.c=278=void handle_page_fault(struct pt_regs *regs)
--
arch/riscv/mm/fault.c-354- if (unlikely(access_error(cause, vma))) {
arch/riscv/mm/fault.c:355: vma_end_read(vma);
arch/riscv/mm/fault.c-356- count_vm_vma_lock_event(VMA_LOCK_SUCCESS);
--
arch/riscv/mm/fault.c-363- if (!(fault & (VM_FAULT_RETRY | VM_FAULT_COMPLETED)))
arch/riscv/mm/fault.c:364: vma_end_read(vma);
arch/riscv/mm/fault.c-365-
--
arch/s390/mm/fault.c=266=static void do_exception(struct pt_regs *regs, int access)
--
arch/s390/mm/fault.c-300- if (!(vma->vm_flags & access)) {
arch/s390/mm/fault.c:301: vma_end_read(vma);
arch/s390/mm/fault.c-302- count_vm_vma_lock_event(VMA_LOCK_SUCCESS);
--
arch/s390/mm/fault.c-306- if (!(fault & (VM_FAULT_RETRY | VM_FAULT_COMPLETED)))
arch/s390/mm/fault.c:307: vma_end_read(vma);
arch/s390/mm/fault.c-308- if (!(fault & VM_FAULT_RETRY)) {
--
arch/x86/mm/fault.c=834=__bad_area(struct pt_regs *regs, unsigned long error_code,
--
arch/x86/mm/fault.c-844- else
arch/x86/mm/fault.c:845: vma_end_read(vma);
arch/x86/mm/fault.c-846-
--
arch/x86/mm/fault.c=1207=void do_user_addr_fault(struct pt_regs *regs,
--
arch/x86/mm/fault.c-1335- if (!(fault & (VM_FAULT_RETRY | VM_FAULT_COMPLETED)))
arch/x86/mm/fault.c:1336: vma_end_read(vma);
arch/x86/mm/fault.c-1337-
--
drivers/android/binder_alloc.c=254=static int binder_page_insert(struct binder_alloc *alloc,
--
drivers/android/binder_alloc.c-266- ret = vm_insert_page(vma, addr, page);
drivers/android/binder_alloc.c:267: vma_end_read(vma);
drivers/android/binder_alloc.c-268- return ret;
--
drivers/android/binder_alloc.c=1134=enum lru_status binder_alloc_free_page(struct list_head *item,
--
drivers/android/binder_alloc.c-1196- else
drivers/android/binder_alloc.c:1197: vma_end_read(vma);
drivers/android/binder_alloc.c-1198- mmput_async(mm);
--
drivers/android/binder_alloc.c-1208- else
drivers/android/binder_alloc.c:1209: vma_end_read(vma);
drivers/android/binder_alloc.c-1210-err_mmap_read_lock_failed:
--
fs/proc/task_mmu.c=141=static void unlock_ctx_vma(struct proc_maps_locking_ctx *lock_ctx)
--
fs/proc/task_mmu.c-143- if (lock_ctx->locked_vma) {
fs/proc/task_mmu.c:144: vma_end_read(lock_ctx->locked_vma);
fs/proc/task_mmu.c-145- lock_ctx->locked_vma = NULL;
--
include/linux/mm.h=852=static inline void release_fault_lock(struct vm_fault *vmf)
--
include/linux/mm.h-854- if (vmf->flags & FAULT_FLAG_VMA_LOCK)
include/linux/mm.h:855: vma_end_read(vmf->vma);
include/linux/mm.h-856- else
--
include/linux/mmap_lock.h=257=static inline bool vma_start_read_locked(struct vm_area_struct *vma)
--
include/linux/mmap_lock.h-261-
include/linux/mmap_lock.h:262:static inline void vma_end_read(struct vm_area_struct *vma)
include/linux/mmap_lock.h-263-{
--
include/linux/mmap_lock.h=502=static inline void vma_lock_init(struct vm_area_struct *vma, bool reset_refcnt) {}
include/linux/mmap_lock.h:503:static inline void vma_end_read(struct vm_area_struct *vma) {}
include/linux/mmap_lock.h-504-static inline void vma_start_write(struct vm_area_struct *vma) {}
--
mm/filemap.c=1717=static int __folio_lock_async(struct folio *folio, struct wait_page_queue *wait)
--
mm/filemap.c-1747- * mmap_lock or per-VMA lock has been released (mmap_read_unlock() or
mm/filemap.c:1748: * vma_end_read()), unless flags had both FAULT_FLAG_ALLOW_RETRY and
mm/filemap.c-1749- * FAULT_FLAG_RETRY_NOWAIT set, in which case the lock is still held.
--
mm/huge_memory.c=1444=vm_fault_t do_huge_pmd_device_private(struct vm_fault *vmf)
--
mm/huge_memory.c-1453- if (vmf->flags & FAULT_FLAG_VMA_LOCK) {
mm/huge_memory.c:1454: vma_end_read(vma);
mm/huge_memory.c-1455- return VM_FAULT_RETRY;
--
mm/hugetlb.c=5711=static vm_fault_t hugetlb_no_page(struct address_space *mapping,
--
mm/hugetlb.c-5919- if (unlikely(ret & VM_FAULT_RETRY))
mm/hugetlb.c:5920: vma_end_read(vma);
mm/hugetlb.c-5921-
--
mm/hugetlb.c=5961=vm_fault_t hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma,
--
mm/hugetlb.c-6131- if (unlikely(ret & VM_FAULT_RETRY))
mm/hugetlb.c:6132: vma_end_read(vma);
mm/hugetlb.c-6133-
--
mm/internal.h=502=static inline vm_fault_t vmf_anon_prepare(struct vm_fault *vmf)
--
mm/internal.h-506- if (unlikely(ret & VM_FAULT_RETRY))
mm/internal.h:507: vma_end_read(vmf->vma);
mm/internal.h-508- return ret;
--
mm/madvise.c=1628=static bool try_vma_read_lock(struct madvise_behavior *madv_behavior)
--
mm/madvise.c-1637- if (!is_vma_lock_sufficient(vma, madv_behavior)) {
mm/madvise.c:1638: vma_end_read(vma);
mm/madvise.c-1639- goto take_mmap_read_lock;
--
mm/madvise.c=1661=int madvise_walk_vmas(struct madvise_behavior *madv_behavior)
--
mm/madvise.c-1677- error = madvise_vma_behavior(madv_behavior);
mm/madvise.c:1678: vma_end_read(madv_behavior->vma);
mm/madvise.c-1679- return error;
--
mm/memory.c=3692=static inline vm_fault_t vmf_can_call_fault(const struct vm_fault *vmf)
--
mm/memory.c-3697- return 0;
mm/memory.c:3698: vma_end_read(vma);
mm/memory.c-3699- return VM_FAULT_RETRY;
--
mm/memory.c=4694=vm_fault_t do_swap_page(struct vm_fault *vmf)
--
mm/memory.c-4726- */
mm/memory.c:4727: vma_end_read(vma);
mm/memory.c-4728- ret = VM_FAULT_RETRY;
--
mm/mmap_lock.c=296=struct vm_area_struct *lock_vma_under_rcu(struct mm_struct *mm,
--
mm/mmap_lock.c-332- if (unlikely(address < vma->vm_start || address >= vma->vm_end)) {
mm/mmap_lock.c:333: vma_end_read(vma);
mm/mmap_lock.c-334- goto inval;
--
mm/mmap_lock.c=369=struct vm_area_struct *lock_next_vma(struct mm_struct *mm,
--
mm/mmap_lock.c-424- rcu_read_unlock();
mm/mmap_lock.c:425: vma_end_read(vma);
mm/mmap_lock.c-426-fallback:
--
mm/userfaultfd.c=118=static struct vm_area_struct *uffd_lock_vma(struct mm_struct *mm,
--
mm/userfaultfd.c-129- if (!(vma->vm_flags & VM_SHARED) && unlikely(!vma->anon_vma))
mm/userfaultfd.c:130: vma_end_read(vma);
mm/userfaultfd.c-131- else
--
mm/userfaultfd.c=148=static struct vm_area_struct *uffd_mfill_lock(struct mm_struct *dst_mm,
--
mm/userfaultfd.c-157-
mm/userfaultfd.c:158: vma_end_read(dst_vma);
mm/userfaultfd.c-159- return ERR_PTR(-ENOENT);
--
mm/userfaultfd.c=162=static void uffd_mfill_unlock(struct vm_area_struct *vma)
mm/userfaultfd.c-163-{
mm/userfaultfd.c:164: vma_end_read(vma);
mm/userfaultfd.c-165-}
--
mm/userfaultfd.c=1678=static int uffd_move_lock(struct mm_struct *mm,
--
mm/userfaultfd.c-1717- /* Undo any locking and retry in mmap_lock critical section */
mm/userfaultfd.c:1718: vma_end_read(*dst_vmap);
mm/userfaultfd.c-1719-
--
mm/userfaultfd.c-1735- /* Undo dst_vmap locking if src_vmap failed to lock */
mm/userfaultfd.c:1736: vma_end_read(*dst_vmap);
mm/userfaultfd.c-1737- err = -EAGAIN;
--
mm/userfaultfd.c=1744=static void uffd_move_unlock(struct vm_area_struct *dst_vma,
--
mm/userfaultfd.c-1746-{
mm/userfaultfd.c:1747: vma_end_read(src_vma);
mm/userfaultfd.c-1748- if (src_vma != dst_vma)
mm/userfaultfd.c:1749: vma_end_read(dst_vma);
mm/userfaultfd.c-1750-}
--
net/ipv4/tcp.c=2183=static struct vm_area_struct *find_tcp_vma(struct mm_struct *mm,
--
net/ipv4/tcp.c-2190- if (vma->vm_ops != &tcp_vm_ops) {
net/ipv4/tcp.c:2191: vma_end_read(vma);
net/ipv4/tcp.c-2192- return NULL;
--
net/ipv4/tcp.c=2209=static int tcp_zerocopy_receive(struct sock *sk,
--
net/ipv4/tcp.c-2334- else
net/ipv4/tcp.c:2335: vma_end_read(vma);
net/ipv4/tcp.c-2336- /* Try to copy straggler data. */
--
rust/helpers/mm.c=42=rust_helper_vma_lookup(struct mm_struct *mm, unsigned long addr)
--
rust/helpers/mm.c-46-
rust/helpers/mm.c:47:__rust_helper void rust_helper_vma_end_read(struct vm_area_struct *vma)
rust/helpers/mm.c-48-{
rust/helpers/mm.c:49: vma_end_read(vma);
rust/helpers/mm.c-50-}
--
rust/kernel/mm.rs=275=pub struct VmaReadGuard<'a> {
rust/kernel/mm.rs-276- vma: &'a VmaRef,
rust/kernel/mm.rs:277: // `vma_end_read` must be called on the same thread as where the lock was taken
rust/kernel/mm.rs-278- _nts: NotThreadSafe,
--
rust/kernel/mm.rs=293= fn drop(&mut self) {
rust/kernel/mm.rs-294- // SAFETY: We hold the read lock by the type invariants.
rust/kernel/mm.rs:295: unsafe { bindings::vma_end_read(self.vma.as_ptr()) };
rust/kernel/mm.rs-296- }
]
|
| 72/2 |
2026/05/30 11:35 |
llm |
expert |
0m
Model:
Tokens:
input: 71594
output: 38
thoughts: 42
Thoughts: Okay, here's my interpretation of those thoughts, summarized in the first person, assuming the context is kernel memory management and I'm an expert in the field:
**VMA Lock Management: The Key Detail**
Right, let me see... I'm looking at this code and it's calling `vma_end_read`. Ah, yes, that's the one. That particular function is critical because it's responsible for releasing the per-VMA read lock. It's easy to overlook these details sometimes, especially when you're deeply immersed in a complex flow. So, the kernel's handling of the VMA read lock is important to keep in mind! Hmm, where is this all actually defined? Oh, yes, of course, it's defined in `include/linux/mmap_lock.h`. Got it. Good to be reminded where those critical functions are defined. Always good to refresh the memory with such details.
|
| 73/2 |
2026/05/30 11:35 |
tool |
read-file |
0m
Args: map[File:include/linux/mmap_lock.h FirstLine:257 LineCount:20]
Results: map[Contents: 257: static inline bool vma_start_read_locked(struct vm_area_struct *vma)
258: {
259: return vma_start_read_locked_nested(vma, 0);
260: }
261:
262: static inline void vma_end_read(struct vm_area_struct *vma)
263: {
264: vma_refcount_put(vma);
265: }
266:
267: static inline unsigned int __vma_raw_mm_seqnum(struct vm_area_struct *vma)
268: {
269: const struct mm_struct *mm = vma->vm_mm;
270:
271: /* We must hold an exclusive write lock for this access to be valid. */
272: mmap_assert_write_locked(vma->vm_mm);
273: return mm->mm_lock_seq.sequence;
274: }
275:
276: /*
]
|
| 74/2 |
2026/05/30 11:35 |
llm |
expert |
0m
Model:
Tokens:
input: 72190
output: 21
thoughts: 39
Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:
**Analyzing the Memory Management Flow**
Right, let me just parse this code. *vma_end_read*... ah, that’s where the read operation on the virtual memory area concludes. The immediate concern is that this function calls `vma_refcount_put(vma)`. Okay, that's not unexpected. We are releasing our hold on the memory mapping.
But hang on a second. *What exactly does* `vma_refcount_put` *do, within the scope of our memory management regime?* I need to re-familiarize myself with the exact mechanics here. Does this imply a simple decrement of a reference counter? Or does it potentially trigger deallocation or some other form of VMA lifecycle management? Could it involve flushing pages, or even relinquishing the VMA entirely if its reference count drops to zero? I need to quickly recall the implications of this particular put operation to ensure there aren't any potential race conditions with any concurrent VMAs or other processes accessing the memory region. Specifically, I'm thinking about the implications for coherency and how this could potentially trigger page fault handling or memory allocation/deallocation elsewhere within the kernel. It’s critical to understand the precise interaction here for our current performance optimization and potential concurrency bugs. Let me double-check the definition and its callers.
|
| 75/2 |
2026/05/30 11:35 |
tool |
grepper |
0m
Args: map[Expression:vma_refcount_put]
Results: map[Output:include/linux/mmap_lock.h=191=static inline __must_check unsigned int
include/linux/mmap_lock.h:192:__vma_refcount_put_return(struct vm_area_struct *vma)
include/linux/mmap_lock.h-193-{
--
include/linux/mmap_lock.h-202-/**
include/linux/mmap_lock.h:203: * vma_refcount_put() - Drop reference count in VMA vm_refcnt field due to a
include/linux/mmap_lock.h-204- * read-lock being dropped.
--
include/linux/mmap_lock.h-209- */
include/linux/mmap_lock.h:210:static inline void vma_refcount_put(struct vm_area_struct *vma)
include/linux/mmap_lock.h-211-{
--
include/linux/mmap_lock.h-216- __vma_lockdep_release_read(vma);
include/linux/mmap_lock.h:217: newcnt = __vma_refcount_put_return(vma);
include/linux/mmap_lock.h-218-
--
include/linux/mmap_lock.h=262=static inline void vma_end_read(struct vm_area_struct *vma)
include/linux/mmap_lock.h-263-{
include/linux/mmap_lock.h:264: vma_refcount_put(vma);
include/linux/mmap_lock.h-265-}
--
include/linux/mmap_lock.h=452=static inline void vma_mark_detached(struct vm_area_struct *vma)
--
include/linux/mmap_lock.h-466- */
include/linux/mmap_lock.h:467: if (likely(!__vma_refcount_put_return(vma)))
include/linux/mmap_lock.h-468- return;
--
mm/mmap_lock.c=76=static unsigned int get_target_refcnt(struct vma_exclude_readers_state *ves)
--
mm/mmap_lock.c-86- *
mm/mmap_lock.c:87: * Note that this function pairs with vma_refcount_put() which will wake up this
mm/mmap_lock.c-88- * thread when it detects that the last reader has released its lock.
--
mm/mmap_lock.c=212=static inline struct vm_area_struct *vma_start_read(struct mm_struct *mm,
--
mm/mmap_lock.c-262- if (unlikely(vma->vm_lock_seq == raw_read_seqcount(&mm->mm_lock_seq))) {
mm/mmap_lock.c:263: vma_refcount_put(vma);
mm/mmap_lock.c-264- vma = NULL;
--
mm/mmap_lock.c-284- mmgrab(other_mm);
mm/mmap_lock.c:285: vma_refcount_put(vma);
mm/mmap_lock.c-286- mmdrop(other_mm);
--
tools/testing/vma/include/dup.h=1012=static inline void vma_mark_detached(struct vm_area_struct *vma)
--
tools/testing/vma/include/dup.h-1015- vma_assert_attached(vma);
tools/testing/vma/include/dup.h:1016: /* We are the only writer, so no need to use vma_refcount_put(). */
tools/testing/vma/include/dup.h-1017- if (unlikely(!refcount_dec_and_test(&vma->vm_refcnt))) {
]
|
| 76/2 |
2026/05/30 11:35 |
llm |
expert |
0m
Model:
Tokens:
input: 73562
output: 35
thoughts: 118
Thoughts: **VMA Reference Count Conundrum**
Okay, wait a minute. `vma_refcount_put` *decrements* the `vma->vm_refcnt`. And, hold on... if `vma_end_read` isn't called, then *the reference count is leaked*! This is bad. Really bad. I need to think this through. If the `vm_refcnt` is leaked... what actually happens downstream? What are the consequences? I know `vma_start_write` is called during operations like `munmap`, and *that* function waits for the `vma->vm_refcnt` to hit zero. This makes it a critical point. Right. Time to dive into `vma_start_write` and see exactly what's going on there.
|
| 77/2 |
2026/05/30 11:35 |
tool |
read-file |
0m
Args: map[File:mm/mmap_lock.c FirstLine:76 LineCount:50]
Results: map[Contents: 76: static unsigned int get_target_refcnt(struct vma_exclude_readers_state *ves)
77: {
78: const unsigned int tgt = ves->detaching ? 0 : 1;
79:
80: return tgt | VM_REFCNT_EXCLUDE_READERS_FLAG;
81: }
82:
83: /*
84: * Mark the VMA as being in a state of excluding readers, check to see if any
85: * VMA read locks are indeed held, and if so wait for them to be released.
86: *
87: * Note that this function pairs with vma_refcount_put() which will wake up this
88: * thread when it detects that the last reader has released its lock.
89: *
90: * The ves->state parameter ought to be set to TASK_UNINTERRUPTIBLE in cases
91: * where we wish the thread to sleep uninterruptibly or TASK_KILLABLE if a fatal
92: * signal is permitted to kill it.
93: *
94: * The function sets the ves->exclusive parameter to true if readers were
95: * excluded, or false if the VMA was detached or an error arose on wait.
96: *
97: * If the function indicates an exclusive lock was acquired via ves->exclusive
98: * the caller is required to invoke __vma_end_exclude_readers() once the
99: * exclusive state is no longer required.
100: *
101: * If ves->state is set to something other than TASK_UNINTERRUPTIBLE, the
102: * function may also return -EINTR to indicate a fatal signal was received while
103: * waiting. Otherwise, the function returns 0.
104: */
105: static int __vma_start_exclude_readers(struct vma_exclude_readers_state *ves)
106: {
107: struct vm_area_struct *vma = ves->vma;
108: unsigned int tgt_refcnt = get_target_refcnt(ves);
109: int err = 0;
110:
111: mmap_assert_write_locked(vma->vm_mm);
112:
113: /*
114: * If vma is detached then only vma_mark_attached() can raise the
115: * vm_refcnt. mmap_write_lock prevents racing with vma_mark_attached().
116: *
117: * See the comment describing the vm_area_struct->vm_refcnt field for
118: * details of possible refcnt values.
119: */
120: if (!refcount_add_not_zero(VM_REFCNT_EXCLUDE_READERS_FLAG, &vma->vm_refcnt)) {
121: ves->detached = true;
122: return 0;
123: }
124:
125: __vma_lockdep_acquire_exclusive(vma);
]
|
| 78/2 |
2026/05/30 11:35 |
llm |
expert |
0m
Model:
Tokens:
input: 74737
output: 36
thoughts: 33
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Analyzing the Exclusion Mechanism in Virtual Memory Management**
Alright, let's unpack this... so, I'm digging into `__vma_start_exclude_readers`. The key here is the synchronization it provides, specifically its dependence on `vma->vm_refcnt`. I'm assuming we're dealing with a `vma` (Virtual Memory Area) that needs to be temporarily protected, likely for some sort of modification. What I find interesting is this wait-for-condition logic. It's not a simple spinlock or mutex; it's a *wait* where the function actively waits for `vma->vm_refcnt` to decrease to a specific target count, `tgt_refcnt`.
Therefore, the point of this code segment is to make sure any readers currently accessing the `vma` have finished their work. It sounds like `tgt_refcnt` is likely the original refcount of the `vma` before the exclusion period begins. This exclusion mechanism is clever since this waiting-for-readers is probably required before safely altering the memory region. It probably uses a more complex mechanism which can efficiently manage reads and writes with less contention than simpler alternatives.
|
| 79/2 |
2026/05/30 11:35 |
tool |
read-file |
0m
Args: map[File:mm/mmap_lock.c FirstLine:126 LineCount:50]
Results: map[Contents: 126: err = rcuwait_wait_event(&vma->vm_mm->vma_writer_wait,
127: refcount_read(&vma->vm_refcnt) == tgt_refcnt,
128: ves->state);
129: if (err) {
130: __vma_end_exclude_readers(ves);
131: return err;
132: }
133:
134: __vma_lockdep_stat_mark_acquired(vma);
135: ves->exclusive = true;
136: return 0;
137: }
138:
139: int __vma_start_write(struct vm_area_struct *vma, int state)
140: {
141: const unsigned int mm_lock_seq = __vma_raw_mm_seqnum(vma);
142: struct vma_exclude_readers_state ves = {
143: .vma = vma,
144: .state = state,
145: };
146: int err;
147:
148: err = __vma_start_exclude_readers(&ves);
149: if (err) {
150: WARN_ON_ONCE(ves.detached);
151: return err;
152: }
153:
154: /*
155: * We should use WRITE_ONCE() here because we can have concurrent reads
156: * from the early lockless pessimistic check in vma_start_read().
157: * We don't really care about the correctness of that early check, but
158: * we should use WRITE_ONCE() for cleanliness and to keep KCSAN happy.
159: */
160: WRITE_ONCE(vma->vm_lock_seq, mm_lock_seq);
161:
162: if (ves.exclusive) {
163: __vma_end_exclude_readers(&ves);
164: /* VMA should remain attached. */
165: WARN_ON_ONCE(ves.detached);
166: }
167:
168: return 0;
169: }
170: EXPORT_SYMBOL_GPL(__vma_start_write);
171:
172: void __vma_exclude_readers_for_detach(struct vm_area_struct *vma)
173: {
174: struct vma_exclude_readers_state ves = {
175: .vma = vma,
]
|
| 80/2 |
2026/05/30 11:35 |
llm |
expert |
0m
Model:
Tokens:
input: 75840
output: 23
thoughts: 254
Thoughts: **The Dreaded Infinite Wait**
Okay, so I'm looking at this code and it's hitting me all at once. `__vma_start_exclude_readers` calls `rcuwait_wait_event`. That, in turn, is waiting for a specific value of `vma->vm_refcnt`. And that wait is predicated on the assumption that `vma_end_read` *will* be called, eventually, to decrement that refcount. If `vma_end_read` never gets called... game over. `vma->vm_refcnt` leaks and `__vma_start_exclude_readers` is stuck. Forever. This is bad.
And it gets worse! I'm seeing that `__vma_start_exclude_readers` is called by `__vma_start_write`, which in turn is called by `vma_start_write`. And `vma_start_write` is right there in the heart of `munmap`, `mremap`, any VMA modification! What's worse, `vma_start_write` is called while holding `mmap_write_lock`!
So, put it all together and the `munmap` thread, if it gets into this state, could block *indefinitely* in `vma_start_write`. Infinite wait, deadlock... all of those nasty things.
Wait a second. Isn't `vma_start_write` called *before* `userfaultfd_unmap_prep`? That could be important. Okay, time to dive deeper and verify the execution path. Let's see what `do_vmi_align_munmap` is doing... there could be something hiding in there...
|
| 81/2 |
2026/05/30 11:35 |
tool |
grepper |
0m
Error:
Args: map[Expression:vma_start_write.*munmap]
Results:
|
| 82/2 |
2026/05/30 11:35 |
llm |
expert |
0m
Model:
Tokens:
input: 76509
output: 20
thoughts: 21
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `vma_start_write`**
Okay, so I need to understand what's happening with this write process. Let's start with `vma_start_write`, which I remember is in `mm/mmap.c`. That's where the virtual memory area (VMA) management code lives, right? Makes sense. I'm guessing this function is involved in initiating a write operation to a specific VMA. Probably some checks, possibly locking, setting some internal flags, and maybe even calling into the underlying page fault or copy-on-write mechanisms depending on the situation. I need to figure out what are the edge cases for this function. Does it handle anonymous memory, file-backed mappings, shared memory, COW pages, etc. and what are the corner cases of these different mapping types? I expect to see checks for things like write permissions, VMAs overlaps and maybe signals and page faults. I want to see how these checks are done to understand how they can be triggered.
|
| 83/2 |
2026/05/30 11:35 |
tool |
grepper |
0m
Args: map[Expression:vma_start_write]
Results: map[Output:Documentation/mm/process_addrs.rst=42=Terminology
--
Documentation/mm/process_addrs.rst-50- :c:func:`!lock_vma_under_rcu` (and unlocked via :c:func:`!vma_end_read`) and a
Documentation/mm/process_addrs.rst:51: write lock via vma_start_write() or vma_start_write_killable()
Documentation/mm/process_addrs.rst-52- (all VMA write locks are unlocked
--
Documentation/mm/process_addrs.rst=89=field (we explore each VMA field in detail below). For the majority you must:
--
Documentation/mm/process_addrs.rst-93- you're done with the VMA, *and*
Documentation/mm/process_addrs.rst:94:* Obtain a VMA write lock via :c:func:`!vma_start_write` for each VMA you wish to
Documentation/mm/process_addrs.rst-95- modify, which will be released automatically when :c:func:`!mmap_write_unlock` is
--
Documentation/mm/process_addrs.rst=415=ordering of locks within memory management code:
--
Documentation/mm/process_addrs.rst-423- hugetlbfs_i_mmap_rwsem_key (in huge_pmd_share, see hugetlbfs below)
Documentation/mm/process_addrs.rst:424: vma_start_write
Documentation/mm/process_addrs.rst-425- mapping->i_mmap_rwsem
--
Documentation/mm/process_addrs.rst=768=duration and the caller of :c:func:`!lock_vma_under_rcu` must drop it via
--
Documentation/mm/process_addrs.rst-770-
Documentation/mm/process_addrs.rst:771:VMA **write** locks are acquired via :c:func:`!vma_start_write` in instances where a
Documentation/mm/process_addrs.rst-772-VMA is about to be modified, unlike :c:func:`!vma_start_read` the lock is always
--
Documentation/mm/process_addrs.rst=803=Writing requires the mmap to be write-locked and the VMA lock to be acquired via
Documentation/mm/process_addrs.rst:804::c:func:`!vma_start_write`, however the write lock is released by the termination or
Documentation/mm/process_addrs.rst-805-downgrade of the mmap write lock so no :c:func:`!vma_end_write` is required.
--
Documentation/mm/process_addrs.rst=845=the mm. During this entire operation mmap write lock is held.
Documentation/mm/process_addrs.rst-846-
Documentation/mm/process_addrs.rst:847:This way, if any read locks are in effect, :c:func:`!vma_start_write` will sleep
Documentation/mm/process_addrs.rst-848-until these are finished and mutual exclusion is achieved.
--
Documentation/mm/process_addrs.rst=908=Stack expansion throws up additional complexities in that we cannot permit there
Documentation/mm/process_addrs.rst:909:to be racing page faults, as a result we invoke :c:func:`!vma_start_write` to
Documentation/mm/process_addrs.rst-910-prevent this in :c:func:`!expand_downwards` or :c:func:`!expand_upwards`.
--
arch/powerpc/kvm/book3s_hv_uvmem.c=391=static int kvmppc_memslot_page_merge(struct kvm *kvm,
--
arch/powerpc/kvm/book3s_hv_uvmem.c-412- }
arch/powerpc/kvm/book3s_hv_uvmem.c:413: vma_start_write(vma);
arch/powerpc/kvm/book3s_hv_uvmem.c-414- /* Copy vm_flags to avoid partial modifications in ksm_madvise */
--
include/linux/mm.h=960=static inline void vm_flags_set(struct vm_area_struct *vma,
--
include/linux/mm.h-962-{
include/linux/mm.h:963: vma_start_write(vma);
include/linux/mm.h-964- vma_flags_set_word(&vma->flags, flags);
--
include/linux/mm.h=967=static inline void vm_flags_clear(struct vm_area_struct *vma,
--
include/linux/mm.h-970- VM_WARN_ON_ONCE(!pgtable_supports_soft_dirty() && (flags & VM_SOFTDIRTY));
include/linux/mm.h:971: vma_start_write(vma);
include/linux/mm.h-972- vma_flags_clear_word(&vma->flags, flags);
--
include/linux/mm.h=989=static inline void vm_flags_mod(struct vm_area_struct *vma,
--
include/linux/mm.h-991-{
include/linux/mm.h:992: vma_start_write(vma);
include/linux/mm.h-993- __vm_flags_mod(vma, set, clear);
--
include/linux/mmap_lock.h=282=static inline bool __is_vma_write_locked(struct vm_area_struct *vma)
--
include/linux/mmap_lock.h-290-
include/linux/mmap_lock.h:291:int __vma_start_write(struct vm_area_struct *vma, int state);
include/linux/mmap_lock.h-292-
--
include/linux/mmap_lock.h-297- */
include/linux/mmap_lock.h:298:static inline void vma_start_write(struct vm_area_struct *vma)
include/linux/mmap_lock.h-299-{
--
include/linux/mmap_lock.h-302-
include/linux/mmap_lock.h:303: __vma_start_write(vma, TASK_UNINTERRUPTIBLE);
include/linux/mmap_lock.h-304-}
--
include/linux/mmap_lock.h-306-/**
include/linux/mmap_lock.h:307: * vma_start_write_killable - Begin writing to a VMA.
include/linux/mmap_lock.h-308- * @vma: The VMA we are going to modify.
--
include/linux/mmap_lock.h=319=static inline __must_check
include/linux/mmap_lock.h:320:int vma_start_write_killable(struct vm_area_struct *vma)
include/linux/mmap_lock.h-321-{
--
include/linux/mmap_lock.h-324-
include/linux/mmap_lock.h:325: return __vma_start_write(vma, TASK_KILLABLE);
include/linux/mmap_lock.h-326-}
--
include/linux/mmap_lock.h=503=static inline void vma_end_read(struct vm_area_struct *vma) {}
include/linux/mmap_lock.h:504:static inline void vma_start_write(struct vm_area_struct *vma) {}
include/linux/mmap_lock.h-505-static inline __must_check
include/linux/mmap_lock.h:506:int vma_start_write_killable(struct vm_area_struct *vma) { return 0; }
include/linux/mmap_lock.h-507-static inline void vma_assert_write_locked(struct vm_area_struct *vma)
--
mm/khugepaged.c=1092=static enum scan_result collapse_huge_page(struct mm_struct *mm, unsigned long address,
--
mm/khugepaged.c-1157- /* check if the pmd is still valid */
mm/khugepaged.c:1158: vma_start_write(vma);
mm/khugepaged.c-1159- result = check_pmd_still_valid(mm, address, pmd);
--
mm/madvise.c=150=static int madvise_update_vma(vm_flags_t new_flags,
--
mm/madvise.c-175- /* vm_flags is protected by the mmap_lock held in write mode. */
mm/madvise.c:176: vma_start_write(vma);
mm/madvise.c-177- vm_flags_reset(vma, new_flags);
--
mm/memory.c=373=void free_pgtables(struct mmu_gather *tlb, struct unmap_desc *unmap)
--
mm/memory.c-399- if (unmap->mm_wr_locked)
mm/memory.c:400: vma_start_write(vma);
mm/memory.c-401- unlink_anon_vmas(vma);
--
mm/memory.c-412- if (unmap->mm_wr_locked)
mm/memory.c:413: vma_start_write(vma);
mm/memory.c-414- unlink_anon_vmas(vma);
--
mm/mempolicy.c=558=void mpol_rebind_mm(struct mm_struct *mm, nodemask_t *new)
--
mm/mempolicy.c-564- for_each_vma(vmi, vma) {
mm/mempolicy.c:565: vma_start_write(vma);
mm/mempolicy.c-566- mpol_rebind_policy(vma->vm_policy, new);
--
mm/mempolicy.c=1754=SYSCALL_DEFINE4(set_mempolicy_home_node, unsigned long, start, unsigned long, len,
--
mm/mempolicy.c-1809-
mm/mempolicy.c:1810: vma_start_write(vma);
mm/mempolicy.c-1811- new->home_node = home_node;
--
mm/mlock.c=423=static void mlock_vma_pages_range(struct vm_area_struct *vma,
--
mm/mlock.c-443- newflags |= VM_IO;
mm/mlock.c:444: vma_start_write(vma);
mm/mlock.c-445- vm_flags_reset_once(vma, newflags);
--
mm/mlock.c=466=static int mlock_fixup(struct vma_iterator *vmi, struct vm_area_struct *vma,
--
mm/mlock.c-505- /* No work to do, and mlocking twice would be wrong */
mm/mlock.c:506: vma_start_write(vma);
mm/mlock.c-507- vm_flags_reset(vma, newflags);
--
mm/mmap.c=1732=__latent_entropy int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
--
mm/mmap.c-1765-
mm/mmap.c:1766: retval = vma_start_write_killable(mpnt);
mm/mmap.c-1767- if (retval < 0)
--
mm/mmap_lock.c=105=static int __vma_start_exclude_readers(struct vma_exclude_readers_state *ves)
--
mm/mmap_lock.c-138-
mm/mmap_lock.c:139:int __vma_start_write(struct vm_area_struct *vma, int state)
mm/mmap_lock.c-140-{
--
mm/mmap_lock.c-169-}
mm/mmap_lock.c:170:EXPORT_SYMBOL_GPL(__vma_start_write);
mm/mmap_lock.c-171-
--
mm/mprotect.c=695=mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
--
mm/mprotect.c-767- */
mm/mprotect.c:768: vma_start_write(vma);
mm/mprotect.c-769- vm_flags_reset_once(vma, newflags);
--
mm/mremap.c=1340=static unsigned long move_vma(struct vma_remap_struct *vrm)
--
mm/mremap.c-1358- /* We don't want racing faults. */
mm/mremap.c:1359: vma_start_write(vrm->vma);
mm/mremap.c-1360-
--
mm/pagewalk.c=427=static inline void process_vma_walk_lock(struct vm_area_struct *vma,
--
mm/pagewalk.c-432- case PGWALK_WRLOCK:
mm/pagewalk.c:433: vma_start_write(vma);
mm/pagewalk.c-434- break;
--
mm/rmap.c-27- * hugetlbfs_i_mmap_rwsem_key (in huge_pmd_share, see hugetlbfs below)
mm/rmap.c:28: * vma_start_write
mm/rmap.c-29- * mapping->i_mmap_rwsem
--
mm/userfaultfd.c=1678=static int uffd_move_lock(struct mm_struct *mm,
--
mm/userfaultfd.c-1707- * mmap_write_lock(mm)
mm/userfaultfd.c:1708: * vma_start_write(src_vma)
mm/userfaultfd.c-1709- * vma_start_read(src_vma)
mm/userfaultfd.c-1710- * mmap_read_lock(mm)
mm/userfaultfd.c:1711: * vma_start_write(dst_vma)
mm/userfaultfd.c-1712- */
--
mm/userfaultfd.c=2074=static void userfaultfd_set_ctx(struct vm_area_struct *vma,
--
mm/userfaultfd.c-2077-{
mm/userfaultfd.c:2078: vma_start_write(vma);
mm/userfaultfd.c-2079- vma->vm_userfaultfd_ctx = (struct vm_userfaultfd_ctx){ctx};
--
mm/vma.c=497=__split_vma(struct vma_iterator *vmi, struct vm_area_struct *vma,
--
mm/vma.c-542-
mm/vma.c:543: vma_start_write(vma);
mm/vma.c:544: vma_start_write(new);
mm/vma.c-545-
--
mm/vma.c=805=static __must_check struct vm_area_struct *vma_merge_existing_range(
--
mm/vma.c-897- /* No matter what happens, we will be adjusting middle. */
mm/vma.c:898: vma_start_write(middle);
mm/vma.c-899-
mm/vma.c-900- if (merge_right) {
mm/vma.c:901: vma_start_write(next);
mm/vma.c-902- vmg->target = next;
--
mm/vma.c-906- if (merge_left) {
mm/vma.c:907: vma_start_write(prev);
mm/vma.c-908- vmg->target = prev;
--
mm/vma.c=1151=int vma_expand(struct vma_merge_struct *vmg)
--
mm/vma.c-1160- mmap_assert_write_locked(vmg->mm);
mm/vma.c:1161: vma_start_write(target);
mm/vma.c-1162-
--
mm/vma.c-1196- if (remove_next) {
mm/vma.c:1197: vma_start_write(next);
mm/vma.c-1198- vmg->__remove_next = true;
--
mm/vma.c=1228=int vma_shrink(struct vma_iterator *vmi, struct vm_area_struct *vma,
--
mm/vma.c-1242-
mm/vma.c:1243: vma_start_write(vma);
mm/vma.c-1244-
--
mm/vma.c=1379=static int vms_gather_munmap_vmas(struct vma_munmap_struct *vms,
--
mm/vma.c-1432- }
mm/vma.c:1433: vma_start_write(next);
mm/vma.c-1434- mas_set(mas_detach, vms->vma_count++);
--
mm/vma.c=1824=static int vma_link(struct mm_struct *mm, struct vm_area_struct *vma)
--
mm/vma.c-1831-
mm/vma.c:1832: vma_start_write(vma);
mm/vma.c-1833- vma_iter_store_new(&vmi, vma);
--
mm/vma.c=2197=int mm_take_all_locks(struct mm_struct *mm)
--
mm/vma.c-2207- /*
mm/vma.c:2208: * vma_start_write() does not have a complement in mm_drop_all_locks()
mm/vma.c:2209: * because vma_start_write() is always asymmetrical; it marks a VMA as
mm/vma.c-2210- * being written to until mmap_write_unlock() or mmap_write_downgrade()
--
mm/vma.c-2215- goto out_unlock;
mm/vma.c:2216: vma_start_write(vma);
mm/vma.c-2217- }
--
mm/vma.c=2506=static int __mmap_new_vma(struct mmap_state *map, struct vm_area_struct **vmap)
--
mm/vma.c-2551- /* Lock the VMA since it is modified after insertion into VMA tree */
mm/vma.c:2552: vma_start_write(vma);
mm/vma.c-2553- vma_iter_store_new(vmi, vma);
--
mm/vma.c=2857=int do_brk_flags(struct vma_iterator *vmi, struct vm_area_struct *vma,
--
mm/vma.c-2904- vma->vm_page_prot = vm_get_page_prot(vm_flags);
mm/vma.c:2905: vma_start_write(vma);
mm/vma.c-2906- if (vma_iter_store_gfp(vmi, vma, GFP_KERNEL))
--
mm/vma.c=3081=int expand_upwards(struct vm_area_struct *vma, unsigned long address)
--
mm/vma.c-3127- /* Lock the VMA before expanding to prevent concurrent page faults */
mm/vma.c:3128: vma_start_write(vma);
mm/vma.c-3129- /* We update the anon VMA tree. */
--
mm/vma.c=3167=int expand_downwards(struct vm_area_struct *vma, unsigned long address)
--
mm/vma.c-3206- /* Lock the VMA before expanding to prevent concurrent page faults */
mm/vma.c:3207: vma_start_write(vma);
mm/vma.c-3208- /* We update the anon VMA tree. */
--
tools/testing/vma/include/custom.h=43=static inline void unlink_anon_vmas(struct vm_area_struct *vma)
--
tools/testing/vma/include/custom.h-48-
tools/testing/vma/include/custom.h:49:static inline void vma_start_write(struct vm_area_struct *vma)
tools/testing/vma/include/custom.h-50-{
--
tools/testing/vma/include/custom.h=55=static inline __must_check
tools/testing/vma/include/custom.h:56:int vma_start_write_killable(struct vm_area_struct *vma)
tools/testing/vma/include/custom.h-57-{
--
tools/testing/vma/include/dup.h=6=struct vm_area_struct;
tools/testing/vma/include/dup.h:7:static inline void vma_start_write(struct vm_area_struct *vma);
tools/testing/vma/include/dup.h-8-
--
tools/testing/vma/include/dup.h=831=static inline void vm_flags_set(struct vm_area_struct *vma,
--
tools/testing/vma/include/dup.h-833-{
tools/testing/vma/include/dup.h:834: vma_start_write(vma);
tools/testing/vma/include/dup.h-835- vma_flags_set_word(&vma->flags, flags);
--
tools/testing/vma/include/dup.h=838=static inline void vm_flags_clear(struct vm_area_struct *vma,
--
tools/testing/vma/include/dup.h-840-{
tools/testing/vma/include/dup.h:841: vma_start_write(vma);
tools/testing/vma/include/dup.h-842- vma_flags_clear_word(&vma->flags, flags);
--
tools/testing/vma/shared.c=88=bool vma_write_started(struct vm_area_struct *vma)
--
tools/testing/vma/shared.c-94-
tools/testing/vma/shared.c:95: /* The vma_start_write() stub simply increments this value. */
tools/testing/vma/shared.c-96- return seq > -1;
--
tools/testing/vma/shared.h=99=int cleanup_mm(struct mm_struct *mm, struct vma_iterator *vmi);
tools/testing/vma/shared.h-100-
tools/testing/vma/shared.h:101:/* Helper function to determine if VMA has had vma_start_write() performed. */
tools/testing/vma/shared.h-102-bool vma_write_started(struct vm_area_struct *vma);
]
|
| 84/2 |
2026/05/30 11:36 |
llm |
expert |
0m
Model:
Tokens:
input: 82925
output: 32
thoughts: 134
Thoughts: Okay, here's my understanding, breaking down the control flow.
**Control Flow in Munmap Operations**
Right, so I'm seeing a crucial sequence here. `vms_gather_munmap_vmas` definitely calls `vma_start_write(next)`. No surprise there, that's what I'd expect for modifications to the VMAs. Now, how does this fit into the larger picture? Okay, `vms_gather_munmap_vmas` is invoked from `do_vmi_align_munmap`. And `do_vmi_align_munmap` itself gets called from the function `do_vmi_munmap`.
Crucially, the code is structured such that `userfaultfd_unmap_prep` is called *after* `vms_gather_munmap_vmas` within the `do_vmi_align_munmap` function. My immediate thought is, I need to understand this `do_vmi_align_munmap` function better. That's the key to understanding the interplay of these routines and any potential race conditions or synchronization issues. Let's delve into that function, the order of these operations has me thinking.
|
| 85/2 |
2026/05/30 11:36 |
tool |
grepper |
0m
Error:
Args: map[Expression:userfaultfd_unmap_prep.*do_vmi_align_munmap]
Results:
|
| 86/2 |
2026/05/30 11:36 |
llm |
expert |
0m
Model:
Tokens:
input: 83366
output: 22
thoughts: 33
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm the expert and considering my own internal monologue:
**Investigating Memory Unmapping Alignment in the Kernel**
Alright, I'm thinking about `do_vmi_align_munmap`. That function is probably the heart of how we handle unmapping virtual memory areas, particularly when alignment plays a role. I need to figure out where it's defined.
Given that it's related to memory management, the logical places to start looking are either `mm/vma.c`, since that's where a lot of the Virtual Memory Area structure manipulation happens, or possibly `mm/mmap.c`, because mmap is how we establish these virtual memory regions in the first place. I'm leaning toward checking `mm/vma.c` first, but I'll keep `mm/mmap.c` in mind as a secondary location if I don't find what I'm looking for initially. I'll need to understand the alignment logic within that function, so I can grasp how it interacts with the underlying memory management to ensure unmaps are handled correctly.
|
| 87/2 |
2026/05/30 11:36 |
tool |
grepper |
0m
Args: map[Expression:userfaultfd_unmap_prep]
Results: map[Output:fs/userfaultfd.c=820=static bool has_unmap_ctx(struct userfaultfd_ctx *ctx, struct list_head *unmaps,
--
fs/userfaultfd.c-832-
fs/userfaultfd.c:833:int userfaultfd_unmap_prep(struct vm_area_struct *vma, unsigned long start,
fs/userfaultfd.c-834- unsigned long end, struct list_head *unmaps)
--
include/linux/userfaultfd_k.h=260=extern bool userfaultfd_remove(struct vm_area_struct *vma,
--
include/linux/userfaultfd_k.h-263-
include/linux/userfaultfd_k.h:264:extern int userfaultfd_unmap_prep(struct vm_area_struct *vma,
include/linux/userfaultfd_k.h-265- unsigned long start, unsigned long end, struct list_head *uf);
--
include/linux/userfaultfd_k.h=412=static inline bool userfaultfd_remove(struct vm_area_struct *vma,
--
include/linux/userfaultfd_k.h-418-
include/linux/userfaultfd_k.h:419:static inline int userfaultfd_unmap_prep(struct vm_area_struct *vma,
include/linux/userfaultfd_k.h-420- unsigned long start, unsigned long end,
--
mm/vma.c=1379=static int vms_gather_munmap_vmas(struct vma_munmap_struct *vms,
--
mm/vma.c-1457- /*
mm/vma.c:1458: * If userfaultfd_unmap_prep returns an error the vmas
mm/vma.c-1459- * will remain split, but userland will get a
--
mm/vma.c-1465- */
mm/vma.c:1466: error = userfaultfd_unmap_prep(next, vms->start,
mm/vma.c-1467- vms->end, vms->uf);
--
tools/testing/vma/include/stubs.h=138=static inline void i_mmap_unlock_write(struct address_space *mapping)
--
tools/testing/vma/include/stubs.h-141-
tools/testing/vma/include/stubs.h:142:static inline int userfaultfd_unmap_prep(struct vm_area_struct *vma,
tools/testing/vma/include/stubs.h-143- unsigned long start,
]
|
| 88/2 |
2026/05/30 11:36 |
llm |
expert |
0m
Model:
Tokens:
input: 84354
output: 23
thoughts: 2155
Thoughts: **Vulnerability Analysis: Userfaultfd Local Denial of Service**
Okay, so here's the breakdown of what I've found. It seems like `userfaultfd_unmap_prep` is called within `vms_gather_munmap_vmas`, which is part of the `munmap` process. The critical issue is that `vma_start_write` is called *before* `userfaultfd_unmap_prep`. This creates a race condition.
Specifically, `vma_start_write` will block if the VMA's reference count (`vma->vm_refcnt`) isn't zero. The `mfill_get_vma` function can leak this read lock. So, if `mfill_get_vma` fails for any reason *before* `userfaultfd_unmap_prep` gets to run, the VMA's reference count is leaked. This can happen if an attacker crafts specific `UFFDIO_COPY` requests that bypass the initial checks in `mfill_get_vma`. If an attacker can trigger a failure in `mfill_get_vma`, then the lock for `mmap_changing_lock` is also leaked.
The crucial exploit condition lies in the `mfill_get_vma` function. An attacker can trigger a jump to `out_unlock` by calling `UFFDIO_COPY` with `UFFDIO_COPY_MODE_WP` on a VMA that wasn't registered with `VM_UFFD_WP`. This specific check, and the resulting early `goto`, is attacker-controlled. The `dst_vma`'s read lock is leaked, and `state->vma` is NULL.
Consequently, `mfill_put_vma` is called, but due to `state->vma` being NULL, it does nothing but return immediately. The lock is leaked. The next `munmap` call on this VMA will then block indefinitely in `vma_start_write`, since the VMA's read lock is still held. This creates a complete and reliable local Denial-of-Service (DoS) because `mmap_write_lock` is now held. Any other thread attempting to acquire `mmap_read_lock` will block forever.
The attacker needs to:
1. Register a VMA with userfaultfd (without WP).
2. Call `UFFDIO_COPY` with `UFFDIO_COPY_MODE_WP`.
3. `mfill_get_vma` will lock the VMA, check the flag, and jump to `out_unlock`.
4. `mfill_put_vma` does nothing because `state->vma` is NULL.
5. The VMA read lock is leaked!
6. Then the attacker calls `munmap` on the VMA.
7. `munmap` calls `vma_start_write`, which blocks FOREVER waiting for the VMA read lock to be released!
8. `munmap` holds `mmap_write_lock`, so any other thread trying to acquire `mmap_read_lock` will block FOREVER!
I've confirmed that the `UFFDIO_COPY_MODE_WP` flag is indeed attacker-controllable, which makes this particularly dangerous.
The leak of the VMA read lock itself might be interesting, but `refcount_t` saturates. So it's very unlikely to lead to any use-after-free conditions. However, the `map_changing_lock` is also leaked. This is an `atomic_long_t`. On 64-bit systems, it would take hundreds of years of incremental updates to wrap around, so it is practically safe. On 32-bit systems, wrapping *might* be possible, but it is not clear that there is a vulnerability at the wrap around point. So this is unlikely to give us privilege escalation.
Finally, this DoS is easily accessible from unprivileged processes if `vm.unprivileged_userfaultfd` is 1, or if they have access to `/dev/userfaultfd`.
Interestingly, the bug report mentions "Tainted: G L syzkaller #0". This is relevant because Syzkaller often runs within user namespaces, where it can be given `CAP_SYS_PTRACE`. However, the bug report is running the tests from a user namespace with `CAP_SYS_PTRACE` capabilities. `capable(CAP_SYS_PTRACE)` checks for capabilities in the init user namespace, and NOT the current user namespace. So this bug is reproducible from within Syzkaller!
|
| 89/2 |
2026/05/30 11:36 |
tool |
grepper |
0m
Args: map[Expression:capable\(CAP_SYS_PTRACE\)]
Results: map[Output:fs/userfaultfd.c=1972=static int userfaultfd_api(struct userfaultfd_ctx *ctx,
--
fs/userfaultfd.c-1988- ret = -EPERM;
fs/userfaultfd.c:1989: if ((features & UFFD_FEATURE_EVENT_FORK) && !capable(CAP_SYS_PTRACE))
fs/userfaultfd.c-1990- goto err_out;
--
fs/userfaultfd.c=2169=static inline bool userfaultfd_syscall_allowed(int flags)
--
fs/userfaultfd.c-2178- */
fs/userfaultfd.c:2179: if (capable(CAP_SYS_PTRACE))
fs/userfaultfd.c-2180- return true;
--
security/yama/yama_lsm.c=432=static int yama_dointvec_minmax(const struct ctl_table *table, int write,
--
security/yama/yama_lsm.c-436-
security/yama/yama_lsm.c:437: if (write && !capable(CAP_SYS_PTRACE))
security/yama/yama_lsm.c-438- return -EPERM;
]
|
| 90/2 |
2026/05/30 11:36 |
llm |
expert |
0m
Model:
Tokens:
input: 87866
output: 35
thoughts: 885
Thoughts: **Analyzing the Userfaultfd Bug**
Okay, let's break this down. My initial assessment was correct: the `capable()` check uses `init_user_ns`, so processes within a user namespace lacking global `CAP_SYS_PTRACE` would normally be blocked. But the beauty, or rather the problem, lies in `UFFD_USER_MODE_ONLY`. It's ALWAYS allowed, no matter the privileges!
This means any unprivileged process can call `userfaultfd(UFFD_USER_MODE_ONLY)`. And critically, `UFFD_USER_MODE_ONLY` is *sufficient* to trigger this bug. The vulnerability is within `UFFDIO_COPY`, and it doesn't discriminate based on the userfaultfd's mode. It will happily copy from a `UFFD_USER_MODE_ONLY` fd!
I need to confirm this: `UFFD_USER_MODE_ONLY` just restricts where the page faults come *from*, it doesn't limit the ioctls. `UFFDIO_COPY` resolves page faults, so, of course, it must be permitted. This is an unprivileged local DoS!
So, the impact, succinctly:
* Exploitable: No (It's a Denial of Service, not privilege escalation or arbitrary code execution)
* DoS: Yes (It's a reliable local DoS. It blocks `mmap_write_lock` indefinitely, rendering the process unkillable, leaking its memory, and potentially triggering a hung task panic.)
* Unprivileged Access: Yes (Through `userfaultfd(UFFD_USER_MODE_ONLY)`)
* User Namespace Access: Yes (Same as above)
* VM Guest/Host/Network/Peripheral/Filesystem: No (No remote or external triggers)
Now, could there be another way to trigger this? I need to look at other possible failure points. The code has a check `if (is_vm_hugetlb_page(dst_vma))` that goes to `out`, but that does not lead to a problem. The important code seems fine. The `ops = vma_uffd_ops(dst_vma)` check is also critical. If that fails (meaning the returned `ops` is `NULL`), the lock is leaked. But when can this happen?
Looking at `vma_uffd_ops`, it returns `NULL` if the VMA isn't anonymous, shmem, or hugetlb. But... `userfaultfd` only allows registering those types! So, in normal operation, `vma_uffd_ops` shouldn't return NULL for a registered VMA.
... Unless the attacker calls `UFFDIO_COPY` on an UNREGISTERED VMA! But wait! `uffd_mfill_lock` should check for registered VMAs first! Ok, I need to investigate `uffd_mfill_lock`.
|
| 91/2 |
2026/05/30 11:36 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:148 LineCount:20]
Results: map[Contents: 148: static struct vm_area_struct *uffd_mfill_lock(struct mm_struct *dst_mm,
149: unsigned long dst_start,
150: unsigned long len)
151: {
152: struct vm_area_struct *dst_vma;
153:
154: dst_vma = uffd_lock_vma(dst_mm, dst_start);
155: if (IS_ERR(dst_vma) || validate_dst_vma(dst_vma, dst_start + len))
156: return dst_vma;
157:
158: vma_end_read(dst_vma);
159: return ERR_PTR(-ENOENT);
160: }
161:
162: static void uffd_mfill_unlock(struct vm_area_struct *vma)
163: {
164: vma_end_read(vma);
165: }
166:
167: #else
]
|
| 92/2 |
2026/05/30 11:36 |
llm |
expert |
0m
Model:
Tokens:
input: 89730
output: 20
thoughts: 18
Thoughts: Here's my thought process, as if I were considering this function:
**Analyzing the Purpose of `validate_dst_vma`**
Okay, so I'm looking at `validate_dst_vma`. The name itself is pretty clear: it's about validating something related to a "destination VMA." My initial thought is, "Okay, VMA is Virtual Memory Area, so this function is likely checking the validity of a memory region we're writing to." But then I re-read it, and focus in. It is very likely concerned with the validation of the VMA in question. This implies something specific about the destination itself – its characteristics, its state, or perhaps its permissions. I need to dig deeper to see precisely what "valid" means in this context, because validity is highly contextual. Am I checking for overlap? Is the region mapped? Do I have the correct permissions? What's the *specific* purpose of this validation within the larger flow? I bet it’s important.
|
| 93/2 |
2026/05/30 11:36 |
tool |
grepper |
0m
Args: map[Expression:validate_dst_vma]
Results: map[Output:mm/userfaultfd.c=72=static __always_inline
mm/userfaultfd.c:73:bool validate_dst_vma(struct vm_area_struct *dst_vma, unsigned long dst_end)
mm/userfaultfd.c-74-{
--
mm/userfaultfd.c=148=static struct vm_area_struct *uffd_mfill_lock(struct mm_struct *dst_mm,
--
mm/userfaultfd.c-154- dst_vma = uffd_lock_vma(dst_mm, dst_start);
mm/userfaultfd.c:155: if (IS_ERR(dst_vma) || validate_dst_vma(dst_vma, dst_start + len))
mm/userfaultfd.c-156- return dst_vma;
--
mm/userfaultfd.c=169=static struct vm_area_struct *uffd_mfill_lock(struct mm_struct *dst_mm,
--
mm/userfaultfd.c-179-
mm/userfaultfd.c:180: if (validate_dst_vma(dst_vma, dst_start + len))
mm/userfaultfd.c-181- return dst_vma;
]
|
| 94/2 |
2026/05/30 11:36 |
llm |
expert |
0m
Model:
Tokens:
input: 90310
output: 34
thoughts: 12
Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own:
**Analyzing `validate_dst_vma`**
Alright, time to crack open `validate_dst_vma`. I'm immediately curious about what kind of checks it's performing. Given the name, I'm expecting it to be focused on validating a destination Virtual Memory Area (VMA). That means it's likely responsible for ensuring that a proposed memory mapping, or modification to an existing mapping, is legitimate and won't violate any constraints. I bet it's involved in preventing address space collisions, checking permissions, maybe even making sure the requested size is within allowed limits and that enough physical memory is available if relevant. This function is probably critical for system stability. A poorly validated destination could lead to all sorts of nasty problems, like kernel panics or security vulnerabilities. I'll need to pay close attention to the specific criteria it's verifying. Let's see... the devil's in the details, so let's get reading!
|
| 95/2 |
2026/05/30 11:37 |
tool |
read-file |
0m
Args: map[File:mm/userfaultfd.c FirstLine:72 LineCount:20]
Results: map[Contents: 72: static __always_inline
73: bool validate_dst_vma(struct vm_area_struct *dst_vma, unsigned long dst_end)
74: {
75: /* Make sure that the dst range is fully within dst_vma. */
76: if (dst_end > dst_vma->vm_end)
77: return false;
78:
79: /*
80: * Check the vma is registered in uffd, this is required to
81: * enforce the VM_MAYWRITE check done at uffd registration
82: * time.
83: */
84: if (!dst_vma->vm_userfaultfd_ctx.ctx)
85: return false;
86:
87: return true;
88: }
89:
90: static __always_inline
91: struct vm_area_struct *find_vma_and_prepare_anon(struct mm_struct *mm,
]
|
| 96/2 |
2026/05/30 11:37 |
llm |
expert |
0m
Model:
Tokens:
input: 90869
output: 38
thoughts: 3189
Thoughts: **Analyzing a Userfaultfd Bug: A Deep Dive**
Okay, here's what I've pieced together about this userfaultfd issue. It looks like `validate_dst_vma` correctly checks the validity of a VMA associated with a userfaultfd context. The function `uffd_mfill_lock` seems to handle the locking and unlocking of the VMA correctly *if* it's invalid. It appears to unlock the VMA (via `vma_end_read`) and return an error (`-ENOENT`) when `validate_dst_vma` finds the VMA is not registered.
The core problem, I believe, lies within `mfill_get_vma`. If `atomic_read(&ctx->mmap_changing)` is true, the code jumps to `out_unlock`. Crucially, `state->vma` isn't set before that `goto`, leading to a VMA *leak* because neither the VMA itself nor the `map_changing_lock` are properly released. I'm certain this is a bug. A simple fix would be to set `state->vma = dst_vma` after `uffd_mfill_lock` and before `down_read`. This would give `mfill_put_vma` the VMA it needs to correctly release the resources on the `out_unlock` path.
However, I need to be absolutely sure this change is safe. Setting `state->vma` early will ensure that `mfill_put_vma` unlocks both the VMA and the `map_changing_lock`. I need to ensure that the order is right. If `state->vma` is set before `down_read(&ctx->map_changing_lock)`, `mfill_put_vma` will release the semaphore without a corresponding `down_read`! So, the fix should be to move `state->vma = dst_vma` *after* the `down_read` call. That makes it safe!
Now, the bug report... The backtrace shows a task blocked in `uffd_lock_vma` due to a lock in `mmap_read_lock`. This means it's trying to acquire `mmap_read_lock`. The fact that it's blocked implies another task *holds* the `mmap_write_lock`. That's where it all gets dicey! My initial thought was that a previous call to `mfill_get_vma` leaked the VMA refcnt and the `map_changing_lock`. However, that should be easy to see. If that's the case, and the leaked lock should be visible in the lockdep output.
Lockdep, however, does not show the `map_changing_lock` in use! Not even from the task holding `mmap_read_lock`, as that task is the one that's blocked. And the lockdep output *should* show it. I checked the initialization to ensure that this lock is tracked by lockdep. This tells me something is seriously amiss. If the task leaked the lock, lockdep would show it. My concern is, could the task that leaked the lock be *still* running, and not be in the lockdep report? The task's stack trace doesn't give a signal! The report says, "Showing all locks held in the system:" so it seems that the other task would have to be visible.
Then I checked the code for `uffd_lock_vma` itself. If the acquire lock fails, it returns false. The return value is then used in `mfill_get_vma` to unlock the VMA. Wait. `mmap_read_lock` is acquired at the beginning of `uffd_lock_vma`. Then `vma_start_read_locked` is called, *while* holding `mmap_read_lock`. If `vma_start_read_locked` fails, then `vma` is set to `ERR_PTR(-EAGAIN)`, and then `mmap_read_unlock(mm)` *is* called. This is all right, as long as `vma_start_read_locked` returns true.
So, either the lock is corrupted, or there's a race I'm missing.
And the bug report says *exactly* this:
"INFO: task syz.2.8:5969 blocked for more than 143 seconds.
...
mmap_read_lock include/linux/mmap_lock.h:592 [inline]
uffd_lock_vma+0xb6/0x2d0 mm/userfaultfd.c:135
uffd_
|
| 97/2 |
2026/05/30 11:37 |
tool |
read-file |
0m
Args: map[File:include/linux/mmap_lock.h FirstLine:220 LineCount:40]
Results: map[Contents: 220: * __vma_start_exclude_readers() may be sleeping waiting for readers to
221: * drop their reference count, so wake it up if we were the last reader
222: * blocking it from being acquired.
223: *
224: * We may be raced by other readers temporarily incrementing the
225: * reference count, though the race window is very small, this might
226: * cause spurious wakeups.
227: */
228: if (newcnt && __vma_are_readers_excluded(newcnt))
229: rcuwait_wake_up(&mm->vma_writer_wait);
230: }
231:
232: /*
233: * Use only while holding mmap read lock which guarantees that locking will not
234: * fail (nobody can concurrently write-lock the vma). vma_start_read() should
235: * not be used in such cases because it might fail due to mm_lock_seq overflow.
236: * This functionality is used to obtain vma read lock and drop the mmap read lock.
237: */
238: static inline bool vma_start_read_locked_nested(struct vm_area_struct *vma, int subclass)
239: {
240: int oldcnt;
241:
242: mmap_assert_locked(vma->vm_mm);
243: if (unlikely(!__refcount_inc_not_zero_limited_acquire(&vma->vm_refcnt, &oldcnt,
244: VM_REFCNT_LIMIT)))
245: return false;
246:
247: __vma_lockdep_acquire_read(vma);
248: return true;
249: }
250:
251: /*
252: * Use only while holding mmap read lock which guarantees that locking will not
253: * fail (nobody can concurrently write-lock the vma). vma_start_read() should
254: * not be used in such cases because it might fail due to mm_lock_seq overflow.
255: * This functionality is used to obtain vma read lock and drop the mmap read lock.
256: */
257: static inline bool vma_start_read_locked(struct vm_area_struct *vma)
258: {
259: return vma_start_read_locked_nested(vma, 0);
]
|
| 98/2 |
2026/05/30 11:37 |
llm |
expert |
0m
Model:
Tokens:
input: 95826
output: 66
thoughts: 4215
Thoughts: **A Deep Dive into a Potential Kernel Deadlock**
Alright, let's break this down. Initially, I was looking at how `vma_start_read_locked_nested` handles the `vma->vm_refcnt`. The logic seemed straightforward, incrementing the refcount and returning failure if it's already at a limit. However, the initial state of `vma->vm_refcnt` piqued my interest. It's zero when detached and >0 when attached, so in a normal scenario, the call to `vma_start_read_locked_nested` should succeed. Then we move on to `uffd_lock_vma` and the `mfill_get_vma`. Now, here's where it gets interesting. I'm seeing a possible refcount and lock leak. The function takes a `down_read` of `ctx->map_changing_lock`, and then checks if `mmap_changing` is true. If it is, the code jumps to `out_unlock`. `out_unlock` calls `mfill_put_vma(state)`, but `state->vma` is NULL! This means that `mfill_put_vma` does *nothing*. Consequently, the `vma->vm_refcnt` increment is leaked, and `down_read(&ctx->map_changing_lock)` is leaked.
If `down_read` is leaked, the lockdep count for `map_changing_lock` is incremented. I noted that lockdep tracks locks for the *current* task. If the task returns to userspace, lockdep clears the lockdep state! That's why it's not showing up in the output. The lock is still held, but lockdep has forgotten about it. Later, a `munmap` call triggers, calling `vma_start_write`, which in turn calls `__vma_start_exclude_readers`, and waits for `vma->vm_refcnt` to drop via `rcuwait_wait_event`. Because that refcount is leaked, this blocks *forever*! `munmap` also holds `mmap_write_lock`, and since it's blocked, it SHOULD show up in lockdep... but it doesn't.
Now, I was initially trying to work out WHY `munmap` was not showing up, but it all comes down to the task that holds the `mmap_write_lock` being killed, so `exit_mm` can not be run, meaning we are blocked in D state.
But if `munmap` is blocked, and there's no task holding the lock according to lockdep, this suggests the deadlock is deeper.
Let's assume the lock that has been leaked is `mmap_read_lock`. The readers increment, and a writer is waiting in the queue. Now, let's say a writer is also blocked in the queue. The writer would be waiting on the readers to drop, but because there's a leaked lock, the reader never drops. So the writer hangs waiting. This can happen with the lock, or even with a killed thread. Because the lock is always in use, the reader cannot proceed because the writer is waiting on the reader, and the reader is also waiting.
Now, consider the root cause of the bug: `mfill_get_vma` is leaking the `vma->vm_refcnt` and `down_read(&ctx->map_changing_lock)`. The control flow in `mfill_get_vma` skips the `mfill_put_vma` call when an error happens. Therefore the functions are not paired correctly, and the refcount is never dropped. The `map_changing_lock` is also never freed in the error case. The lock will block until the refcount is dropped.
This is a local DoS, easily reproducible with `userfaultfd`, and potentially accessible to unprivileged processes through user namespaces. It's not a privilege escalation or memory corruption bug, but it's a solid denial-of-service vulnerability. My current analysis indicates there are no exploitable race conditions here, and it does not appear to be directly linked to any of the trigger categories (VM, network, filesystem, etc).
|
| 99/2 |
2026/05/30 11:38 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 100/2 |
2026/05/30 11:38 |
llm |
expert |
0m
Model:
Tokens:
input: 101086
output: 998
thoughts: 567
Thoughts: **My Analysis of the Userfaultfd Bug**
Okay, the results are in, and now it's time to craft the final explanation of this nasty little bug and its implications. I've pinpointed the issue: it's nestled deep within `mfill_get_vma` in `mm/userfaultfd.c`.
Here's the problem: when `mfill_get_vma` encounters a failure—say, `atomic_read(&ctx->mmap_changing)` is true or the VMA lacks `VM_UFFD_WP` when it should have `MFILL_ATOMIC_WP`—it jumps to `out_unlock` and calls `mfill_put_vma(state)`. Seems reasonable enough on the surface. However, the critical flaw lies in how the `state->vma` pointer is handled. It's *only* assigned at the very end of `mfill_get_vma`, right before `out:`.
This means that on the error path, when `mfill_put_vma` is invoked, `state->vma` is still `NULL`. Now, `mfill_put_vma` correctly checks for `NULL` with `if (!state->vma) return;` and bails out, intending to do nothing. But the *intention* doesn't matter. It returns early without releasing any of the locks it holds, which is a disaster.
This careless exit leaks two crucial locks. First, there's the per-VMA read lock (`vma->vm_refcnt`), which was acquired by `uffd_mfill_lock`. Second, there's the `ctx->map_changing_lock` read lock, acquired by `down_read`.
These leaked locks cause a cascade of problems. Any subsequent operation attempting to acquire them for writing will block indefinitely. Take `munmap`, for example. It grabs `mmap_write_lock` and then calls `vma_start_write` (which waits for `vma->vm_refcnt` to drop to 0) and `userfaultfd_unmap_prep` (acquiring `down_write(&ctx->map_changing_lock)`). Since the read locks are still held, `munmap` gets stuck in an uninterruptible "D" state, holding onto `mmap_write_lock`. Anything else that needs to acquire `mmap_read_lock`—like a page fault or a `mmap` call—will also block forever, leading to a hung task.
The impact? It's a local Denial of Service (DoS) vulnerability. The scary part is that an unprivileged user can trigger this. The `userfaultfd` feature can be used by unprivileged users if they set the `UFFD_USER_MODE_ONLY` flag, effectively bypassing the usual `CAP_SYS_PTRACE` and `vm.unprivileged_userfaultfd` checks. Thankfully, it doesn't seem to open the door to memory corruption or privilege escalation, but a DoS is bad enough.
|
| 101/1 |
2026/05/30 11:38 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the provided kernel bug report and source code, here is
the assessment of the vulnerability:
### Bug Explanation
The bug is a lock leak in the `userfaultfd` subsystem, specifically in the
`mfill_get_vma()` function in `mm/userfaultfd.c`.
When `mfill_get_vma()` is called (e.g., during a `UFFDIO_COPY` ioctl), it first
locks the destination VMA using `uffd_mfill_lock()` (which acquires the per-VMA
read lock and increments `vma->vm_refcnt`) and then acquires a read lock on
`ctx->map_changing_lock`.
After acquiring these locks, it performs several validation checks. If any of
these checks fail (for example, if `ctx->mmap_changing` is true, or if a
write-protect copy is requested on a VMA without `VM_UFFD_WP`), the code jumps
to the `out_unlock` label to clean up and return an error:
```c
out_unlock:
mfill_put_vma(state);
return err;
```
However, `mfill_put_vma(state)` relies on `state->vma` to know which locks to
release. The problem is that `state->vma` is only assigned at the very end of
`mfill_get_vma()` (at the `out:` label). In the error paths, `state->vma` is
still `NULL`.
When `mfill_put_vma()` sees that `state->vma` is `NULL`, it returns immediately
without doing anything:
```c
static void mfill_put_vma(struct mfill_state *state)
{
if (!state->vma)
return;
// ... releases locks ...
}
```
As a result, both the **per-VMA read lock** and the **`ctx->map_changing_lock`
read lock** are permanently leaked.
### Impact and Deadlock Scenario
If an attacker intentionally triggers this error path (e.g., by racing
`UFFDIO_COPY` with `munmap`, or simply by passing `UFFDIO_COPY_MODE_WP` on a VMA
not registered for write-protection), the locks are leaked.
Subsequently, if the process attempts to unmap or modify the VMA (e.g., via
`munmap()`), the kernel will:
1. Acquire the `mmap_write_lock` for the process.
2. Call `vma_start_write()`, which waits uninterruptibly for `vma->vm_refcnt` to
drop to 0.
3. Call `userfaultfd_unmap_prep()`, which waits uninterruptibly for
`down_write(&ctx->map_changing_lock)`.
Because the read locks were leaked, the `munmap` thread will block forever in
`D` state while holding the `mmap_write_lock`. Any other thread in the process
(or the kernel's OOM reaper) that subsequently tries to acquire `mmap_read_lock`
will also block forever. This perfectly matches the `syz.2.8` task blocked in
`mmap_read_lock` shown in the bug report.
### Security Dimensions
* **Exploitable:** **No**. This is a pure logic bug leading to unreleased locks.
It does not cause memory corruption, use-after-free, or out-of-bounds accesses,
and cannot be used for privilege escalation.
* **Denial Of Service:** **Yes**. An attacker can reliably freeze their own
process in an unkillable `D` state. Because the process cannot be killed (even
by `SIGKILL` or the OOM killer), its memory cannot be reclaimed, leading to a
resource leak. On systems with `kernel.hung_task_panic=1`, this will reliably
panic and reboot the entire system.
* **Accessible From Unprivileged Processes:** **Yes**. While full `userfaultfd`
access is often restricted to root or `CAP_SYS_PTRACE`, unprivileged users can
always create a userfaultfd by passing the `UFFD_USER_MODE_ONLY` flag. This flag
is sufficient to register a VMA and trigger the `UFFDIO_COPY` ioctl bug.
* **Accessible From User Namespaces:** **Yes**. For the same reasons as above.
* **VM Guest / Host / Network / Peripheral / Filesystem Trigger:** **No**. The
bug is strictly tied to the local `userfaultfd` syscall/ioctl interface.]
|