| 0/0 |
2026/05/16 20:25 |
flow |
assessment-security |
31m
Results: map[DenialOfService:true Explanation:**Analysis of the Bug**
The bug is a recursive locking deadlock in the `perf_events` subsystem.
When a user calls `mmap()` on a perf event file descriptor, the kernel invokes
`perf_mmap()`. In this function, the `event->mmap_mutex` is acquired using a
`scoped_guard`. After setting up the ring buffer, `perf_mmap()` calls
`map_range()` to map the buffer pages into the user's VMA.
If `map_range()` fails (for example, due to memory allocation failures in
`remap_pfn_range()` when allocating page tables under memory pressure, or due to
fault injection as seen in the syzkaller reproducer), `perf_mmap()` attempts to
clean up by calling `perf_mmap_close(vma)`.
Inside `perf_mmap_close()`, the code decrements the `event->mmap_count`
reference counter. If this counter drops to zero (which it will if this was the
first time the event was mmap'ed), `perf_mmap_close()` attempts to acquire
`event->mmap_mutex` to safely detach the ring buffer:
```c
if (!refcount_dec_and_mutex_lock(&event->mmap_count,
&event->mmap_mutex))
goto out_put;
```
However, because `perf_mmap()` is still holding `event->mmap_mutex` via the
`scoped_guard`, the thread attempts to lock the same mutex twice. This results
in a classic recursive locking deadlock. The thread will hang indefinitely in
the `TASK_UNINTERRUPTIBLE` (D) state.
**Security Impact**
* **Exploitable:** False. The bug is a simple deadlock. It does not cause memory
corruption, out-of-bounds accesses, or use-after-free conditions. It cannot be
used for privilege escalation or arbitrary code execution.
* **Denial of Service:** True. Because the deadlocked thread is in an
uninterruptible state, it cannot be killed (even with `SIGKILL`). Furthermore,
the thread hangs while holding its own `mm->mmap_lock` for writing. An attacker
can repeatedly spawn processes that trigger this deadlock, eventually exhausting
system resources such as the PID space and memory, resulting in a local Denial
of Service.
* **Accessible From Unprivileged Processes:** True. Unprivileged users are
generally allowed to open certain perf events (like `PERF_TYPE_SOFTWARE` with
`exclude_kernel=1`, depending on the `kernel.perf_event_paranoid` sysctl) and
can call `mmap()` on them. By artificially creating memory pressure (e.g., via
memory cgroups or exhausting available RAM), an unprivileged user can force
`map_range()` to fail with `-ENOMEM`, reliably triggering the deadlock.
* **Accessible From User Namespaces:** True. The bug is reachable from
unprivileged contexts, including within user namespaces.
The bug is specific to the local system call interface for `perf_events` and
cannot be triggered remotely, via peripherals, or from a VM guest context. Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:false VMHostTrigger:false]
|
| 1/1 |
2026/05/16 20:25 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#define BITMASK(bf_off,bf_len) (((1ull << (bf_len)) - 1) << (bf_off))
#define STORE_BY_BITMASK(type,htobe,addr,val,bf_off,bf_len) *(type*)(addr) = htobe((htobe(*(type*)(addr)) & ~BITMASK((bf_off), (bf_len))) | (((type)(val) << (bf_off)) & BITMASK((bf_off), (bf_len))))
static bool write_file(const char* file, const char* what, ...)
{
char buf[1024];
va_list args;
va_start(args, what);
vsnprintf(buf, sizeof(buf), what, args);
va_end(args);
buf[sizeof(buf) - 1] = 0;
int len = strlen(buf);
int fd = open(file, O_WRONLY | O_CLOEXEC);
if (fd == -1)
return false;
if (write(fd, buf, len) != len) {
int err = errno;
close(fd);
errno = err;
return false;
}
close(fd);
return true;
}
static int inject_fault(int nth)
{
int fd;
fd = open("/proc/thread-self/fail-nth", O_RDWR);
if (fd == -1)
exit(1);
char buf[16];
sprintf(buf, "%d", nth);
if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf))
exit(1);
return fd;
}
static const char* setup_fault()
{
int fd = open("/proc/self/make-it-fail", O_WRONLY);
if (fd == -1)
return "CONFIG_FAULT_INJECTION is not enabled";
close(fd);
fd = open("/proc/thread-self/fail-nth", O_WRONLY);
if (fd == -1)
return "kernel does not have systematic fault injection support";
close(fd);
static struct {
const char* file;
const char* val;
bool fatal;
} files[] = {
{"/sys/kernel/debug/failslab/ignore-gfp-wait", "N", true},
{"/sys/kernel/debug/fail_futex/ignore-private", "N", false},
{"/sys/kernel/debug/fail_page_alloc/ignore-gfp-highmem", "N", false},
{"/sys/kernel/debug/fail_page_alloc/ignore-gfp-wait", "N", false},
{"/sys/kernel/debug/fail_page_alloc/min-order", "0", false},
};
unsigned i;
for (i = 0; i < sizeof(files) / sizeof(files[0]); i++) {
if (!write_file(files[i].file, files[i].val)) {
if (files[i].fatal)
return "failed to write fault injection file";
}
}
return NULL;
}
uint64_t r[1] = {0xffffffffffffffff};
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if ((reason = setup_fault()))
printf("the reproducer may not work as expected: fault injection setup failed: %s\n", reason);
intptr_t res = 0;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// perf_event_open arguments: [
// attr: ptr[in, perf_event_attr] {
// perf_event_attr {
// type: perf_event_type = 0x2 (4 bytes)
// size: len = 0x80 (4 bytes)
// config0: int8 = 0x49 (1 bytes)
// config1: int8 = 0x1 (1 bytes)
// config2: int8 = 0x0 (1 bytes)
// config3: int8 = 0x0 (1 bytes)
// config4: const = 0x0 (4 bytes)
// sample_freq: int64 = 0x1 (8 bytes)
// sample_type: perf_sample_type = 0x14a69b (8 bytes)
// read_format: perf_read_format = 0x0 (8 bytes)
// disabled: int64 = 0x0 (0 bytes)
// inherit: int64 = 0x0 (0 bytes)
// pinned: int64 = 0x0 (0 bytes)
// exclusive: int64 = 0x0 (0 bytes)
// exclude_user: int64 = 0x0 (0 bytes)
// exclude_kernel: int64 = 0x0 (0 bytes)
// exclude_hv: int64 = 0x0 (0 bytes)
// exclude_idle: int64 = 0x0 (0 bytes)
// mmap: int64 = 0x0 (0 bytes)
// comm: int64 = 0x0 (0 bytes)
// freq: int64 = 0x0 (0 bytes)
// inherit_stat: int64 = 0x0 (0 bytes)
// enable_on_exec: int64 = 0x0 (0 bytes)
// task: int64 = 0x0 (0 bytes)
// watermark: int64 = 0x0 (0 bytes)
// precise_ip: int64 = 0x0 (0 bytes)
// mmap_data: int64 = 0x0 (0 bytes)
// sample_id_all: int64 = 0x0 (0 bytes)
// exclude_host: int64 = 0x0 (0 bytes)
// exclude_guest: int64 = 0x0 (0 bytes)
// exclude_callchain_kernel: int64 = 0x0 (0 bytes)
// exclude_callchain_user: int64 = 0x0 (0 bytes)
// mmap2: int64 = 0x0 (0 bytes)
// comm_exec: int64 = 0x0 (0 bytes)
// use_clockid: int64 = 0x0 (0 bytes)
// context_switch: int64 = 0x0 (0 bytes)
// write_backward: int64 = 0x0 (0 bytes)
// namespaces: int64 = 0x0 (0 bytes)
// ksymbol: int64 = 0x0 (0 bytes)
// bpf_event: int64 = 0x0 (0 bytes)
// aux_output: int64 = 0x0 (0 bytes)
// cgroup: int64 = 0x0 (0 bytes)
// text_poke: int64 = 0x0 (0 bytes)
// build_id: int64 = 0x0 (0 bytes)
// inherit_thread: int64 = 0x0 (0 bytes)
// remove_on_exec: int64 = 0x0 (0 bytes)
// sigtrap: int64 = 0x0 (0 bytes)
// __reserved_1: const = 0x0 (8 bytes)
// wakeup_events: int32 = 0x0 (4 bytes)
// bp_type: perf_bp_type = 0x0 (4 bytes)
// bp_config: union perf_bp_config {
// perf_bp: perf_bp {
// bp_addr: nil
// bp_len: perf_bp_lens = 0x1 (8 bytes)
// }
// }
// branch_sample_type: perf_branch_sample_type = 0x402d (8 bytes)
// sample_regs_user: int64 = 0xc844 (8 bytes)
// sample_stack_user: int32 = 0x410 (4 bytes)
// clockid: clock_type = 0x0 (4 bytes)
// sample_regs_intr: int64 = 0x7 (8 bytes)
// aux_watermark: int32 = 0x400002 (4 bytes)
// sample_max_stack: int16 = 0x0 (2 bytes)
// __reserved_2: const = 0x0 (2 bytes)
// aux_sample_size: int32 = 0x0 (4 bytes)
// __reserved_3: const = 0x0 (4 bytes)
// sig_data: int64 = 0x4 (8 bytes)
// }
// }
// pid: pid (resource)
// cpu: intptr = 0xffffffffffffffff (8 bytes)
// group: fd_perf (resource)
// flags: perf_flags = 0x2 (8 bytes)
// ]
// returns fd_perf
*(uint32_t*)0x200000002bc0 = 2;
*(uint32_t*)0x200000002bc4 = 0x80;
*(uint8_t*)0x200000002bc8 = 0x49;
*(uint8_t*)0x200000002bc9 = 1;
*(uint8_t*)0x200000002bca = 0;
*(uint8_t*)0x200000002bcb = 0;
*(uint32_t*)0x200000002bcc = 0;
*(uint64_t*)0x200000002bd0 = 1;
*(uint64_t*)0x200000002bd8 = 0x14a69b;
*(uint64_t*)0x200000002be0 = 0;
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 0, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 1, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 2, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 3, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 4, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 5, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 6, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 7, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 8, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 9, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 10, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 11, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 12, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 13, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 14, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 15, 2);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 17, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 18, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 19, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 20, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 21, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 22, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 23, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 24, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 25, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 26, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 27, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 28, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 29, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 30, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 31, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 32, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 33, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 34, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 35, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 36, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 37, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 38, 26);
*(uint32_t*)0x200000002bf0 = 0;
*(uint32_t*)0x200000002bf4 = 0;
*(uint64_t*)0x200000002bf8 = 0;
*(uint64_t*)0x200000002c00 = 1;
*(uint64_t*)0x200000002c08 = 0x402d;
*(uint64_t*)0x200000002c10 = 0xc844;
*(uint32_t*)0x200000002c18 = 0x410;
*(uint32_t*)0x200000002c1c = 0;
*(uint64_t*)0x200000002c20 = 7;
*(uint32_t*)0x200000002c28 = 0x400002;
*(uint16_t*)0x200000002c2c = 0;
*(uint16_t*)0x200000002c2e = 0;
*(uint32_t*)0x200000002c30 = 0;
*(uint32_t*)0x200000002c34 = 0;
*(uint64_t*)0x200000002c38 = 4;
res = syscall(__NR_perf_event_open, /*attr=*/0x200000002bc0ul, /*pid=*/0, /*cpu=*/(intptr_t)-1, /*group=*/(intptr_t)-1, /*flags=PERF_FLAG_FD_OUTPUT*/2ul);
if (res != -1)
r[0] = res;
// mmap arguments: [
// addr: VMA[0x2000]
// len: len = 0x2000 (8 bytes)
// prot: mmap_prot = 0x1000008 (8 bytes)
// flags: mmap_flags = 0x13 (8 bytes)
// fd: fd (resource)
// offset: intptr = 0x0 (8 bytes)
// ]
inject_fault(12);
syscall(__NR_mmap, /*addr=*/0x200000ffe000ul, /*len=*/0x2000ul, /*prot=PROT_GROWSDOWN|PROT_SEM*/0x1000008ul, /*flags=MAP_SHARED_VALIDATE|MAP_FIXED*/0x13ul, /*fd=*/r[0], /*offset=*/0ul);
return 0;
}
]
|
| 2/1 |
2026/05/16 20:25 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/3e5ccb343c33d4af45a1398704330af8e94bafbf]
|
| 3/1 |
2026/05/16 20:26 |
action |
kernel-builder |
14m
Results: map[KernelObj:/app/workdir/cache/build/86568be860ca24b71f621adf91854371d9877412]
|
| 4/1 |
2026/05/16 20:41 |
action |
codesearch-prepare |
10m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/05/16 20:51 |
agent |
expert |
4m
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 greeping
".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).
Don't make assumptions; verify them with source code access. 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 completly 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 tranfers, that's 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 exploitabability 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 Confidetial 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.
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:
R10: 0000000000000013 R11: 0000000000000246 R12: 0000000000000002
R13: 00007ff784215fac R14: 00007ff784215fa0 R15: 00007ff784215fa0
</TASK>
============================================
WARNING: possible recursive locking detected
syzkaller #0 Not tainted
--------------------------------------------
syz.0.17/5460 is trying to acquire lock:
ffff88801f26c9e0 (&event->mmap_mutex){+.+.}-{4:4}, at: refcount_dec_and_mutex_lock+0x30/0xa0 lib/refcount.c:118
but task is already holding lock:
ffff88801f26c9e0 (&event->mmap_mutex){+.+.}-{4:4}, at: class_mutex_constructor include/linux/mutex.h:253 [inline]
ffff88801f26c9e0 (&event->mmap_mutex){+.+.}-{4:4}, at: perf_mmap+0x1bb/0x4b0 kernel/events/core.c:7453
other info that might help us debug this:
Possible unsafe locking scenario:
CPU0
----
lock(&event->mmap_mutex);
lock(&event->mmap_mutex);
*** DEADLOCK ***
May be due to missing lock nesting notation
2 locks held by syz.0.17/5460:
#0: ffff888011a94080 (&mm->mmap_lock){++++}-{4:4}, at: mmap_write_lock_killable include/linux/mmap_lock.h:554 [inline]
#0: ffff888011a94080 (&mm->mmap_lock){++++}-{4:4}, at: vm_mmap_pgoff+0x234/0x4f0 mm/util.c:579
#1: ffff88801f26c9e0 (&event->mmap_mutex){+.+.}-{4:4}, at: class_mutex_constructor include/linux/mutex.h:253 [inline]
#1: ffff88801f26c9e0 (&event->mmap_mutex){+.+.}-{4:4}, at: perf_mmap+0x1bb/0x4b0 kernel/events/core.c:7453
stack backtrace:
CPU: 0 UID: 0 PID: 5460 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_deadlock_bug+0x279/0x290 kernel/locking/lockdep.c:3041
check_deadlock kernel/locking/lockdep.c:3093 [inline]
validate_chain kernel/locking/lockdep.c:3895 [inline]
__lock_acquire+0x253f/0x2cf0 kernel/locking/lockdep.c:5237
lock_acquire+0xf0/0x2e0 kernel/locking/lockdep.c:5868
__mutex_lock_common kernel/locking/mutex.c:614 [inline]
__mutex_lock+0x19f/0x1300 kernel/locking/mutex.c:776
refcount_dec_and_mutex_lock+0x30/0xa0 lib/refcount.c:118
perf_mmap_close+0x953/0xf90 kernel/events/core.c:7064
perf_mmap+0x418/0x4b0 kernel/events/core.c:7488
vfs_mmap include/linux/fs.h:2070 [inline]
mmap_file mm/internal.h:167 [inline]
__mmap_new_file_vma mm/vma.c:2468 [inline]
__mmap_new_vma mm/vma.c:2532 [inline]
__mmap_region mm/vma.c:2759 [inline]
mmap_region+0x18fe/0x2240 mm/vma.c:2837
do_mmap+0xc39/0x10c0 mm/mmap.c:559
vm_mmap_pgoff+0x2c9/0x4f0 mm/util.c:581
ksys_mmap_pgoff+0x51e/0x760 mm/mmap.c:605
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:0x7ff783f9c799
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007ffeef4c8518 EFLAGS: 00000246 ORIG_RAX: 0000000000000009
RAX: ffffffffffffffda RBX: 00007ff784215fa0 RCX: 00007ff783f9c799
RDX: 0000000001000008 RSI: 0000000000002000 RDI: 0000200000ffe000
RBP: 00007ffeef4c8580 R08: 0000000000000003 R09: 0000000000000000
R10: 0000000000000013 R11: 0000000000000246 R12: 0000000000000002
R13: 00007ff784215fac R14: 00007ff784215fa0 R15: 00007ff784215fa0
</TASK>
It is reproducible with the followint program.
Keep in mind that it may lack the precise threading, sandboxing, and some arguments of a working reproducer.
But it should give an idea of the involved syscalls.
// autogenerated by syzkaller (https://github.com/google/syzkaller)
#define _GNU_SOURCE
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
#define BITMASK(bf_off,bf_len) (((1ull << (bf_len)) - 1) << (bf_off))
#define STORE_BY_BITMASK(type,htobe,addr,val,bf_off,bf_len) *(type*)(addr) = htobe((htobe(*(type*)(addr)) & ~BITMASK((bf_off), (bf_len))) | (((type)(val) << (bf_off)) & BITMASK((bf_off), (bf_len))))
static bool write_file(const char* file, const char* what, ...)
{
char buf[1024];
va_list args;
va_start(args, what);
vsnprintf(buf, sizeof(buf), what, args);
va_end(args);
buf[sizeof(buf) - 1] = 0;
int len = strlen(buf);
int fd = open(file, O_WRONLY | O_CLOEXEC);
if (fd == -1)
return false;
if (write(fd, buf, len) != len) {
int err = errno;
close(fd);
errno = err;
return false;
}
close(fd);
return true;
}
static int inject_fault(int nth)
{
int fd;
fd = open("/proc/thread-self/fail-nth", O_RDWR);
if (fd == -1)
exit(1);
char buf[16];
sprintf(buf, "%d", nth);
if (write(fd, buf, strlen(buf)) != (ssize_t)strlen(buf))
exit(1);
return fd;
}
static const char* setup_fault()
{
int fd = open("/proc/self/make-it-fail", O_WRONLY);
if (fd == -1)
return "CONFIG_FAULT_INJECTION is not enabled";
close(fd);
fd = open("/proc/thread-self/fail-nth", O_WRONLY);
if (fd == -1)
return "kernel does not have systematic fault injection support";
close(fd);
static struct {
const char* file;
const char* val;
bool fatal;
} files[] = {
{"/sys/kernel/debug/failslab/ignore-gfp-wait", "N", true},
{"/sys/kernel/debug/fail_futex/ignore-private", "N", false},
{"/sys/kernel/debug/fail_page_alloc/ignore-gfp-highmem", "N", false},
{"/sys/kernel/debug/fail_page_alloc/ignore-gfp-wait", "N", false},
{"/sys/kernel/debug/fail_page_alloc/min-order", "0", false},
};
unsigned i;
for (i = 0; i < sizeof(files) / sizeof(files[0]); i++) {
if (!write_file(files[i].file, files[i].val)) {
if (files[i].fatal)
return "failed to write fault injection file";
}
}
return NULL;
}
uint64_t r[1] = {0xffffffffffffffff};
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000ul, /*prot=PROT_WRITE|PROT_READ|PROT_EXEC*/7ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200001000000ul, /*len=*/0x1000ul, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
if ((reason = setup_fault()))
printf("the reproducer may not work as expected: fault injection setup failed: %s\n", reason);
intptr_t res = 0;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// perf_event_open arguments: [
// attr: ptr[in, perf_event_attr] {
// perf_event_attr {
// type: perf_event_type = 0x2 (4 bytes)
// size: len = 0x80 (4 bytes)
// config0: int8 = 0x49 (1 bytes)
// config1: int8 = 0x1 (1 bytes)
// config2: int8 = 0x0 (1 bytes)
// config3: int8 = 0x0 (1 bytes)
// config4: const = 0x0 (4 bytes)
// sample_freq: int64 = 0x1 (8 bytes)
// sample_type: perf_sample_type = 0x14a69b (8 bytes)
// read_format: perf_read_format = 0x0 (8 bytes)
// disabled: int64 = 0x0 (0 bytes)
// inherit: int64 = 0x0 (0 bytes)
// pinned: int64 = 0x0 (0 bytes)
// exclusive: int64 = 0x0 (0 bytes)
// exclude_user: int64 = 0x0 (0 bytes)
// exclude_kernel: int64 = 0x0 (0 bytes)
// exclude_hv: int64 = 0x0 (0 bytes)
// exclude_idle: int64 = 0x0 (0 bytes)
// mmap: int64 = 0x0 (0 bytes)
// comm: int64 = 0x0 (0 bytes)
// freq: int64 = 0x0 (0 bytes)
// inherit_stat: int64 = 0x0 (0 bytes)
// enable_on_exec: int64 = 0x0 (0 bytes)
// task: int64 = 0x0 (0 bytes)
// watermark: int64 = 0x0 (0 bytes)
// precise_ip: int64 = 0x0 (0 bytes)
// mmap_data: int64 = 0x0 (0 bytes)
// sample_id_all: int64 = 0x0 (0 bytes)
// exclude_host: int64 = 0x0 (0 bytes)
// exclude_guest: int64 = 0x0 (0 bytes)
// exclude_callchain_kernel: int64 = 0x0 (0 bytes)
// exclude_callchain_user: int64 = 0x0 (0 bytes)
// mmap2: int64 = 0x0 (0 bytes)
// comm_exec: int64 = 0x0 (0 bytes)
// use_clockid: int64 = 0x0 (0 bytes)
// context_switch: int64 = 0x0 (0 bytes)
// write_backward: int64 = 0x0 (0 bytes)
// namespaces: int64 = 0x0 (0 bytes)
// ksymbol: int64 = 0x0 (0 bytes)
// bpf_event: int64 = 0x0 (0 bytes)
// aux_output: int64 = 0x0 (0 bytes)
// cgroup: int64 = 0x0 (0 bytes)
// text_poke: int64 = 0x0 (0 bytes)
// build_id: int64 = 0x0 (0 bytes)
// inherit_thread: int64 = 0x0 (0 bytes)
// remove_on_exec: int64 = 0x0 (0 bytes)
// sigtrap: int64 = 0x0 (0 bytes)
// __reserved_1: const = 0x0 (8 bytes)
// wakeup_events: int32 = 0x0 (4 bytes)
// bp_type: perf_bp_type = 0x0 (4 bytes)
// bp_config: union perf_bp_config {
// perf_bp: perf_bp {
// bp_addr: nil
// bp_len: perf_bp_lens = 0x1 (8 bytes)
// }
// }
// branch_sample_type: perf_branch_sample_type = 0x402d (8 bytes)
// sample_regs_user: int64 = 0xc844 (8 bytes)
// sample_stack_user: int32 = 0x410 (4 bytes)
// clockid: clock_type = 0x0 (4 bytes)
// sample_regs_intr: int64 = 0x7 (8 bytes)
// aux_watermark: int32 = 0x400002 (4 bytes)
// sample_max_stack: int16 = 0x0 (2 bytes)
// __reserved_2: const = 0x0 (2 bytes)
// aux_sample_size: int32 = 0x0 (4 bytes)
// __reserved_3: const = 0x0 (4 bytes)
// sig_data: int64 = 0x4 (8 bytes)
// }
// }
// pid: pid (resource)
// cpu: intptr = 0xffffffffffffffff (8 bytes)
// group: fd_perf (resource)
// flags: perf_flags = 0x2 (8 bytes)
// ]
// returns fd_perf
*(uint32_t*)0x200000002bc0 = 2;
*(uint32_t*)0x200000002bc4 = 0x80;
*(uint8_t*)0x200000002bc8 = 0x49;
*(uint8_t*)0x200000002bc9 = 1;
*(uint8_t*)0x200000002bca = 0;
*(uint8_t*)0x200000002bcb = 0;
*(uint32_t*)0x200000002bcc = 0;
*(uint64_t*)0x200000002bd0 = 1;
*(uint64_t*)0x200000002bd8 = 0x14a69b;
*(uint64_t*)0x200000002be0 = 0;
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 0, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 1, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 2, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 3, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 4, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 5, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 6, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 7, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 8, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 9, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 10, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 11, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 12, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 13, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 14, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 15, 2);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 17, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 18, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 19, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 20, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 21, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 22, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 23, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 24, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 25, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 26, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 27, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 28, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 29, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 30, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 31, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 32, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 33, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 34, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 35, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 36, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 37, 1);
STORE_BY_BITMASK(uint64_t, , 0x200000002be8, 0, 38, 26);
*(uint32_t*)0x200000002bf0 = 0;
*(uint32_t*)0x200000002bf4 = 0;
*(uint64_t*)0x200000002bf8 = 0;
*(uint64_t*)0x200000002c00 = 1;
*(uint64_t*)0x200000002c08 = 0x402d;
*(uint64_t*)0x200000002c10 = 0xc844;
*(uint32_t*)0x200000002c18 = 0x410;
*(uint32_t*)0x200000002c1c = 0;
*(uint64_t*)0x200000002c20 = 7;
*(uint32_t*)0x200000002c28 = 0x400002;
*(uint16_t*)0x200000002c2c = 0;
*(uint16_t*)0x200000002c2e = 0;
*(uint32_t*)0x200000002c30 = 0;
*(uint32_t*)0x200000002c34 = 0;
*(uint64_t*)0x200000002c38 = 4;
res = syscall(__NR_perf_event_open, /*attr=*/0x200000002bc0ul, /*pid=*/0, /*cpu=*/(intptr_t)-1, /*group=*/(intptr_t)-1, /*flags=PERF_FLAG_FD_OUTPUT*/2ul);
if (res != -1)
r[0] = res;
// mmap arguments: [
// addr: VMA[0x2000]
// len: len = 0x2000 (8 bytes)
// prot: mmap_prot = 0x1000008 (8 bytes)
// flags: mmap_flags = 0x13 (8 bytes)
// fd: fd (resource)
// offset: intptr = 0x0 (8 bytes)
// ]
inject_fault(12);
syscall(__NR_mmap, /*addr=*/0x200000ffe000ul, /*len=*/0x2000ul, /*prot=PROT_GROWSDOWN|PROT_SEM*/0x1000008ul, /*flags=MAP_SHARED_VALIDATE|MAP_FIXED*/0x13ul, /*fd=*/r[0], /*offset=*/0ul);
return 0;
}
Reply: **Analysis of the Bug**
The bug is a recursive locking deadlock in the `perf_events` subsystem.
When a user calls `mmap()` on a perf event file descriptor, the kernel invokes `perf_mmap()`. In this function, the `event->mmap_mutex` is acquired using a `scoped_guard`. After setting up the ring buffer, `perf_mmap()` calls `map_range()` to map the buffer pages into the user's VMA.
If `map_range()` fails (for example, due to memory allocation failures in `remap_pfn_range()` when allocating page tables under memory pressure, or due to fault injection as seen in the syzkaller reproducer), `perf_mmap()` attempts to clean up by calling `perf_mmap_close(vma)`.
Inside `perf_mmap_close()`, the code decrements the `event->mmap_count` reference counter. If this counter drops to zero (which it will if this was the first time the event was mmap'ed), `perf_mmap_close()` attempts to acquire `event->mmap_mutex` to safely detach the ring buffer:
```c
if (!refcount_dec_and_mutex_lock(&event->mmap_count, &event->mmap_mutex))
goto out_put;
```
However, because `perf_mmap()` is still holding `event->mmap_mutex` via the `scoped_guard`, the thread attempts to lock the same mutex twice. This results in a classic recursive locking deadlock. The thread will hang indefinitely in the `TASK_UNINTERRUPTIBLE` (D) state.
**Security Impact**
* **Exploitable:** False. The bug is a simple deadlock. It does not cause memory corruption, out-of-bounds accesses, or use-after-free conditions. It cannot be used for privilege escalation or arbitrary code execution.
* **Denial of Service:** True. Because the deadlocked thread is in an uninterruptible state, it cannot be killed (even with `SIGKILL`). Furthermore, the thread hangs while holding its own `mm->mmap_lock` for writing. An attacker can repeatedly spawn processes that trigger this deadlock, eventually exhausting system resources such as the PID space and memory, resulting in a local Denial of Service.
* **Accessible From Unprivileged Processes:** True. Unprivileged users are generally allowed to open certain perf events (like `PERF_TYPE_SOFTWARE` with `exclude_kernel=1`, depending on the `kernel.perf_event_paranoid` sysctl) and can call `mmap()` on them. By artificially creating memory pressure (e.g., via memory cgroups or exhausting available RAM), an unprivileged user can force `map_range()` to fail with `-ENOMEM`, reliably triggering the deadlock.
* **Accessible From User Namespaces:** True. The bug is reachable from unprivileged contexts, including within user namespaces.
The bug is specific to the local system call interface for `perf_events` and cannot be triggered remotely, via peripherals, or from a VM guest context.
|
| 6/2 |
2026/05/16 20:52 |
llm |
expert |
0m
Model:
Tokens:
input: 11516
output: 40
thoughts: 324
Thoughts: **Analyzing a Syzkaller Report on a Potential Recursive Lock in Perf**
Okay, so I've got this syzkaller report flashing a "WARNING: possible recursive locking detected" in `perf_mmap_close`. The stack trace immediately screams a problem: we're seeing `lock(&event->mmap_mutex)` followed *immediately* by `lock(&event->mmap_mutex)` within the same function, leading to a deadlock. That's a classic red flag.
The trace itself gives a good breadcrumb trail. We've got `perf_mmap_close` itself at the top, the likely culprit. It's calling into `perf_mmap`, which in turn leads us through the usual suspects for memory mapping: `vfs_mmap`, `mmap_file`, `__mmap_new_file_vma`, `__mmap_new_vma`, `__mmap_region`, and finally, down into `do_mmap`. This is useful, because the context of the problem is clear.
My next step is to dive right into `kernel/events/core.c` and take a look at the code for both `perf_mmap` and `perf_mmap_close`. I need to understand how `event->mmap_mutex` is being used in those functions and, more critically, *why* we're seeing the same mutex being acquired twice in `perf_mmap_close`. What's the control flow? Is there some conditional path that leads to this double-lock, or is it a logic error?
I need to understand the lifecycle of `event->mmap_mutex` in this context. Is it held across different calls, or is something else causing a race? I'm betting the root cause is in a subtle interaction in `perf_mmap` and how it interacts with the closing of the mapping.
|
| 7/2 |
2026/05/16 20:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/events/core.c IncludeLines:true Name:perf_mmap]
Results: map[SourceCode:7422: static int perf_mmap(struct file *file, struct vm_area_struct *vma)
7423: {
7424: struct perf_event *event = file->private_data;
7425: unsigned long vma_size, nr_pages;
7426: mapped_f mapped;
7427: int ret;
7428:
7429: /*
7430: * Don't allow mmap() of inherited per-task counters. This would
7431: * create a performance issue due to all children writing to the
7432: * same rb.
7433: */
7434: if (event->cpu == -1 && event->attr.inherit)
7435: return -EINVAL;
7436:
7437: if (!(vma->vm_flags & VM_SHARED))
7438: return -EINVAL;
7439:
7440: ret = security_perf_event_read(event);
7441: if (ret)
7442: return ret;
7443:
7444: vma_size = vma->vm_end - vma->vm_start;
7445: nr_pages = vma_size / PAGE_SIZE;
7446:
7447: if (nr_pages > INT_MAX)
7448: return -ENOMEM;
7449:
7450: if (vma_size != PAGE_SIZE * nr_pages)
7451: return -EINVAL;
7452:
7453: scoped_guard (mutex, &event->mmap_mutex) {
7454: /*
7455: * This relies on __pmu_detach_event() taking mmap_mutex after marking
7456: * the event REVOKED. Either we observe the state, or __pmu_detach_event()
7457: * will detach the rb created here.
7458: */
7459: if (event->state <= PERF_EVENT_STATE_REVOKED)
7460: return -ENODEV;
7461:
7462: if (vma->vm_pgoff == 0)
7463: ret = perf_mmap_rb(vma, event, nr_pages);
7464: else
7465: ret = perf_mmap_aux(vma, event, nr_pages);
7466: if (ret)
7467: return ret;
7468:
7469: /*
7470: * Since pinned accounting is per vm we cannot allow fork() to copy our
7471: * vma.
7472: */
7473: vm_flags_set(vma, VM_DONTCOPY | VM_DONTEXPAND | VM_DONTDUMP);
7474: vma->vm_ops = &perf_mmap_vmops;
7475:
7476: mapped = get_mapped(event, event_mapped);
7477: if (mapped)
7478: mapped(event, vma->vm_mm);
7479:
7480: /*
7481: * Try to map it into the page table. On fail, invoke
7482: * perf_mmap_close() to undo the above, as the callsite expects
7483: * full cleanup in this case and therefore does not invoke
7484: * vmops::close().
7485: */
7486: ret = map_range(event->rb, vma);
7487: if (ret)
7488: perf_mmap_close(vma);
7489: }
7490:
7491: return ret;
7492: }
SourceFile:kernel/events/core.c]
|
| 8/2 |
2026/05/16 20:52 |
llm |
expert |
0m
Model:
Tokens:
input: 13347
output: 42
thoughts: 62
Thoughts: **Analyzing the mmap Workflow**
Okay, so I'm digging into this code and trying to understand the `perf_mmap` function. Right off the bat, I see it grabbing `event->mmap_mutex` at line 7453. That's a critical point for synchronization, so I'll keep that in mind. Now, after doing whatever it needs to do, it calls `perf_mmap_close(vma)` at line 7488. Since this is the cleanup function, that call is important in understanding what happens when the mapping is no longer needed. So, let's dive into `perf_mmap_close` to see what's happening.
|
| 9/2 |
2026/05/16 20:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/events/core.c IncludeLines:true Name:perf_mmap_close]
Results: map[SourceCode:7014: /*
7015: * A buffer can be mmap()ed multiple times; either directly through the same
7016: * event, or through other events by use of perf_event_set_output().
7017: *
7018: * In order to undo the VM accounting done by perf_mmap() we need to destroy
7019: * the buffer here, where we still have a VM context. This means we need
7020: * to detach all events redirecting to us.
7021: */
7022: static void perf_mmap_close(struct vm_area_struct *vma)
7023: {
7024: struct perf_event *event = vma->vm_file->private_data;
7025: mapped_f unmapped = get_mapped(event, event_unmapped);
7026: struct perf_buffer *rb = ring_buffer_get(event);
7027: struct user_struct *mmap_user = rb->mmap_user;
7028: int mmap_locked = rb->mmap_locked;
7029: unsigned long size = perf_data_size(rb);
7030: bool detach_rest = false;
7031:
7032: /* FIXIES vs perf_pmu_unregister() */
7033: if (unmapped)
7034: unmapped(event, vma->vm_mm);
7035:
7036: /*
7037: * The AUX buffer is strictly a sub-buffer, serialize using aux_mutex
7038: * to avoid complications.
7039: */
7040: if (rb_has_aux(rb) && vma->vm_pgoff == rb->aux_pgoff &&
7041: refcount_dec_and_mutex_lock(&rb->aux_mmap_count, &rb->aux_mutex)) {
7042: /*
7043: * Stop all AUX events that are writing to this buffer,
7044: * so that we can free its AUX pages and corresponding PMU
7045: * data. Note that after rb::aux_mmap_count dropped to zero,
7046: * they won't start any more (see perf_aux_output_begin()).
7047: */
7048: perf_pmu_output_stop(event);
7049:
7050: /* now it's safe to free the pages */
7051: atomic_long_sub(rb->aux_nr_pages - rb->aux_mmap_locked, &mmap_user->locked_vm);
7052: atomic64_sub(rb->aux_mmap_locked, &vma->vm_mm->pinned_vm);
7053:
7054: /* this has to be the last one */
7055: rb_free_aux(rb);
7056: WARN_ON_ONCE(refcount_read(&rb->aux_refcount));
7057:
7058: mutex_unlock(&rb->aux_mutex);
7059: }
7060:
7061: if (refcount_dec_and_test(&rb->mmap_count))
7062: detach_rest = true;
7063:
7064: if (!refcount_dec_and_mutex_lock(&event->mmap_count, &event->mmap_mutex))
7065: goto out_put;
7066:
7067: ring_buffer_attach(event, NULL);
7068: mutex_unlock(&event->mmap_mutex);
7069:
7070: /* If there's still other mmap()s of this buffer, we're done. */
7071: if (!detach_rest)
7072: goto out_put;
7073:
7074: /*
7075: * No other mmap()s, detach from all other events that might redirect
7076: * into the now unreachable buffer. Somewhat complicated by the
7077: * fact that rb::event_lock otherwise nests inside mmap_mutex.
7078: */
7079: again:
7080: rcu_read_lock();
7081: list_for_each_entry_rcu(event, &rb->event_list, rb_entry) {
7082: if (!atomic_long_inc_not_zero(&event->refcount)) {
7083: /*
7084: * This event is en-route to free_event() which will
7085: * detach it and remove it from the list.
7086: */
7087: continue;
7088: }
7089: rcu_read_unlock();
7090:
7091: mutex_lock(&event->mmap_mutex);
7092: /*
7093: * Check we didn't race with perf_event_set_output() which can
7094: * swizzle the rb from under us while we were waiting to
7095: * acquire mmap_mutex.
7096: *
7097: * If we find a different rb; ignore this event, a next
7098: * iteration will no longer find it on the list. We have to
7099: * still restart the iteration to make sure we're not now
7100: * iterating the wrong list.
7101: */
7102: if (event->rb == rb)
7103: ring_buffer_attach(event, NULL);
7104:
7105: mutex_unlock(&event->mmap_mutex);
7106: put_event(event);
7107:
7108: /*
7109: * Restart the iteration; either we're on the wrong list or
7110: * destroyed its integrity by doing a deletion.
7111: */
7112: goto again;
7113: }
7114: rcu_read_unlock();
7115:
7116: /*
7117: * It could be there's still a few 0-ref events on the list; they'll
7118: * get cleaned up by free_event() -- they'll also still have their
7119: * ref on the rb and will free it whenever they are done with it.
7120: *
7121: * Aside from that, this buffer is 'fully' detached and unmapped,
7122: * undo the VM accounting.
7123: */
7124:
7125: atomic_long_sub((size >> PAGE_SHIFT) + 1 - mmap_locked,
7126: &mmap_user->locked_vm);
7127: atomic64_sub(mmap_locked, &vma->vm_mm->pinned_vm);
7128: free_uid(mmap_user);
7129:
7130: out_put:
7131: ring_buffer_put(rb); /* could be last */
7132: }
SourceFile:kernel/events/core.c]
|
| 10/2 |
2026/05/16 20:52 |
llm |
expert |
0m
Model:
Tokens:
input: 15543
output: 42
thoughts: 283
Thoughts: **Deadlock Detection: A Deep Dive into `perf_mmap` and `perf_mmap_close`**
Okay, so I'm looking at this `perf_mmap` function, and it's grabbing `event->mmap_mutex` with a `scoped_guard`, which is good practice. Then, it calls `map_range`, and if that fails, it calls `perf_mmap_close(vma)`. The red flag goes up immediately when I see what `perf_mmap_close` does. It tries to do `refcount_dec_and_mutex_lock` on `event->mmap_mutex`. And here's the kicker: `event` *is* the same `event` from `perf_mmap` via `vma->vm_file->private_data`.
My initial thought was, "Uh oh, potential deadlock!" We've got `perf_mmap` holding the mutex, and then `perf_mmap_close` tries to grab it again. It seems pretty obvious. But, wait, `refcount_dec_and_mutex_lock` is smart. It only locks the mutex if the refcount drops to zero. That's a crucial detail. I need to figure out if `perf_mmap` *increments* `event->mmap_count`. I need to track where that count is being adjusted. Let me quickly scan `perf_mmap_rb` and `perf_mmap_aux` to be sure. This refcount is the key. Is it being incremented? Is it being decremented? I need to understand that flow.
|
| 11/2 |
2026/05/16 20:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/events/core.c IncludeLines:true Name:perf_mmap_rb]
Results: map[SourceCode:7268: static int perf_mmap_rb(struct vm_area_struct *vma, struct perf_event *event,
7269: unsigned long nr_pages)
7270: {
7271: long extra = 0, user_extra = nr_pages;
7272: struct perf_buffer *rb;
7273: int rb_flags = 0;
7274:
7275: nr_pages -= 1;
7276:
7277: /*
7278: * If we have rb pages ensure they're a power-of-two number, so we
7279: * can do bitmasks instead of modulo.
7280: */
7281: if (nr_pages != 0 && !is_power_of_2(nr_pages))
7282: return -EINVAL;
7283:
7284: WARN_ON_ONCE(event->ctx->parent_ctx);
7285:
7286: if (event->rb) {
7287: if (data_page_nr(event->rb) != nr_pages)
7288: return -EINVAL;
7289:
7290: /*
7291: * If this event doesn't have mmap_count, we're attempting to
7292: * create an alias of another event's mmap(); this would mean
7293: * both events will end up scribbling the same user_page;
7294: * which makes no sense.
7295: */
7296: if (!refcount_read(&event->mmap_count))
7297: return -EBUSY;
7298:
7299: if (refcount_inc_not_zero(&event->rb->mmap_count)) {
7300: /*
7301: * Success -- managed to mmap() the same buffer
7302: * multiple times.
7303: */
7304: perf_mmap_account(vma, user_extra, extra);
7305: refcount_inc(&event->mmap_count);
7306: return 0;
7307: }
7308:
7309: /*
7310: * Raced against perf_mmap_close()'s
7311: * refcount_dec_and_mutex_lock() remove the
7312: * event and continue as if !event->rb
7313: */
7314: ring_buffer_attach(event, NULL);
7315: }
7316:
7317: if (!perf_mmap_calc_limits(vma, &user_extra, &extra))
7318: return -EPERM;
7319:
7320: if (vma->vm_flags & VM_WRITE)
7321: rb_flags |= RING_BUFFER_WRITABLE;
7322:
7323: rb = rb_alloc(nr_pages,
7324: event->attr.watermark ? event->attr.wakeup_watermark : 0,
7325: event->cpu, rb_flags);
7326:
7327: if (!rb)
7328: return -ENOMEM;
7329:
7330: refcount_set(&rb->mmap_count, 1);
7331: rb->mmap_user = get_current_user();
7332: rb->mmap_locked = extra;
7333:
7334: ring_buffer_attach(event, rb);
7335:
7336: perf_event_update_time(event);
7337: perf_event_init_userpage(event);
7338: perf_event_update_userpage(event);
7339:
7340: perf_mmap_account(vma, user_extra, extra);
7341: refcount_set(&event->mmap_count, 1);
7342:
7343: return 0;
7344: }
SourceFile:kernel/events/core.c]
|
| 12/2 |
2026/05/16 20:52 |
llm |
expert |
0m
Model:
Tokens:
input: 17373
output: 39
thoughts: 286
Thoughts: **Deadlock Deep Dive: `perf_mmap` and `perf_mmap_close`**
Okay, so I'm digging into this `perf_mmap` issue, and I've got a pretty clear picture of the problem. My initial thought process was: `perf_mmap_rb` increases or sets `event->mmap_count` to 1. Then it calls `map_range` on the relevant `rb` and `vma`. If `map_range` fails, `perf_mmap_close` gets called. Now, `perf_mmap_close` then decrements `event->mmap_count` and attempts to lock `event->mmap_mutex`. If `event->mmap_count` was at one to begin with, this will drop to zero and we *expect* `perf_mmap_close` to lock `event->mmap_mutex`. But, `event->mmap_mutex` is *already* held by `perf_mmap`! That screams deadlock, right?
Then I went back and realized I was missing a key element: `perf_mmap` holds `event->mmap_mutex` using a `scoped_guard(mutex, &event->mmap_mutex)`. The issue is, when `map_range` fails, the system calls `perf_mmap_close(vma)` *while still holding* `event->mmap_mutex`. And, as I suspected, `perf_mmap_close` then *tries* to grab that same `event->mmap_mutex` again. Boom! Classic deadlock scenario. `perf_mmap` has the mutex, `perf_mmap_close` is blocked waiting for it.
Now, I need to check the exact failure conditions of `map_range` to understand the conditions that trigger this whole mess. Gotta find what exactly causes this deadlock scenario!
|
| 13/2 |
2026/05/16 20:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/events/core.c IncludeLines:true Name:map_range]
Results: map[SourceCode:7156: static int map_range(struct perf_buffer *rb, struct vm_area_struct *vma)
7157: {
7158: unsigned long nr_pages = vma_pages(vma);
7159: int err = 0;
7160: unsigned long pagenum;
7161:
7162: /*
7163: * We map this as a VM_PFNMAP VMA.
7164: *
7165: * This is not ideal as this is designed broadly for mappings of PFNs
7166: * referencing memory-mapped I/O ranges or non-system RAM i.e. for which
7167: * !pfn_valid(pfn).
7168: *
7169: * We are mapping kernel-allocated memory (memory we manage ourselves)
7170: * which would more ideally be mapped using vm_insert_page() or a
7171: * similar mechanism, that is as a VM_MIXEDMAP mapping.
7172: *
7173: * However this won't work here, because:
7174: *
7175: * 1. It uses vma->vm_page_prot, but this field has not been completely
7176: * setup at the point of the f_op->mmp() hook, so we are unable to
7177: * indicate that this should be mapped CoW in order that the
7178: * mkwrite() hook can be invoked to make the first page R/W and the
7179: * rest R/O as desired.
7180: *
7181: * 2. Anything other than a VM_PFNMAP of valid PFNs will result in
7182: * vm_normal_page() returning a struct page * pointer, which means
7183: * vm_ops->page_mkwrite() will be invoked rather than
7184: * vm_ops->pfn_mkwrite(), and this means we have to set page->mapping
7185: * to work around retry logic in the fault handler, however this
7186: * field is no longer allowed to be used within struct page.
7187: *
7188: * 3. Having a struct page * made available in the fault logic also
7189: * means that the page gets put on the rmap and becomes
7190: * inappropriately accessible and subject to map and ref counting.
7191: *
7192: * Ideally we would have a mechanism that could explicitly express our
7193: * desires, but this is not currently the case, so we instead use
7194: * VM_PFNMAP.
7195: *
7196: * We manage the lifetime of these mappings with internal refcounts (see
7197: * perf_mmap_open() and perf_mmap_close()) so we ensure the lifetime of
7198: * this mapping is maintained correctly.
7199: */
7200: for (pagenum = 0; pagenum < nr_pages; pagenum++) {
7201: unsigned long va = vma->vm_start + PAGE_SIZE * pagenum;
7202: struct page *page = perf_mmap_to_page(rb, vma->vm_pgoff + pagenum);
7203:
7204: if (page == NULL) {
7205: err = -EINVAL;
7206: break;
7207: }
7208:
7209: /* Map readonly, perf_mmap_pfn_mkwrite() called on write fault. */
7210: err = remap_pfn_range(vma, va, page_to_pfn(page), PAGE_SIZE,
7211: vm_get_page_prot(vma->vm_flags & ~VM_SHARED));
7212: if (err)
7213: break;
7214: }
7215:
7216: #ifdef CONFIG_MMU
7217: /* Clear any partial mappings on error. */
7218: if (err)
7219: zap_page_range_single(vma, vma->vm_start, nr_pages * PAGE_SIZE, NULL);
7220: #endif
7221:
7222: return err;
7223: }
SourceFile:kernel/events/core.c]
|
| 14/2 |
2026/05/16 20:52 |
llm |
expert |
0m
Model:
Tokens:
input: 19317
output: 44
thoughts: 936
Thoughts: **Analyzing a Potential Deadlock in perf_mmap**
Okay, so I'm looking at this `perf_mmap` issue, and it seems like there's a nasty deadlock scenario. Let's break it down. The core problem is that `map_range` can bail out if `remap_pfn_range` fails, and `remap_pfn_range` can fail for a few reasons, including fault injection, memory exhaustion, or an invalid range. In the syzkaller reproducer, fault injection (`inject_fault(12)`) triggers the failure *before* the `mmap`. So `remap_pfn_range` returns an error.
The important bit is that, after the `remap_pfn_range` failure, `perf_mmap` calls `perf_mmap_close(vma)` *while holding `event->mmap_mutex`*. `perf_mmap_close` then tries to re-acquire that same mutex. Boom. Deadlock.
Now, is this exploitable? A straightforward deadlock in `perf_mmap` will hang the process calling `mmap`. If it's an unprivileged process, it's just hanging itself. But, does it hold any system-wide locks? Let's see... It holds the process's own `mm->mmap_lock` (acquired via `mmap_write_lock_killable` in `vm_mmap_pgoff`).
Initially, I thought, if it's just the process's `mmap_lock`, it only affects that process. But then, the `event->mmap_mutex` is a mutex specific to a `perf_event`. Okay, if multiple processes share the same `perf_event`, then maybe *they* are blocked by `event->mmap_mutex`, but that's just a local deadlock.
Wait, are there any *other* locks involved? Let's check the stack trace! Ah, there it is! The stack trace confirms the deadlock locks: `mm->mmap_lock` and `event->mmap_mutex`.
Okay, so, it's the process's `mm->mmap_lock` *and* the event's mutex. Since `mm` is the memory descriptor of the process itself, hanging while holding `mmap_lock` would prevent other threads in the same process from doing memory operations. And that process *cannot* be killed if it's holding `mmap_lock` in an uninterruptible state.
Wait a second, the default `mutex_lock` is uninterruptible... unless it's `mutex_lock_interruptible` or `mutex_lock_killable`. Here, `refcount_dec_and_mutex_lock` uses `mutex_lock`, so it *is* uninterruptible! This means that thread is stuck in the `D` state (TASK_UNINTERRUPTIBLE), which means it *cannot* be killed by `SIGKILL`.
Got it. So, we have an unkillable process! The process will live forever, leaking its resources (memory, task struct, etc.). An unprivileged user could repeatedly trigger this deadlock by creating new processes, eventually exhausting the system resources (PID space, memory, etc.). That's a classic Denial-of-Service (DoS) vulnerability. We have a potentially severe issue here!
Hang on... can this happen *without* fault injection? Could `map_range` fail some other way? `remap_pfn_range` allocates page tables. Under memory pressure, this could fail with `-ENOMEM`. We also need to check `perf_mmap_to_page` for null returns.
|
| 15/2 |
2026/05/16 20:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/events/core.c IncludeLines:true Name:perf_mmap_to_page]
Results: map[SourceCode: 959: struct page *
960: perf_mmap_to_page(struct perf_buffer *rb, unsigned long pgoff)
961: {
962: if (rb->aux_nr_pages) {
963: /* above AUX space */
964: if (pgoff > rb->aux_pgoff + rb->aux_nr_pages)
965: return NULL;
966:
967: /* AUX space */
968: if (pgoff >= rb->aux_pgoff) {
969: int aux_pgoff = array_index_nospec(pgoff - rb->aux_pgoff, rb->aux_nr_pages);
970: return virt_to_page(rb->aux_pages[aux_pgoff]);
971: }
972: }
973:
974: return __perf_mmap_to_page(rb, pgoff);
975: }
SourceFile:kernel/events/ring_buffer.c]
|
| 16/2 |
2026/05/16 20:52 |
llm |
expert |
0m
Model:
Tokens:
input: 21407
output: 47
thoughts: 233
Thoughts: **Analyzing the Page Offset Calculation in Perf Events mmap**
Okay, so I'm digging into this `perf_mmap` function, and I see a potential issue with how we handle the page offset within the ring buffer. Specifically, I'm concerned about the scenarios where `perf_mmap_to_page` might return NULL. The core of my concern boils down to the page offset exceeding the allocated page range.
Let me break it down. `perf_mmap_to_page` *can* return NULL if the provided `pgoff` exceeds the bounds defined by `rb->aux_pgoff + rb->aux_nr_pages`. I understand that. But then I check how `perf_mmap` itself calls these functions:
```c
if (vma->vm_pgoff == 0)
ret = perf_mmap_rb(vma, event, nr_pages);
else
ret = perf_mmap_aux(vma, event, nr_pages);
```
My logic then focuses on how `perf_mmap_rb` allocates the ring buffer (`rb`). It seems like we allocate the `rb` with `nr_pages - 1` *data* pages, right? That's what I am seeing. So, the *total* number of pages is still `nr_pages`. Within the ring buffer, the `map_range` function iterates, and the loop variable `pagenum` goes from 0 to `nr_pages - 1`. The `vma->vm_pgoff` is set to 0. So, `pgoff` also goes from 0 to `nr_pages - 1`. This makes sense. It seems that `__perf_mmap_to_page` is, in fact, handling `pgoff` values up to `nr_pages - 1`.
Therefore, the condition where `perf_mmap_to_page` would return NULL due to `pgoff` being out of bounds *shouldn't* occur in the `perf_mmap_rb` case. However, I want to double-check this, especially in the `perf_mmap_aux` scenario. Something feels off, so I should see if there are assumptions I'm overlooking or if this is an edge case I need to document.
|
| 17/2 |
2026/05/16 20:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/events/ring_buffer.c IncludeLines:true Name:__perf_mmap_to_page]
Results: map[SourceCode: 804: static struct page *
805: __perf_mmap_to_page(struct perf_buffer *rb, unsigned long pgoff)
806: {
807: if (pgoff > rb->nr_pages)
808: return NULL;
809:
810: if (pgoff == 0)
811: return virt_to_page(rb->user_page);
812:
813: return virt_to_page(rb->data_pages[pgoff - 1]);
814: }
SourceFile:kernel/events/ring_buffer.c]
|
| 18/2 |
2026/05/16 20:52 |
llm |
expert |
0m
Model:
Tokens:
input: 22392
output: 42
thoughts: 34
Thoughts: Okay, here's my interpretation of those thoughts, presented as a personal thought process, as requested:
**Navigating the Memory Mapping Landscape**
Alright, let's see... so I'm thinking about `perf_mmap_to_page`. From what I understand, this function, when given a valid `pgoff`, should *never* return NULL. That's a key assumption, and it has implications for error handling in the surrounding code. It means I can rely on a valid page being returned in the "normal" successful case. Makes things simpler to reason about, really.
Now, I need to consider `perf_mmap_aux` as well. That function's behavior in relation to potential errors is another angle I should explore. Does it follow a similar pattern? Does it fail in a way that aligns with my expectations? Understanding the specifics of its error cases is important because both mmap functions play an important role in how I interpret performance data. I need to make sure I am accounting for edge cases in both functions.
|
| 19/2 |
2026/05/16 20:52 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/events/core.c IncludeLines:true Name:perf_mmap_aux]
Results: map[SourceCode:7346: static int perf_mmap_aux(struct vm_area_struct *vma, struct perf_event *event,
7347: unsigned long nr_pages)
7348: {
7349: long extra = 0, user_extra = nr_pages;
7350: u64 aux_offset, aux_size;
7351: struct perf_buffer *rb;
7352: int ret, rb_flags = 0;
7353:
7354: rb = event->rb;
7355: if (!rb)
7356: return -EINVAL;
7357:
7358: guard(mutex)(&rb->aux_mutex);
7359:
7360: /*
7361: * AUX area mapping: if rb->aux_nr_pages != 0, it's already
7362: * mapped, all subsequent mappings should have the same size
7363: * and offset. Must be above the normal perf buffer.
7364: */
7365: aux_offset = READ_ONCE(rb->user_page->aux_offset);
7366: aux_size = READ_ONCE(rb->user_page->aux_size);
7367:
7368: if (aux_offset < perf_data_size(rb) + PAGE_SIZE)
7369: return -EINVAL;
7370:
7371: if (aux_offset != vma->vm_pgoff << PAGE_SHIFT)
7372: return -EINVAL;
7373:
7374: /* already mapped with a different offset */
7375: if (rb_has_aux(rb) && rb->aux_pgoff != vma->vm_pgoff)
7376: return -EINVAL;
7377:
7378: if (aux_size != nr_pages * PAGE_SIZE)
7379: return -EINVAL;
7380:
7381: /* already mapped with a different size */
7382: if (rb_has_aux(rb) && rb->aux_nr_pages != nr_pages)
7383: return -EINVAL;
7384:
7385: if (!is_power_of_2(nr_pages))
7386: return -EINVAL;
7387:
7388: if (!refcount_inc_not_zero(&rb->mmap_count))
7389: return -EINVAL;
7390:
7391: if (rb_has_aux(rb)) {
7392: refcount_inc(&rb->aux_mmap_count);
7393:
7394: } else {
7395: if (!perf_mmap_calc_limits(vma, &user_extra, &extra)) {
7396: refcount_dec(&rb->mmap_count);
7397: return -EPERM;
7398: }
7399:
7400: WARN_ON(!rb && event->rb);
7401:
7402: if (vma->vm_flags & VM_WRITE)
7403: rb_flags |= RING_BUFFER_WRITABLE;
7404:
7405: ret = rb_alloc_aux(rb, event, vma->vm_pgoff, nr_pages,
7406: event->attr.aux_watermark, rb_flags);
7407: if (ret) {
7408: refcount_dec(&rb->mmap_count);
7409: return ret;
7410: }
7411:
7412: refcount_set(&rb->aux_mmap_count, 1);
7413: rb->aux_mmap_locked = extra;
7414: }
7415:
7416: perf_mmap_account(vma, user_extra, extra);
7417: refcount_inc(&event->mmap_count);
7418:
7419: return 0;
7420: }
SourceFile:kernel/events/core.c]
|
| 20/2 |
2026/05/16 20:52 |
llm |
expert |
0m
Model:
Tokens:
input: 23840
output: 41
thoughts: 494
Thoughts: **Analyzing a Potential Deadlock in Perf Event Mmap Handling**
Okay, let's break this down. I'm looking at the `perf_mmap_aux` function and how it interacts with the mmap count. I see that it increments `event->mmap_count`. The core issue appears when `map_range` fails. If that happens, `perf_mmap_close` is going to be called, which decrements the count and *then* tries to lock `event->mmap_mutex`.
The critical piece here is `refcount_dec_and_mutex_lock`. If the count was initially 1 (meaning it's a "new" buffer), this drops it to zero and *then* attempts to take the mutex. If the count was greater than 1, it just decrements the count.
Now, rewind to `perf_mmap_rb`. If this is a new buffer, `event->mmap_count` gets set to 1. If it's an existing one, it gets incremented. The scenario I'm worried about is this: a *new* buffer, `map_range` fails, and `perf_mmap_close` gets called. The count drops to 0, and then `refcount_dec_and_mutex_lock` attempts to lock that mutex.
Hold on... the mutex is *already* locked by `perf_mmap`. Bingo. Deadlock! `perf_mmap` is holding the mutex, `perf_mmap_close` wants it, but the count is at zero, so it tries to acquire it, but is blocked, but it will never be released, because the first thread will never release it.
Now, the important question is: can `map_range` fail without something extreme like fault injection? Ah, yes, `remap_pfn_range` can return `-ENOMEM`. Is `-ENOMEM` reachable from unprivileged user space? Yes, exhausting memory or hitting a limit is a possibility. But `remap_pfn_range` allocates page tables; so mapping a large range could trigger a memory allocation failure, even with the size limitation in `perf_mmap`.
Is there any other way `remap_pfn_range` can fail? I should really check the actual code for that. But `-ENOMEM` feels like the likely culprit for triggering this, especially under heavy load or with a compromised system attempting to starve the perf event ring buffer.
|
| 21/2 |
2026/05/16 20:53 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c IncludeLines:true Name:remap_pfn_range]
Results: map[SourceCode:3135: /**
3136: * remap_pfn_range - remap kernel memory to userspace
3137: * @vma: user vma to map to
3138: * @addr: target page aligned user address to start at
3139: * @pfn: page frame number of kernel physical memory address
3140: * @size: size of mapping area
3141: * @prot: page protection flags for this mapping
3142: *
3143: * Note: this is only safe if the mm semaphore is held when called.
3144: *
3145: * Return: %0 on success, negative error code otherwise.
3146: */
3147: int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
3148: unsigned long pfn, unsigned long size, pgprot_t prot)
3149: {
3150: int err;
3151:
3152: err = remap_pfn_range_prepare_vma(vma, addr, pfn, size);
3153: if (err)
3154: return err;
3155:
3156: return do_remap_pfn_range(vma, addr, pfn, size, prot);
3157: }
SourceFile:mm/memory.c]
|
| 22/2 |
2026/05/16 20:53 |
llm |
expert |
1m
Model:
Tokens:
input: 25301
output: 46
thoughts: 30
Thoughts: Okay, here's my take on those functions, putting myself in the role of the thinker:
**Dissecting Memory Remapping: `remap_pfn_range_prepare_vma` and `do_remap_pfn_range`**
Alright, let's dive into this memory remap situation. I need to understand how these two functions, `remap_pfn_range_prepare_vma` and `do_remap_pfn_range`, work together. I'm assuming, and I could be wrong, that `remap_pfn_range_prepare_vma` is responsible for setting the stage, perhaps validating the Virtual Memory Area (VMA) and preparing it for the actual remapping. It's likely involved with things like adjusting permissions, checking for overlaps, and maybe even setting up some initial page table entries or checking for the availability of the physical pages that are being mapped. This feels like the "housekeeping" part of the process, ensuring everything is safe and sound before the real work begins.
Then, there's `do_remap_pfn_range`. I'd wager this is the heavy lifter. This function's probably where the actual mapping of physical page frame numbers (PFNs) to virtual addresses takes place. This is where the page table entries are ultimately manipulated to create the new virtual-to-physical address mapping. I imagine this function is deeply intertwined with the architecture-specific MMU operations, so I'll need to drill down into the specific implementation details for the target platform to get the full picture. Crucially, I'm expecting this function to be responsible for handling all the memory management complexities, from PTE updates to TLB invalidation and cache coherency, to ensure that the memory map is established correctly and that the relevant caches are flushed. It's likely this function uses a carefully crafted set of lock and memory barrier sequences to avoid race conditions.
My assumption is that there's a delicate dance here, a handoff between those functions, in which `remap_pfn_range_prepare_vma` validates and makes ready and `do_remap_pfn_range` actually completes the remap itself. I'll need to look at the function call structure and how parameters and data are passed between them to confirm my understanding and identify any areas of potential concurrency hazards. I'll focus on how each function handles errors and whether there are any potential vulnerabilities, especially considering this operation involves direct manipulation of the page tables and therefore, memory itself.
|
| 23/2 |
2026/05/16 20:54 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c IncludeLines:true Name:remap_pfn_range_prepare_vma]
Results: map[SourceCode:3120: static int remap_pfn_range_prepare_vma(struct vm_area_struct *vma, unsigned long addr,
3121: unsigned long pfn, unsigned long size)
3122: {
3123: unsigned long end = addr + PAGE_ALIGN(size);
3124: int err;
3125:
3126: err = get_remap_pgoff(is_cow_mapping(vma->vm_flags), addr, end,
3127: vma->vm_start, vma->vm_end, pfn, &vma->vm_pgoff);
3128: if (err)
3129: return err;
3130:
3131: vma_set_flags_mask(vma, VMA_REMAP_FLAGS);
3132: return 0;
3133: }
SourceFile:mm/memory.c]
|
| 24/2 |
2026/05/16 20:54 |
llm |
expert |
0m
Model:
Tokens:
input: 26182
output: 42
thoughts: 14
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm an expert in this field, presented as a first-person summary:
**Checking `get_remap_pgoff`**
Alright, time to dive in. Let's start by zeroing in on `get_remap_pgoff`. This function is critical; it's the one that determines the offset for remapping page table entries, right? I need to quickly refresh my understanding of how this particular function is behaving in this context. I'll need to think about the intricacies of virtual address translation, physical address mapping, and how the page table entries are being manipulated. Specifically, I should consider the current architecture, memory layout, and what the overall goal of this function is. Is it dealing with a specific type of memory region or a general-purpose remapping procedure? I'll probably also want to review any recent changes to this code. I need to get a solid grasp of `get_remap_pgoff`βs behavior before I continue, to ensure that everything is in order.
|
| 25/2 |
2026/05/16 20:54 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c IncludeLines:true Name:get_remap_pgoff]
Results: map[SourceCode:2960: static int get_remap_pgoff(bool is_cow, unsigned long addr,
2961: unsigned long end, unsigned long vm_start, unsigned long vm_end,
2962: unsigned long pfn, pgoff_t *vm_pgoff_p)
2963: {
2964: /*
2965: * There's a horrible special case to handle copy-on-write
2966: * behaviour that some programs depend on. We mark the "original"
2967: * un-COW'ed pages by matching them up with "vma->vm_pgoff".
2968: * See vm_normal_page() for details.
2969: */
2970: if (is_cow) {
2971: if (addr != vm_start || end != vm_end)
2972: return -EINVAL;
2973: *vm_pgoff_p = pfn;
2974: }
2975:
2976: return 0;
2977: }
SourceFile:mm/memory.c]
|
| 26/2 |
2026/05/16 20:54 |
llm |
expert |
0m
Model:
Tokens:
input: 26787
output: 43
thoughts: 76
Thoughts: Here's my analysis of that code snippet:
**Diving into the `perf_mmap` Function**
Okay, so I'm looking at `perf_mmap` and the first thing I see is that `vma->vm_flags` includes `VM_SHARED`. Interesting. That immediately tells me this isn't a copy-on-write mapping because `is_cow_mapping(vma->vm_flags)` will be false.
Next, I'm checking the return value of `get_remap_pgoff`. It's returning 0, which is what I'd expect given the shared flag. This suggests a straightforward, non-COW remapping scenario.
Now, the natural follow-up is to understand what's happening with `do_remap_pfn_range`. I need to figure out how that function is handling the actual physical page frame number (PFN) mapping in this context, given that it's a shared, non-COW mmap. That's where the real work must be done. I need to understand what `do_remap_pfn_range` is doing to map the underlying physical memory into the process's virtual address space in a shared manner. This is the core of how this shared memory area is set up.
|
| 27/2 |
2026/05/16 20:54 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c IncludeLines:true Name:do_remap_pfn_range]
Results: map[SourceCode:3095: static int do_remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
3096: unsigned long pfn, unsigned long size, pgprot_t prot)
3097: {
3098: return remap_pfn_range_track(vma, addr, pfn, size, prot);
3099: }
SourceFile:mm/memory.c]
|
| 28/2 |
2026/05/16 20:54 |
llm |
expert |
0m
Model:
Tokens:
input: 27315
output: 43
thoughts: 15
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience, presented in the first person:
**Diving into `remap_pfn_range_track`**
Alright, time to refresh my memory on `remap_pfn_range_track`. I need to revisit its purpose and behavior. Given its name, I'm assuming it has something to do with tracking page frame numbers (PFNs) during a remapping operation, likely related to memory management. It's probably invoked when dealing with memory regions that are being made accessible, either from user-space or from the kernel. I suspect there are scenarios where tracking is vital for some kind of management. My guess is it might be related to things like managing coherency, dealing with copy-on-write, or maybe even implementing some form of page fault optimization. I should look into what subsystems utilize this function to understand what use cases it supports. I need to understand its parameters β the virtual address range, the PFN range, and any flags that are involved. These details will paint a clearer picture of how it integrates into the memory management lifecycle. Ultimately, I'll need to figure out its role in the bigger picture, and whether it's related to some more advanced memory management strategy. Let's delve in and see what's actually happening under the hood.
|
| 29/2 |
2026/05/16 20:54 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c IncludeLines:true Name:remap_pfn_range_track]
Results: map[SourceCode:3058: static int remap_pfn_range_track(struct vm_area_struct *vma, unsigned long addr,
3059: unsigned long pfn, unsigned long size, pgprot_t prot)
3060: {
3061: struct pfnmap_track_ctx *ctx = NULL;
3062: int err;
3063:
3064: size = PAGE_ALIGN(size);
3065:
3066: /*
3067: * If we cover the full VMA, we'll perform actual tracking, and
3068: * remember to untrack when the last reference to our tracking
3069: * context from a VMA goes away. We'll keep tracking the whole pfn
3070: * range even during VMA splits and partial unmapping.
3071: *
3072: * If we only cover parts of the VMA, we'll only setup the cachemode
3073: * in the pgprot for the pfn range.
3074: */
3075: if (addr == vma->vm_start && addr + size == vma->vm_end) {
3076: if (vma->pfnmap_track_ctx)
3077: return -EINVAL;
3078: ctx = pfnmap_track_ctx_alloc(pfn, size, &prot);
3079: if (IS_ERR(ctx))
3080: return PTR_ERR(ctx);
3081: } else if (pfnmap_setup_cachemode(pfn, size, &prot)) {
3082: return -EINVAL;
3083: }
3084:
3085: err = remap_pfn_range_notrack(vma, addr, pfn, size, prot);
3086: if (ctx) {
3087: if (err)
3088: kref_put(&ctx->kref, pfnmap_track_ctx_release);
3089: else
3090: vma->pfnmap_track_ctx = ctx;
3091: }
3092: return err;
3093: }
SourceFile:mm/memory.c]
|
| 30/2 |
2026/05/16 20:54 |
llm |
expert |
0m
Model:
Tokens:
input: 28270
output: 42
thoughts: 2528
Thoughts: **Deadlock in perf_mmap due to -ENOMEM**
Okay, so here's the breakdown of this potential deadlock in `perf_mmap`. My initial thought is that `remap_pfn_range` can fail with `-ENOMEM`, which can happen due to memory pressure, and this could be triggered by an unprivileged user by creating numerous mappings and exhausting memory. This can lead to problems within the `perf_mmap` function.
The core issue is that when `remap_pfn_range` fails in the first mmap and returns -ENOMEM, the `perf_mmap_close` function is called and causes a deadlock. Here's how:
1. `perf_mmap` acquires `event->mmap_mutex`.
2. `map_range` is then called.
3. If `map_range` fails in first mmap, `perf_mmap_close` gets called while holding the mutex.
4. Within `perf_mmap_close`, it decrements `event->mmap_count`, from 1 to 0 (since it is the first mmap) and attempts to acquire the `event->mmap_mutex` again.
5. This creates a deadlock. The process hangs in the 'D' state, holding `mm->mmap_lock`.
Since an unprivileged user can call `perf_event_open`, they can trigger this condition, potentially leading to a local DoS. It seems the event type doesn't matter, as the vulnerability is in `perf_mmap`, which is independent of the event type, though.
Let's double-check how these values are handled:
`event->mmap_count` is set to 1 when the event is first mmap'ed. If the first mmap fails, `perf_mmap_close` decrements it to 0, causing the deadlock.
For non-zero `vma->vm_pgoff`, which triggers `perf_mmap_aux`, the increment and decrement of `event->mmap_count` don't lead to a deadlock unless the first mmap also fails in the `map_range`.
Okay, now it seems the `remap_pfn_range` function is called page by page within `map_range` using `remap_pfn_range`. The code shows the first page (pagenum==0) can trigger the failure.
The condition `if (addr == vma->vm_start && addr + size == vma->vm_end)` is false, and `pfnmap_setup_cachemode` is called, which can lead to other failures, but `-ENOMEM` would seem the most likely.
So the vulnerability comes in `perf_mmap` when the user triggers the creation of the first mmap and `map_range` fails during the mmap and it returns -ENOMEM to the calling code.
|
| 31/2 |
2026/05/16 20:54 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c IncludeLines:true Name:pfnmap_setup_cachemode]
Results: map[SourceCode: 908: int pfnmap_setup_cachemode(unsigned long pfn, unsigned long size, pgprot_t *prot)
909: {
910: resource_size_t paddr = (resource_size_t)pfn << PAGE_SHIFT;
911: enum page_cache_mode pcm;
912:
913: if (!pat_enabled())
914: return 0;
915:
916: pcm = lookup_memtype(paddr);
917:
918: /* Check memtype for the remaining pages */
919: while (size > PAGE_SIZE) {
920: size -= PAGE_SIZE;
921: paddr += PAGE_SIZE;
922: if (pcm != lookup_memtype(paddr))
923: return -EINVAL;
924: }
925:
926: pgprot_set_cachemode(prot, pcm);
927: return 0;
928: }
SourceFile:arch/x86/mm/pat/memtype.c]
|
| 32/2 |
2026/05/16 20:54 |
llm |
expert |
0m
Model:
Tokens:
input: 31810
output: 44
thoughts: 110
Thoughts: **My Analysis of Memory Mapping in the Perf Event System**
Okay, let's break this down. `pfnmap_setup_cachemode` is returning 0 on an x86 architecture, but only when the Page Attribute Table (PAT) is enabled, and the memory types are a match. Interesting. Now, `perf_mmap_to_page` is providing a page, which was, in turn, allocated by the `perf_event` subsystem. These are just standard RAM pages, nothing fancy. Digging a bit deeper, `lookup_memtype` is querying the memory type for this normal RAM, and itβs predictably returning `_PAGE_CACHE_MODE_WB` β write-back, which makes perfect sense. Given all of that, it means `pfnmap_setup_cachemode` is, as expected, giving us that 0 return value. Finally, after all this work, we're calling `remap_pfn_range_notrack`. This suggests we're mapping physical page frames into the user address space. This makes sense within the context of performance monitoring and the need to access the perf event ring buffer directly.
|
| 33/2 |
2026/05/16 20:55 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c IncludeLines:true Name:remap_pfn_range_notrack]
Results: map[SourceCode:3008: /*
3009: * Variant of remap_pfn_range that does not call track_pfn_remap. The caller
3010: * must have pre-validated the caching bits of the pgprot_t.
3011: */
3012: static int remap_pfn_range_notrack(struct vm_area_struct *vma, unsigned long addr,
3013: unsigned long pfn, unsigned long size, pgprot_t prot)
3014: {
3015: int error = remap_pfn_range_internal(vma, addr, pfn, size, prot);
3016:
3017: if (!error)
3018: return 0;
3019:
3020: /*
3021: * A partial pfn range mapping is dangerous: it does not
3022: * maintain page reference counts, and callers may free
3023: * pages due to the error. So zap it early.
3024: */
3025: zap_page_range_single(vma, addr, size, NULL);
3026: return error;
3027: }
SourceFile:mm/memory.c]
|
| 34/2 |
2026/05/16 20:55 |
llm |
expert |
0m
Model:
Tokens:
input: 32556
output: 43
thoughts: 140
Thoughts: **Analyzing `remap_pfn_range_internal` Failure Scenarios**
Okay, so I'm digging into `remap_pfn_range_internal`. My immediate thought is, how can I make this thing fail, and specifically, how can I force it to return `-ENOMEM`? It's allocating page tables, so memory pressure is obviously a key factor. I was thinking initially about resource limits like `RLIMIT_AS` or `RLIMIT_MEMLOCK`, but then I realized the page table allocation is tied to the memory cgroup or just regular system memory accounting, so those aren't the direct levers.
Aha! Memory cgroups. I *could* use those to put a tight constraint on memory allocation and trigger the `-ENOMEM` return. Or, of course, just exhaust the system memory. That seems pretty straightforward.
But is that the only way? Are there any other more subtle failure modes? I'm wondering... what about the virtual memory area's `vma->vm_flags`? Could an invalid combination of those flags somehow lead to a failure within `remap_pfn_range_internal`? The code specifically checks those flags, so I need to explore what combinations of `vm_flags` might trigger a failure.
|
| 35/2 |
2026/05/16 20:55 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/memory.c IncludeLines:true Name:remap_pfn_range_internal]
Results: map[SourceCode:2979: static int remap_pfn_range_internal(struct vm_area_struct *vma, unsigned long addr,
2980: unsigned long pfn, unsigned long size, pgprot_t prot)
2981: {
2982: pgd_t *pgd;
2983: unsigned long next;
2984: unsigned long end = addr + PAGE_ALIGN(size);
2985: struct mm_struct *mm = vma->vm_mm;
2986: int err;
2987:
2988: if (WARN_ON_ONCE(!PAGE_ALIGNED(addr)))
2989: return -EINVAL;
2990:
2991: VM_WARN_ON_ONCE(!vma_test_all_flags_mask(vma, VMA_REMAP_FLAGS));
2992:
2993: BUG_ON(addr >= end);
2994: pfn -= addr >> PAGE_SHIFT;
2995: pgd = pgd_offset(mm, addr);
2996: flush_cache_range(vma, addr, end);
2997: do {
2998: next = pgd_addr_end(addr, end);
2999: err = remap_p4d_range(mm, pgd, addr, next,
3000: pfn + (addr >> PAGE_SHIFT), prot);
3001: if (err)
3002: return err;
3003: } while (pgd++, addr = next, addr != end);
3004:
3005: return 0;
3006: }
SourceFile:mm/memory.c]
|
| 36/2 |
2026/05/16 20:55 |
llm |
expert |
0m
Model:
Tokens:
input: 33486
output: 41
thoughts: 2578
Thoughts: **Analysis of the Perf Event mmap Deadlock**
Okay, so I'm digging into this `perf_event` mmap issue. It looks like the core problem boils down to a recursive lock, which leads to a deadlock. I started by tracing the mmap path: `remap_p4d_range` -> `remap_pud_range` -> `remap_pmd_range` -> `remap_pte_range`. These functions handle page table allocation. Naturally, memory exhaustion is the most obvious failure point, resulting in `-ENOMEM`. I wondered if there were other failure paths.
My initial thought was `perf_mmap_to_page` returning NULL. I checked its logic. It calls `perf_mmap_to_page` which should return NULL if `pgoff > rb->nr_pages`, but the loop in `map_range` ensures that `pgoff` is always within the valid range, since we iterate from `0` to `nr_pages - 1`. So, it seemed like it wouldn't.
However, I started thinking about the sizes and how they interact. What if `vma_pages(vma)` is larger than `nr_pages`? I checked the `perf_mmap` function. `nr_pages` is correctly calculated as `vma_size / PAGE_SIZE`, corresponding to the VMA size. It passes this to `perf_mmap_rb`, where it gets passed to `nr_pages` in `data_page_nr`. I noticed a subtle difference: `rb->nr_pages` ends up being `nr_pages - 1`. Back in `map_range`, `pagenum` iterates up to `nr_pages - 1`. So, `pgoff` (which is `pagenum`) goes up to `nr_pages - 1`, and `perf_mmap_to_page` won't return NULL, since `pgoff` will not exceed `rb->nr_pages`. Therefore, `perf_mmap_to_page` will not return `NULL`.
Okay, next I pondered the failure of `perf_mmap_rb`. If this happens, `perf_mmap` returns immediately without calling `map_range` or `perf_mmap_close`. It looks like `perf_mmap_close` is only called if `map_range` fails. `map_range` can fail if `remap_pfn_range` fails. `remap_pfn_range` can fail on `-ENOMEM`, or fault injection. Since the VMA is created fresh by `mmap`, I'm guessing that it can't have `VM_PFNMAP` set. I saw that `pfnmap_setup_cachemode` returns 0. So, it comes down to `-ENOMEM` or fault injection as the only way `remap_pfn_range` fails.
So, it's pretty clear that memory exhaustion is the *only* typical way `map_range` can fail, absent fault injection, as the only way to get this particular deadlock to occur. But is that considered exploitable? Is memory exhaustion a valid way to trigger a bug for exploitability/DoS? Yes, local DoS through memory exhaustion leading to a deadlock is indeed a valid DoS. The user can create the situation using memory cgroups.
So, the deadlock specifically involves `event->mmap_mutex` and `mm->mmap_lock`. Because it locks these, it only causes the process that's mmap'ing this perf event to be hung. This is not a system-wide DoS, except that it prevents the process from being killed (it becomes "D" state, unkillable), which could be leveraged to create many unkillable processes, exhausting PID space or other system-wide resources. Okay, so it can be a local DoS.
I went back and checked `perf_mmap_to_page` again in case I missed something. There is an `aux_nr_pages` path. But that seems to involve a separate code path (`perf_mmap_aux`). With `vma->vm_pgoff == 0` (mapping the main buffer), the code path is `perf_mmap_rb`, which requires a match. The `rb->aux_pgoff` is at least `rb->nr_pages + 1`, and `pgoff` is at most `rb->nr_pages`. So, the `pgoff` (0 to `rb->nr_pages - 1`) will never be in the AUX space. `perf_mmap_to_page` will never return NULL via the AUX code path.
Okay
|
| 37/2 |
2026/05/16 20:55 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:kernel/events/core.c IncludeLines:true Name:perf_event_open]
Results: map[SourceCode: SourceFile:]
|
| 38/2 |
2026/05/16 20:55 |
llm |
expert |
0m
Model:
Tokens:
input: 37151
output: 43
thoughts: 14
Thoughts: Okay, here's my summarized thought process, presented as if I'm thinking it:
**Digging into Performance Monitoring**
Alright, I need to get to the bottom of this performance issue. Let's start with a low-level look at what's happening under the hood. My first instinct is to see if I can leverage some of the powerful system-level performance monitoring tools. Okay, I'm going to start with a search for `sys_perf_event_open`. This should be a good starting point. I suspect that this system call, or something related to it, is likely involved in the data collection process I'm trying to understand or influence. It's the core of how we access the Performance Monitoring Unit (PMU) and other performance counters on the system. Let's see what that search reveals. This should give me some context about the existing code.
|
| 39/2 |
2026/05/16 20:55 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:kernel/events/core.c IncludeLines:true Name:sys_perf_event_open]
Results: map[SourceCode: SourceFile:]
|
| 40/2 |
2026/05/16 20:55 |
llm |
expert |
0m
Model:
Tokens:
input: 37416
output: 26
thoughts: 17
Thoughts: Okay, here's my interpretation of that:
**Diving into `perf_event_open`**
Alright, let's break this down. Seeing `SYSCALL_DEFINE5(perf_event_open` immediately tells me we're looking at the core of performance monitoring within the kernel. It's a system call definition, `DEFINE5` indicating it takes five arguments, and it's named `perf_event_open`. This is the entry point, the fundamental function used to create and configure performance monitoring events. It's the gatekeeper, the key function used by userspace applications to tap into the kernel's performance counters and tracing capabilities. I need to understand its role in setting up performance monitoring events. It is most likely used by `perf` and other tools to perform its main function. I need to understand its purpose and role to gain a stronger understanding of performance.
|
| 41/2 |
2026/05/16 20:56 |
tool |
grepper |
0m
Args: map[Expression:SYSCALL_DEFINE5\(perf_event_open]
Results: map[Output:kernel/events/core.c=13769=perf_check_permission(struct perf_event_attr *attr, struct task_struct *task)
--
kernel/events/core.c-13807- */
kernel/events/core.c:13808:SYSCALL_DEFINE5(perf_event_open,
kernel/events/core.c-13809- struct perf_event_attr __user *, attr_uptr,
]
|
| 42/2 |
2026/05/16 20:56 |
llm |
expert |
0m
Model:
Tokens:
input: 37773
output: 41
thoughts: 11
Thoughts: Okay, here's my attempt at summarizing that thought process, tailored for an expert audience:
**Checking `perf_check_permission`**
Alright, let's dive in. I see that bit of code, `perf_check_permission`, and my initial thought is, "Okay, what's happening here?" It's the point where we're going to examine permissions, which, in itself, tells me we're likely dealing with some form of access control. I need to understand the granularity and the context. Is this related to file system operations, resource access, maybe even something more abstract like a database query execution?
My immediate checklist starts running:
* **Underlying Security Model:** What permissioning scheme are we using? Is it POSIX-style, ACL-based, RBAC, ABAC? Knowing this dictates how I approach the check.
* **Performance Implications:** The function name itself has "perf" in it, so I'm already mentally flagging this as a potential performance bottleneck. I need to be aware of the *frequency* of this check. Is it happening per operation, per request, or is it a one-time thing? High-frequency checks demand highly efficient implementation.
* **Concurrency:** If multiple threads or processes might be accessing this function concurrently, I'm thinking about potential race conditions and the locking mechanisms (or lack thereof) that are in place. Any thread-safety issues jump to the front.
* **Error Handling:** What happens if the permission check fails? How are errors being propagated, logged, and handled? Is there a fallback mechanism? Are we providing clear and secure feedback on permission errors?
* **Testability:** Given the code, are there adequate tests that will catch any possible edge cases? Is it possible to test permission denial/grant scenarios effectively?
So, my mental parsing here is a quick assessment of its purpose within the system, focusing on its security implications and the overall impact on the performance of the system. Iβm thinking through the different potential implications of an efficient permission check.
|
| 43/2 |
2026/05/16 20:56 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/events/core.c IncludeLines:true Name:perf_check_permission]
Results: map[SourceCode:13768: static bool
13769: perf_check_permission(struct perf_event_attr *attr, struct task_struct *task)
13770: {
13771: unsigned int ptrace_mode = PTRACE_MODE_READ_REALCREDS;
13772: bool is_capable = perfmon_capable();
13773:
13774: if (attr->sigtrap) {
13775: /*
13776: * perf_event_attr::sigtrap sends signals to the other task.
13777: * Require the current task to also have CAP_KILL.
13778: */
13779: rcu_read_lock();
13780: is_capable &= ns_capable(__task_cred(task)->user_ns, CAP_KILL);
13781: rcu_read_unlock();
13782:
13783: /*
13784: * If the required capabilities aren't available, checks for
13785: * ptrace permissions: upgrade to ATTACH, since sending signals
13786: * can effectively change the target task.
13787: */
13788: ptrace_mode = PTRACE_MODE_ATTACH_REALCREDS;
13789: }
13790:
13791: /*
13792: * Preserve ptrace permission check for backwards compatibility. The
13793: * ptrace check also includes checks that the current task and other
13794: * task have matching uids, and is therefore not done here explicitly.
13795: */
13796: return is_capable || ptrace_may_access(task, ptrace_mode);
13797: }
SourceFile:kernel/events/core.c]
|
| 44/2 |
2026/05/16 20:56 |
llm |
expert |
0m
Model:
Tokens:
input: 38772
output: 41
thoughts: 16
Thoughts: Okay, here's my interpretation of that.
**Analyzing the `perf_event_open` System Call**
Right, so I'm diving into `SYSCALL_DEFINE5(perf_event_open)` again. This is the heart of the kernel's interface for performance monitoring, a crucial piece for any serious profiling work. I need to refresh my understanding of how this specific system call is defined, since it is a crucial component of any performance tuning endeavor. Let's see... it's a macro, of course. `SYSCALL_DEFINE5` indicates it's a system call with five arguments. I need to identify what these five arguments are. I'll need to go to the source code to confirm the variables, but my guess is the arguments will include: a `struct perf_event_attr` pointer to define the properties of the event; the process or CPU the event should be attached to; the CPU ID/group of CPUs; flags to control behavior; and potentially a group leader file descriptor. Understanding the interplay of these parameters will illuminate the nuances of event configuration and potential bottlenecks. Now I need to review what these arguments mean, because it is important to remember what each argument influences, and the purpose of each.
|
| 45/2 |
2026/05/16 20:56 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:kernel/events/core.c IncludeLines:true Name:perf_event_open]
Results: map[SourceCode: SourceFile:]
|
| 46/2 |
2026/05/16 20:56 |
llm |
expert |
0m
Model:
Tokens:
input: 39111
output: 20
thoughts: 8
Thoughts: Okay, here's my attempt at summarizing the thought process, tailored for an expert audience, and delivered in the first person:
**Diving into the System Call**
Alright, let's cut the fluff and get down to brass tacks. I need to understand this interaction at a granular level. The documentation provides a framework, but that's not enough to truly grasp the nuances. I'm going to start by meticulously examining the system call itself. I'll peel back the layers β the arguments, the return values, the kernel entry point, the whole shebang. Forget high-level abstractions for now; I need to dissect the actual mechanism. Let's see how this thing *really* works. What are the potential race conditions? The security implications? The performance bottlenecks? Only a thorough dissection of the syscall can answer these questions effectively. So, let's dive in.
|
| 47/2 |
2026/05/16 20:56 |
tool |
grepper |
0m
Args: map[Expression:perf_event_paranoid]
Results: map[Output:Documentation/admin-guide/perf-security.rst=226=perf_events *scope* and *access* control for unprivileged processes
Documentation/admin-guide/perf-security.rst:227:is governed by perf_event_paranoid [2]_ setting:
Documentation/admin-guide/perf-security.rst-228-
--
Documentation/admin-guide/sysctl/kernel.rst=975=allowed to execute.
--
Documentation/admin-guide/sysctl/kernel.rst-990-
Documentation/admin-guide/sysctl/kernel.rst:991:perf_event_paranoid
Documentation/admin-guide/sysctl/kernel.rst-992-===================
--
drivers/gpu/drm/i915/i915_perf.c-44- * without special privileges. Access to system-wide metrics requires root
drivers/gpu/drm/i915/i915_perf.c:45: * privileges by default, unless changed via the dev.i915.perf_event_paranoid
drivers/gpu/drm/i915/i915_perf.c-46- * sysctl option.
--
include/linux/perf_event.h=1762=static inline int perf_callchain_store(struct perf_callchain_entry_ctx *ctx, u64 ip)
--
include/linux/perf_event.h-1774-
include/linux/perf_event.h:1775:extern int sysctl_perf_event_paranoid;
include/linux/perf_event.h-1776-extern int sysctl_perf_event_sample_rate;
--
include/linux/perf_event.h=1788=static inline int perf_is_paranoid(void)
include/linux/perf_event.h-1789-{
include/linux/perf_event.h:1790: return sysctl_perf_event_paranoid > -1;
include/linux/perf_event.h-1791-}
--
include/linux/perf_event.h=1795=static inline int perf_allow_cpu(void)
include/linux/perf_event.h-1796-{
include/linux/perf_event.h:1797: if (sysctl_perf_event_paranoid > 0 && !perfmon_capable())
include/linux/perf_event.h-1798- return -EACCES;
--
include/linux/perf_event.h=1803=static inline int perf_allow_tracepoint(void)
include/linux/perf_event.h-1804-{
include/linux/perf_event.h:1805: if (sysctl_perf_event_paranoid > -1 && !perfmon_capable())
include/linux/perf_event.h-1806- return -EPERM;
--
kernel/events/core.c=482=static __always_inline bool is_guest_mediated_pmu_loaded(void)
--
kernel/events/core.c-494- */
kernel/events/core.c:495:int sysctl_perf_event_paranoid __read_mostly = 2;
kernel/events/core.c-496-
--
kernel/events/core.c=572=static const struct ctl_table events_core_sysctl_table[] = {
--
kernel/events/core.c-577- {
kernel/events/core.c:578: .procname = "perf_event_paranoid",
kernel/events/core.c:579: .data = &sysctl_perf_event_paranoid,
kernel/events/core.c:580: .maxlen = sizeof(sysctl_perf_event_paranoid),
kernel/events/core.c-581- .mode = 0644,
--
kernel/events/core.c=14689=int perf_allow_kernel(void)
kernel/events/core.c-14690-{
kernel/events/core.c:14691: if (sysctl_perf_event_paranoid > 1 && !perfmon_capable())
kernel/events/core.c-14692- return -EACCES;
--
kernel/ksyms_common.c=9=static inline int kallsyms_for_perf(void)
--
kernel/ksyms_common.c-11-#ifdef CONFIG_PERF_EVENTS
kernel/ksyms_common.c:12: extern int sysctl_perf_event_paranoid;
kernel/ksyms_common.c-13-
kernel/ksyms_common.c:14: if (sysctl_perf_event_paranoid <= 1)
kernel/ksyms_common.c-15- return 1;
--
kernel/ksyms_common.c-22- * kernel profiling and are explicitly not paranoid (so kptr_restrict
kernel/ksyms_common.c:23: * is clear, and sysctl_perf_event_paranoid isn't set).
kernel/ksyms_common.c-24- *
--
tools/kvm/kvm_stat/kvm_stat=1680=Requirements:
--
tools/kvm/kvm_stat/kvm_stat-1684- /proc/pid/task
tools/kvm/kvm_stat/kvm_stat:1685:- /proc/sys/kernel/perf_event_paranoid < 1 if user has no
tools/kvm/kvm_stat/kvm_stat-1686- CAP_SYS_ADMIN and perf events are used.
--
tools/perf/Documentation/perf-intel-pt.txt=649=nor snapshot size is specified, then the default is 4MiB for privileged users
tools/perf/Documentation/perf-intel-pt.txt:650:(or if /proc/sys/kernel/perf_event_paranoid < 0), 128KiB for unprivileged users.
tools/perf/Documentation/perf-intel-pt.txt-651-If an unprivileged user does not specify mmap pages, the mmap pages will be
--
tools/perf/Documentation/perf-intel-pt.txt=672=The default auxtrace mmap size for Intel PT is 4MiB/page_size for privileged users
tools/perf/Documentation/perf-intel-pt.txt:673:(or if /proc/sys/kernel/perf_event_paranoid < 0), 128KiB for unprivileged users.
tools/perf/Documentation/perf-intel-pt.txt-674-If an unprivileged user does not specify mmap pages, the mmap pages will be
--
tools/perf/Documentation/perf-intel-pt.txt=831=Privileged vs non-privileged users
--
tools/perf/Documentation/perf-intel-pt.txt-833-
tools/perf/Documentation/perf-intel-pt.txt:834:Unless /proc/sys/kernel/perf_event_paranoid is set to -1, unprivileged users
tools/perf/Documentation/perf-intel-pt.txt-835-have memory limits imposed upon them. That affects what buffer sizes they can
--
tools/perf/Documentation/perf-intel-pt.txt=848=as the sched:sched_switch tracepoints will be used to receive such information:
tools/perf/Documentation/perf-intel-pt.txt-849-
tools/perf/Documentation/perf-intel-pt.txt:850:Unless /proc/sys/kernel/perf_event_paranoid is set to -1, unprivileged users are
tools/perf/Documentation/perf-intel-pt.txt-851-not permitted to use tracepoints which means there is insufficient side-band
--
tools/perf/Documentation/perf-list.txt=238=Some event qualifiers, such as "any", are also root only.
tools/perf/Documentation/perf-list.txt-239-
tools/perf/Documentation/perf-list.txt:240:This can be overridden by setting the kernel.perf_event_paranoid
tools/perf/Documentation/perf-list.txt-241-sysctl to -1, which allows non root to use these events.
--
tools/perf/Documentation/perf-list.txt=243=For accessing trace point events perf needs to have read access to
tools/perf/Documentation/perf-list.txt:244:/sys/kernel/tracing, even when perf_event_paranoid is in a relaxed
tools/perf/Documentation/perf-list.txt-245-setting.
--
tools/perf/Documentation/perf-stat.txt=493=and -a (global monitoring) is needed, requiring root rights or
tools/perf/Documentation/perf-stat.txt:494:perf.perf_event_paranoid=-1.
tools/perf/Documentation/perf-stat.txt-495-
--
tools/perf/Documentation/security.txt=134=Perf tool provides a message similar to the one below:
--
tools/perf/Documentation/security.txt-141- more perf_event access control information and adjusting the policy.
tools/perf/Documentation/security.txt:142: Consider adjusting /proc/sys/kernel/perf_event_paranoid setting to open
tools/perf/Documentation/security.txt-143- access to performance monitoring and observability operations for users
tools/perf/Documentation/security.txt-144- without CAP_PERFMON or CAP_SYS_ADMIN Linux capability.
tools/perf/Documentation/security.txt:145: perf_event_paranoid setting is -1:
tools/perf/Documentation/security.txt-146- -1: Allow use of (almost) all events by all users
--
tools/perf/Documentation/security.txt-150- >= 2: Disallow kernel profiling
tools/perf/Documentation/security.txt:151: To make the adjusted perf_event_paranoid setting permanent preserve it
tools/perf/Documentation/security.txt:152: in /etc/sysctl.conf (e.g. kernel.perf_event_paranoid = <setting>)
tools/perf/Documentation/security.txt-153-
--
tools/perf/arch/arm/util/cs-etm.c=330=static int cs_etm_recording_options(struct auxtrace_record *itr,
--
tools/perf/arch/arm/util/cs-etm.c-339- struct perf_cpu_map *cpus = evlist->core.user_requested_cpus;
tools/perf/arch/arm/util/cs-etm.c:340: bool privileged = perf_event_paranoid_check(-1);
tools/perf/arch/arm/util/cs-etm.c-341- int err = 0;
--
tools/perf/arch/arm64/util/arm-spe.c=297=static int arm_spe_setup_aux_buffer(struct record_opts *opts)
tools/perf/arch/arm64/util/arm-spe.c-298-{
tools/perf/arch/arm64/util/arm-spe.c:299: bool privileged = perf_event_paranoid_check(-1);
tools/perf/arch/arm64/util/arm-spe.c-300-
--
tools/perf/arch/arm64/util/hisi-ptt.c=65=static int hisi_ptt_set_auxtrace_mmap_page(struct record_opts *opts)
tools/perf/arch/arm64/util/hisi-ptt.c-66-{
tools/perf/arch/arm64/util/hisi-ptt.c:67: bool privileged = perf_event_paranoid_check(-1);
tools/perf/arch/arm64/util/hisi-ptt.c-68-
--
tools/perf/arch/x86/util/intel-bts.c=105=static int intel_bts_recording_options(struct auxtrace_record *itr,
--
tools/perf/arch/x86/util/intel-bts.c-113- const struct perf_cpu_map *cpus = evlist->core.user_requested_cpus;
tools/perf/arch/x86/util/intel-bts.c:114: bool privileged = perf_event_paranoid_check(-1);
tools/perf/arch/x86/util/intel-bts.c-115-
--
tools/perf/arch/x86/util/intel-pt.c=612=static int intel_pt_recording_options(struct auxtrace_record *itr,
--
tools/perf/arch/x86/util/intel-pt.c-621- const struct perf_cpu_map *cpus = evlist->core.user_requested_cpus;
tools/perf/arch/x86/util/intel-pt.c:622: bool privileged = perf_event_paranoid_check(-1);
tools/perf/arch/x86/util/intel-pt.c-623- u64 tsc_bit;
--
tools/perf/builtin-record.c=1345=static int record__open(struct record *rec)
--
tools/perf/builtin-record.c-1454-"WARNING: Kernel address maps (/proc/{kallsyms,modules}) are restricted,\n"
tools/perf/builtin-record.c:1455:"check /proc/sys/kernel/kptr_restrict and /proc/sys/kernel/perf_event_paranoid.\n\n"
tools/perf/builtin-record.c-1456-"Samples in kernel functions may not be resolved if a suitable vmlinux\n"
--
tools/perf/builtin-top.c=745=static void perf_event__process_sample(const struct perf_tool *tool,
--
tools/perf/builtin-top.c-789-"Kernel address maps (/proc/{kallsyms,modules}) are restricted.\n\n"
tools/perf/builtin-top.c:790:"Check /proc/sys/kernel/kptr_restrict and /proc/sys/kernel/perf_event_paranoid.\n\n"
tools/perf/builtin-top.c-791-"Kernel%s samples will not be resolved.\n",
--
tools/perf/builtin-trace.c=1964=static char *trace__machine__resolve_kernel_addr(void *vmachine, unsigned long long *addrp, char **modp)
--
tools/perf/builtin-trace.c-1972- pr_warning("Kernel address maps (/proc/{kallsyms,modules}) are restricted.\n\n"
tools/perf/builtin-trace.c:1973: "Check /proc/sys/kernel/kptr_restrict and /proc/sys/kernel/perf_event_paranoid.\n\n"
tools/perf/builtin-trace.c-1974- "Kernel samples will not be resolved.\n");
--
tools/perf/tests/mmap-basic.c=35=static int test__basic_mmap(struct test_suite *test __maybe_unused, int subtest __maybe_unused)
--
tools/perf/tests/mmap-basic.c-102- pr_debug("failed to open counter: %s, "
tools/perf/tests/mmap-basic.c:103: "tweak /proc/sys/kernel/perf_event_paranoid?\n",
tools/perf/tests/mmap-basic.c-104- str_error_r(errno, sbuf, sizeof(sbuf)));
--
tools/perf/tests/openat-syscall-all-cpus.c=22=static int test__openat_syscall_event_on_all_cpus(struct test_suite *test __maybe_unused,
--
tools/perf/tests/openat-syscall-all-cpus.c-57- pr_debug("failed to open counter: %s, "
tools/perf/tests/openat-syscall-all-cpus.c:58: "tweak /proc/sys/kernel/perf_event_paranoid?\n",
tools/perf/tests/openat-syscall-all-cpus.c-59- str_error_r(errno, sbuf, sizeof(sbuf)));
--
tools/perf/tests/openat-syscall.c=17=static int test__openat_syscall_event(struct test_suite *test __maybe_unused,
--
tools/perf/tests/openat-syscall.c-41- pr_debug("failed to open counter: %s, "
tools/perf/tests/openat-syscall.c:42: "tweak /proc/sys/kernel/perf_event_paranoid?\n",
tools/perf/tests/openat-syscall.c-43- str_error_r(errno, sbuf, sizeof(sbuf)));
--
tools/perf/tests/shell/amd-ibs-swfilt.sh=4=ParanoidAndNotRoot() {
tools/perf/tests/shell/amd-ibs-swfilt.sh:5: [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
tools/perf/tests/shell/amd-ibs-swfilt.sh-6-}
--
tools/perf/tests/shell/amd-ibs-swfilt.sh=47=else
tools/perf/tests/shell/amd-ibs-swfilt.sh:48: echo "[SKIP] not root and perf_event_paranoid too high for exclude_user"
tools/perf/tests/shell/amd-ibs-swfilt.sh-49- err=2
--
tools/perf/tests/shell/amd-ibs-swfilt.sh=67=else
tools/perf/tests/shell/amd-ibs-swfilt.sh:68: echo "[SKIP] not root and perf_event_paranoid too high for system-wide/exclude_user"
tools/perf/tests/shell/amd-ibs-swfilt.sh-69- err=2
--
tools/perf/tests/shell/amd-ibs-swfilt.sh=87=else
tools/perf/tests/shell/amd-ibs-swfilt.sh:88: echo "[SKIP] not root and perf_event_paranoid too high for exclude_user"
tools/perf/tests/shell/amd-ibs-swfilt.sh-89- err=2
--
tools/perf/tests/shell/lib/stat_output.sh-3-
tools/perf/tests/shell/lib/stat_output.sh:4:# Return true if perf_event_paranoid is > $1 and not running as root.
tools/perf/tests/shell/lib/stat_output.sh-5-function ParanoidAndNotRoot()
tools/perf/tests/shell/lib/stat_output.sh-6-{
tools/perf/tests/shell/lib/stat_output.sh:7: [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
tools/perf/tests/shell/lib/stat_output.sh-8-}
--
tools/perf/tests/shell/record_lbr.sh=7=ParanoidAndNotRoot() {
tools/perf/tests/shell/record_lbr.sh:8: [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
tools/perf/tests/shell/record_lbr.sh-9-}
--
tools/perf/tests/shell/stat+json_output.sh=28=trap trap_cleanup EXIT TERM INT
tools/perf/tests/shell/stat+json_output.sh-29-
tools/perf/tests/shell/stat+json_output.sh:30:# Return true if perf_event_paranoid is > $1 and not running as root.
tools/perf/tests/shell/stat+json_output.sh-31-function ParanoidAndNotRoot()
tools/perf/tests/shell/stat+json_output.sh-32-{
tools/perf/tests/shell/stat+json_output.sh:33: [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
tools/perf/tests/shell/stat+json_output.sh-34-}
--
tools/perf/tests/shell/stat_all_metricgroups.sh=5=ParanoidAndNotRoot()
tools/perf/tests/shell/stat_all_metricgroups.sh-6-{
tools/perf/tests/shell/stat_all_metricgroups.sh:7: [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
tools/perf/tests/shell/stat_all_metricgroups.sh-8-}
--
tools/perf/tests/shell/stat_all_metrics.sh=5=ParanoidAndNotRoot()
tools/perf/tests/shell/stat_all_metrics.sh-6-{
tools/perf/tests/shell/stat_all_metrics.sh:7: [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
tools/perf/tests/shell/stat_all_metrics.sh-8-}
--
tools/perf/tests/shell/test_stat_intel_tpebs.sh=7=ParanoidAndNotRoot() {
tools/perf/tests/shell/test_stat_intel_tpebs.sh:8: [ "$(id -u)" != 0 ] && [ "$(cat /proc/sys/kernel/perf_event_paranoid)" -gt $1 ]
tools/perf/tests/shell/test_stat_intel_tpebs.sh-9-}
--
tools/perf/util/event.h=404=void event_attr_init(struct perf_event_attr *attr);
tools/perf/util/event.h-405-
tools/perf/util/event.h:406:int perf_event_paranoid(void);
tools/perf/util/event.h:407:bool perf_event_paranoid_check(int max_level);
tools/perf/util/event.h-408-
--
tools/perf/util/evlist.c=101=struct evlist *evlist__new_default(void)
--
tools/perf/util/evlist.c-109-
tools/perf/util/evlist.c:110: can_profile_kernel = perf_event_paranoid_check(1);
tools/perf/util/evlist.c-111-
--
tools/perf/util/evlist.c=1650=int evlist__strerror_open(struct evlist *evlist, int err, char *buf, size_t size)
--
tools/perf/util/evlist.c-1659- "Error:\t%m.\n"
tools/perf/util/evlist.c:1660: "Hint:\tCheck /proc/sys/kernel/perf_event_paranoid setting.");
tools/perf/util/evlist.c-1661-
tools/perf/util/evlist.c:1662: value = perf_event_paranoid();
tools/perf/util/evlist.c-1663-
--
tools/perf/util/evlist.c-1673- printed += scnprintf(buf + printed, size - printed,
tools/perf/util/evlist.c:1674: "Hint:\tTry: 'sudo sh -c \"echo -1 > /proc/sys/kernel/perf_event_paranoid\"'\n"
tools/perf/util/evlist.c-1675- "Hint:\tThe current value is %d.", value);
--
tools/perf/util/evsel.c=3783=bool evsel__fallback(struct evsel *evsel, struct target *target, int err,
--
tools/perf/util/evsel.c-3808- } else if (err == EACCES && !evsel->core.attr.exclude_kernel &&
tools/perf/util/evsel.c:3809: (paranoid = perf_event_paranoid()) > 1) {
tools/perf/util/evsel.c-3810- const char *name = evsel__name(evsel);
--
tools/perf/util/evsel.c-3827- evsel->name = new_name;
tools/perf/util/evsel.c:3828: scnprintf(msg, msgsize, "kernel.perf_event_paranoid=%d, trying "
tools/perf/util/evsel.c-3829- "to fall back to excluding kernel and hypervisor "
--
tools/perf/util/evsel.c=3979=int evsel__open_strerror(struct evsel *evsel, struct target *target,
--
tools/perf/util/evsel.c-4005- return printed + scnprintf(msg + printed, size - printed,
tools/perf/util/evsel.c:4006: "Consider adjusting /proc/sys/kernel/perf_event_paranoid setting to open\n"
tools/perf/util/evsel.c-4007- "access to performance monitoring and observability operations for processes\n"
--
tools/perf/util/evsel.c-4010- "https://www.kernel.org/doc/html/latest/admin-guide/perf-security.html\n"
tools/perf/util/evsel.c:4011: "perf_event_paranoid setting is %d:\n"
tools/perf/util/evsel.c-4012- " -1: Allow use of (almost) all events by all users\n"
--
tools/perf/util/evsel.c-4016- ">= 2: Disallow kernel profiling\n"
tools/perf/util/evsel.c:4017: "To make the adjusted perf_event_paranoid setting permanent preserve it\n"
tools/perf/util/evsel.c:4018: "in /etc/sysctl.conf (e.g. kernel.perf_event_paranoid = <setting>)",
tools/perf/util/evsel.c:4019: perf_event_paranoid());
tools/perf/util/evsel.c-4020- case ENOENT:
--
tools/perf/util/pfm.c=124=static bool is_libpfm_event_supported(const char *name, struct perf_cpu_map *cpus,
--
tools/perf/util/pfm.c-148- * This happens if the paranoid value
tools/perf/util/pfm.c:149: * /proc/sys/kernel/perf_event_paranoid is set to 2
tools/perf/util/pfm.c-150- * Re-run with exclude_kernel set; we don't do that
--
tools/perf/util/print-events.c=134=bool is_event_supported(u8 type, u64 config)
--
tools/perf/util/print-events.c-154- * The event may fail to open if the paranoid value
tools/perf/util/print-events.c:155: * /proc/sys/kernel/perf_event_paranoid is set to 2
tools/perf/util/print-events.c-156- * Re-run with exclude_kernel set; we don't do that by
--
tools/perf/util/probe-event.c=843=post_process_kernel_probe_trace_events(struct probe_trace_event *tevs,
--
tools/perf/util/probe-event.c-859- "Check /proc/sys/kernel/kptr_restrict\n"
tools/perf/util/probe-event.c:860: "and /proc/sys/kernel/perf_event_paranoid. "
tools/perf/util/probe-event.c-861- "Or run as privileged perf user.\n\n");
--
tools/perf/util/probe-event.c=3116=static int find_probe_trace_events_from_map(struct perf_probe_event *pev,
--
tools/perf/util/probe-event.c-3173- "Check /proc/sys/kernel/kptr_restrict\n"
tools/perf/util/probe-event.c:3174: "and /proc/sys/kernel/perf_event_paranoid. "
tools/perf/util/probe-event.c-3175- "Or run as privileged perf user.\n\n");
--
tools/perf/util/symbol.c=2322=static bool symbol__read_kptr_restrict(void)
--
tools/perf/util/symbol.c-2338- /* Per kernel/kallsyms.c:
tools/perf/util/symbol.c:2339: * we also restrict when perf_event_paranoid > 1 w/o CAP_SYSLOG
tools/perf/util/symbol.c-2340- */
tools/perf/util/symbol.c:2341: if (perf_event_paranoid() > 1 && !cap_syslog)
tools/perf/util/symbol.c-2342- value = true;
--
tools/perf/util/util.c=361=size_t hex_width(u64 v)
--
tools/perf/util/util.c-370-
tools/perf/util/util.c:371:int perf_event_paranoid(void)
tools/perf/util/util.c-372-{
--
tools/perf/util/util.c-374-
tools/perf/util/util.c:375: if (sysctl__read_int("kernel/perf_event_paranoid", &value))
tools/perf/util/util.c-376- return INT_MAX;
--
tools/perf/util/util.c-380-
tools/perf/util/util.c:381:bool perf_event_paranoid_check(int max_level)
tools/perf/util/util.c-382-{
--
tools/perf/util/util.c-390-
tools/perf/util/util.c:391: return perf_event_paranoid() <= max_level;
tools/perf/util/util.c-392-}
--
tools/power/x86/turbostat/turbostat.c=8608=void linux_perf_init(void)
--
tools/power/x86/turbostat/turbostat.c-8612-
tools/power/x86/turbostat/turbostat.c:8613: if (access("/proc/sys/kernel/perf_event_paranoid", F_OK))
tools/power/x86/turbostat/turbostat.c-8614- return;
--
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c=221=void test_unpriv_bpf_disabled(void)
--
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c-232- char unprivileged_bpf_disabled_orig[32] = {};
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c:233: char perf_event_paranoid_orig[32] = {};
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c-234- struct bpf_prog_info prog_info = {};
--
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c-258- /* allow user without caps to use perf events */
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c:259: if (!ASSERT_OK(sysctl_set("/proc/sys/kernel/perf_event_paranoid", perf_event_paranoid_orig,
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c-260- "-1"),
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c:261: "set_perf_event_paranoid"))
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c-262- goto cleanup;
--
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c-308- cap_enable_effective(save_caps, NULL);
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c:309: if (strlen(perf_event_paranoid_orig) > 0)
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c:310: sysctl_set("/proc/sys/kernel/perf_event_paranoid", NULL, perf_event_paranoid_orig);
tools/testing/selftests/bpf/prog_tests/unpriv_bpf_disabled.c-311- if (strlen(unprivileged_bpf_disabled_orig) > 0)
--
tools/testing/selftests/powerpc/pmu/lib.c=137=int parse_proc_maps(void)
--
tools/testing/selftests/powerpc/pmu/lib.c-173-
tools/testing/selftests/powerpc/pmu/lib.c:174:#define PARANOID_PATH "/proc/sys/kernel/perf_event_paranoid"
tools/testing/selftests/powerpc/pmu/lib.c-175-
]
|
| 48/2 |
2026/05/16 20:56 |
llm |
expert |
0m
Model:
Tokens:
input: 47793
output: 66
thoughts: 1303
Thoughts: **Analysis of the Perf Event Deadlock**
Okay, let's break this down. My initial observation is that `perf_event_paranoid` is set to 2 by default, which is important. This means that, despite the Syzkaller reproducer running as root with `exclude_kernel = 0`, an unprivileged user can still interact with `PERF_TYPE_SOFTWARE` and `PERF_TYPE_HARDWARE` events if they set `exclude_kernel = 1`. This is key to understanding the exploitability. With `exclude_kernel = 1`, an unprivileged user can open a perf event, then `mmap` it.
Now, regarding the exploit, the core issue seems to be a deadlock in `perf_mmap_close`. This occurs because `perf_mmap_close` attempts to lock `event->mmap_mutex`, which is already held by `perf_mmap`. This does *not* lead to memory corruption or privilege escalation, which eliminates any direct exploitation. The consequence, however, is a thread hanging in the 'D' state, holding `mm->mmap_lock` and `event->mmap_mutex`.
Therefore, the critical dimension is whether this is a Denial of Service (DoS) vulnerability. Yes, the ability to create numerous unkillable 'D' state threads could exhaust system resources, specifically PIDs and memory, resulting in a local DoS. The unprivileged access part is true, as demonstrated above: unprivileged users *can* open perf events and call `mmap` if `exclude_kernel = 1`. They also have access to it within user namespaces. This isn't a VM issue, though, so the VM triggers are false.
I need to revisit the `perf_mmap` function. My initial thought is to make sure it can be called by an unprivileged user, and indeed it is, as the only restriction is `perf_event_paranoid`, which isn't a problem here. The relevant code snippets that are called are good.
Next, I've checked for other ways in which `map_range` can fail and trigger the deadlock. Specifically, can `remap_pfn_range` fail? It seems unlikely. I investigated the various flags set and checked by `remap_pfn_range`, and things look correctly aligned.
But, what if `vma_pages(vma)` is very large? This is interesting. `rb_alloc` is called. If `rb_alloc` fails, `perf_mmap_rb` returns `-ENOMEM`. This happens *before* `map_range`, so it won't trigger the deadlock. If `rb_alloc` succeeds, and thus `map_range` is called, `remap_pfn_range` gets called for each page. Now we get the deadlock if this fails due to memory exhaustion while allocating page tables. Aha! This means an unprivileged user can *definitely* trigger this DoS by exhausting memory and then calling `mmap`.
Finally, I confirmed that `event->mmap_mutex` is a regular mutex. If this mutex is locked twice in the same thread, a deadlock results. And this is exactly what we have here. In a kernel *with* lockdep, it will print a warning. In a production kernel *without* lockdep, the thread will hang in `__mutex_lock_common`, which is a serious security issue: a local DoS.
Therefore, here's the summary:
* Exploitable: False
* DenialOfService: True
* Unprivileged: True
* UserNamespace: True
* VMGuestTrigger: False
* VMHostTrigger: False
* NetworkTrigger: False
* RemoteTrigger: False
* PeripheralTrigger: False
* FilesystemTrigger: False
I will now `set-results`.
|
| 49/2 |
2026/05/16 20:56 |
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]
|
| 50/2 |
2026/05/16 20:56 |
llm |
expert |
0m
Model:
Tokens:
input: 50067
output: 674
thoughts: 343
Thoughts: **My Analysis of the Perf Event Deadlock**
Alright, the results are in, and I've got a clear picture of what's happening. My task now is to condense this into a final response. This deadlock is nasty β it's a very specific issue within `perf_mmap`, specifically when `map_range` throws an error.
Here's the sequence: `perf_mmap` grabs `event->mmap_mutex` using a `scoped_guard`. That's standard practice. But if `map_range` fails β and it can, under memory pressure β it calls `perf_mmap_close(vma)`. The problem lies within `perf_mmap_close`. It decrements `event->mmap_count` and *if* that hits zero, it *tries* to lock `event->mmap_mutex` *again*. Now, because `perf_mmap` is still running in the same thread and already *holds* that mutex, this results in a recursive locking deadlock. It's a classic example of that anti-pattern, the thread will inevitably get stuck indefinitely in `TASK_UNINTERRUPTIBLE`. It's going to be holding `mm->mmap_lock` and, critically, `event->mmap_mutex` at that point.
The exploit window here is interesting. An unprivileged user can trigger this quite easily. They just need to open a perf event (a software event with `exclude_kernel=1` is a good starting point), call `mmap` on it, and then orchestrate a memory exhaustion condition to make `map_range` fail. By making `remap_pfn_range` fail with `-ENOMEM`, they can induce that failure inside the `mmap`. The user needs to repeatedly trigger this to cause a denial-of-service attack. A local DoS becomes possible because this can create unkillable processes, which then exhaust critical resources like PIDs and memory.
The good news (if you can call it that) is that the implications here are limited. It's only a deadlock; there's no memory corruption or privilege escalation involved. It's confined to a DoS scenario.
|
| 51/1 |
2026/05/16 20:56 |
action |
format-explanation |
0m
Results: map[Explanation:**Analysis of the Bug**
The bug is a recursive locking deadlock in the `perf_events` subsystem.
When a user calls `mmap()` on a perf event file descriptor, the kernel invokes
`perf_mmap()`. In this function, the `event->mmap_mutex` is acquired using a
`scoped_guard`. After setting up the ring buffer, `perf_mmap()` calls
`map_range()` to map the buffer pages into the user's VMA.
If `map_range()` fails (for example, due to memory allocation failures in
`remap_pfn_range()` when allocating page tables under memory pressure, or due to
fault injection as seen in the syzkaller reproducer), `perf_mmap()` attempts to
clean up by calling `perf_mmap_close(vma)`.
Inside `perf_mmap_close()`, the code decrements the `event->mmap_count`
reference counter. If this counter drops to zero (which it will if this was the
first time the event was mmap'ed), `perf_mmap_close()` attempts to acquire
`event->mmap_mutex` to safely detach the ring buffer:
```c
if (!refcount_dec_and_mutex_lock(&event->mmap_count,
&event->mmap_mutex))
goto out_put;
```
However, because `perf_mmap()` is still holding `event->mmap_mutex` via the
`scoped_guard`, the thread attempts to lock the same mutex twice. This results
in a classic recursive locking deadlock. The thread will hang indefinitely in
the `TASK_UNINTERRUPTIBLE` (D) state.
**Security Impact**
* **Exploitable:** False. The bug is a simple deadlock. It does not cause memory
corruption, out-of-bounds accesses, or use-after-free conditions. It cannot be
used for privilege escalation or arbitrary code execution.
* **Denial of Service:** True. Because the deadlocked thread is in an
uninterruptible state, it cannot be killed (even with `SIGKILL`). Furthermore,
the thread hangs while holding its own `mm->mmap_lock` for writing. An attacker
can repeatedly spawn processes that trigger this deadlock, eventually exhausting
system resources such as the PID space and memory, resulting in a local Denial
of Service.
* **Accessible From Unprivileged Processes:** True. Unprivileged users are
generally allowed to open certain perf events (like `PERF_TYPE_SOFTWARE` with
`exclude_kernel=1`, depending on the `kernel.perf_event_paranoid` sysctl) and
can call `mmap()` on them. By artificially creating memory pressure (e.g., via
memory cgroups or exhausting available RAM), an unprivileged user can force
`map_range()` to fail with `-ENOMEM`, reliably triggering the deadlock.
* **Accessible From User Namespaces:** True. The bug is reachable from
unprivileged contexts, including within user namespaces.
The bug is specific to the local system call interface for `perf_events` and
cannot be triggered remotely, via peripherals, or from a VM guest context.]
|