| 0/0 |
2026/08/29 00:01 |
flow |
assessment-security |
2m
Results: map[DenialOfService:false Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the security impact:
**Bug Mechanism:**
The warning is triggered by `lockdep_assert_preemption_disabled()` (via the
`preempt_disable_nested()` macro) inside `__mod_zone_page_state()`.
This occurs during a CPU offline event. When a CPU is offlined, the CPU hotplug
callback `page_alloc_cpu_dead()` is invoked, which in turn calls
`mlock_drain_remote()` to process the `mlock_fbatch` of the dying CPU.
Unlike other `mlock_folio_batch()` callers (which hold a `local_lock` that
disables preemption), `mlock_drain_remote()` does not disable preemption. If a
folio in the drained batch is not on the LRU list, `__munlock_folio()` skips
locking the `lruvec` (which would have disabled interrupts/preemption) and
directly calls `__zone_stat_mod_folio()`. This eventually calls
`__mod_zone_page_state()`, which expects preemption to be disabled to safely
update per-CPU statistics, thus triggering the warning.
**Exploitability & Denial of Service:**
The bug is a strict lockdep/preemption warning. The worst-case consequence of
this bug is a minor accounting error in the `vm_stat_diff` for the `NR_MLOCK`
counter if the thread is preempted and migrated to another CPU exactly during
the per-CPU variable update. It does not cause memory corruption, information
leaks, deadlocks, or system crashes. Therefore, it is **not exploitable** and
does **not cause a Denial of Service (DoS)**.
**Access & Triggers:**
Triggering this bug requires the ability to offline a CPU (e.g., by writing `0`
to `/sys/devices/system/cpu/cpu*/online`).
- **Unprivileged / User Namespaces:** CPU hotplug operations are strictly
restricted to the global root user. Sysfs files for CPU management are owned by
global root (`0644`) and are typically mounted read-only or are otherwise
inaccessible with sufficient privileges inside user namespaces. Thus, it is
**not accessible** to unprivileged users or from user namespaces.
- **VM Guest:** A root user inside a KVM guest can trigger this warning within
the guest kernel by offlining a virtual CPU, but this has no impact on the host
or other guests.
- **Remote / Peripheral / Filesystem:** The bug is tied to CPU hotplugging and
memory locking, and cannot be triggered via network traffic, untrusted
peripherals, or malicious filesystems. Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:true VMHostTrigger:false]
|
| 1/1 |
2026/08/29 00:01 |
action |
syz-repro-to-c-repro |
0m
Results: map[SimplifiedCRepro:// Copyright 2026 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
// IMPORTANT: Do not copy the macros or definitions below directly into your reproducer.
// Instead, add the following line to your reproducer:
// #include "race_toolkit.h"
// --- Race Condition Toolkit ---
// Macros and snippets for CPU pinning, memory barriers, and userfaultfd.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/futex.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
// Unbuffered I/O: Ensure logs are written immediately.
#define SETUP_UNBUFFERED_IO() setvbuf(stdout, NULL, _IONBF, 0)
// CPU Pinning: Pin the current thread to a specific CPU core.
#define PIN_TO_CPU(cpu) \
do { \
cpu_set_t mask; \
CPU_ZERO(&mask); \
CPU_SET(cpu, &mask); \
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { \
perror("sched_setaffinity"); \
} \
} while (0)
// Memory Barrier: Ensure memory ordering.
#define MB() __atomic_thread_fence(__ATOMIC_SEQ_CST)
// Spin-wait Barrier: Wait until a memory location has a specific value.
// Best for tight race windows (low latency, no context switches).
#define WAIT_ON(addr, val) \
do { \
while (__atomic_load_n(addr, __ATOMIC_ACQUIRE) != (val)) \
; \
} while (0)
// Signal: Set a memory location to a specific value to release a WAIT_ON.
#define SIGNAL(addr, val) __atomic_store_n(addr, val, __ATOMIC_RELEASE)
// --- Timing Primitives ---
// Robust timing loops in VM environments (using CLOCK_MONOTONIC to avoid time(NULL) jumps).
static inline double timer_elapsed_sec(struct timespec* start)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
perror("clock_gettime(CLOCK_MONOTONIC) elapsed");
exit(1);
}
return (double)(now.tv_sec - start->tv_sec) + (double)(now.tv_nsec - start->tv_nsec) / 1e9;
}
// Initialize a monotonic timer variable.
#define TIMER_START(t) \
struct timespec t; \
if (clock_gettime(CLOCK_MONOTONIC, &t) == -1) { \
perror("clock_gettime(CLOCK_MONOTONIC) start"); \
exit(1); \
}
// Check if the elapsed time since 't' is less than 'sec' seconds.
#define TIMER_NOT_EXPIRED(t, sec) (timer_elapsed_sec(&(t)) < (double)(sec))
// Futex-based Event: Shared with syzkaller executor.
// Best for general synchronization or longer waits to save CPU.
typedef struct {
int state;
} event_t;
static void event_init(event_t* ev)
{
ev->state = 0;
}
static void event_reset(event_t* ev)
{
ev->state = 0;
}
static void event_set(event_t* ev)
{
if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE)) {
fprintf(stderr, "event already set\n");
exit(1);
}
__atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
}
static void event_wait(event_t* ev)
{
while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
}
// userfaultfd setup: Register a memory range for page fault handling.
static int setup_uffd(void* addr, size_t len)
{
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
return -1;
struct uffdio_api api = {.api = UFFD_API, .features = 0};
if (ioctl(uffd, UFFDIO_API, &api) == -1) {
close(uffd);
return -1;
}
struct uffdio_register reg = {
.range = {.start = (uintptr_t)addr, .len = len},
.mode = UFFDIO_REGISTER_MODE_MISSING};
if (ioctl(uffd, UFFDIO_REGISTER, ®) == -1) {
close(uffd);
return -1;
}
return uffd;
}
// --- Guidance on Usage ---
// 1. Use WAIT_ON/SIGNAL for tight race conditions to avoid scheduling overhead.
// 2. Use event_t (futexes) for general coordination or when waiting for longer periods.
// 3. Always use PIN_TO_CPU to increase race probability on multi-core systems.
// 4. Use setup_uffd to register a memory range for page fault handling. This allows you to
// pause a thread accessing that memory until you handle the fault, creating a reliable
// and controllable race window.
// 5. Call SETUP_UNBUFFERED_IO() at the start of main() to ensure that logs are printed
// immediately. This is essential for understanding the exact interleaving of events
// when debugging race conditions.
// 6. For timing-based loops (e.g., running a race for 10 seconds), do NOT use time(NULL)
// or loops relying on real-time clocks, as VM clocks are highly unreliable and can fail or drift.
// Instead, use the robust monotonic timing primitives TIMER_START and TIMER_NOT_EXPIRED:
// TIMER_START(start);
// while (TIMER_NOT_EXPIRED(start, 10.0)) {
// // Your race logic here
// }
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
volatile int step = 0;
void *addr;
void *thread0(void *arg) {
PIN_TO_CPU(0);
WAIT_ON(&step, 1);
// Fault in the page on CPU 0.
// It gets added to CPU 0's lru_add batch, but not yet on the LRU list.
*(volatile char *)addr = 1;
printf("[+] thread0: faulted in page on CPU 0\n");
SIGNAL(&step, 2);
// Spin to prevent CPU 0 from draining its lru_add batch.
WAIT_ON(&step, 5);
return NULL;
}
void *thread1(void *arg) {
PIN_TO_CPU(1);
WAIT_ON(&step, 2);
// mlock the page on CPU 1.
// It adds the page to CPU 1's mlock_fbatch with LRU_FOLIO, then drains it.
// __mlock_folio sees the page is not on LRU (still in CPU 0's batch) and does nothing.
int res = mlock(addr, 4096);
if (res < 0) {
printf("[-] Failed to mlock: %s\n", strerror(errno));
exit(1);
}
printf("[+] mlock successful.\n");
// munmap the page on CPU 1.
// Since the VMA is locked, it calls munlock_vma_folio, which adds the page
// to CPU 1's mlock_fbatch with NO flags. munmap does not drain the batch.
res = munmap(addr, 4096);
if (res < 0) {
printf("[-] Failed to munmap: %s\n", strerror(errno));
exit(1);
}
printf("[+] munmap successful.\n");
SIGNAL(&step, 3);
// Sleep until the test finishes.
WAIT_ON(&step, 5);
return NULL;
}
int main() {
SETUP_UNBUFFERED_IO();
// Pin main thread to CPU 2+ to avoid interfering with CPU 0 and 1.
// If CPU 2 is not available, it will just fail and run anywhere.
cpu_set_t set;
CPU_ZERO(&set);
for (int i = 2; i < 8; i++) {
CPU_SET(i, &set);
}
int res = sched_setaffinity(0, sizeof(set), &set);
if (res < 0) {
printf("[-] Failed to sched_setaffinity (ignoring): %s\n", strerror(errno));
} else {
printf("[+] sched_setaffinity successful.\n");
}
// Ensure CPU 1 is online initially.
int fd = open("/sys/devices/system/cpu/cpu1/online", O_WRONLY);
if (fd < 0) {
printf("[-] Failed to open /sys/devices/system/cpu/cpu1/online: %s\n", strerror(errno));
exit(1);
}
printf("[+] open /sys/devices/system/cpu/cpu1/online successful.\n");
res = write(fd, "1\n", 2);
if (res < 0) {
printf("[-] Failed to write to /sys/devices/system/cpu/cpu1/online: %s\n", strerror(errno));
exit(1);
}
printf("[+] write to /sys/devices/system/cpu/cpu1/online successful (CPU 1 is online).\n");
res = close(fd);
if (res < 0) {
printf("[-] Failed to close fd: %s\n", strerror(errno));
exit(1);
}
printf("[+] close fd successful.\n");
addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
printf("[-] Failed to mmap: %s\n", strerror(errno));
exit(1);
}
printf("[+] mmap successful.\n");
pthread_t t0, t1;
res = pthread_create(&t0, NULL, thread0, NULL);
if (res != 0) {
printf("[-] Failed to pthread_create t0: %s\n", strerror(res));
exit(1);
}
printf("[+] pthread_create t0 successful.\n");
res = pthread_create(&t1, NULL, thread1, NULL);
if (res != 0) {
printf("[-] Failed to pthread_create t1: %s\n", strerror(res));
exit(1);
}
printf("[+] pthread_create t1 successful.\n");
SIGNAL(&step, 1);
WAIT_ON(&step, 3);
// Offline CPU 1.
// This triggers page_alloc_cpu_dead -> mlock_drain_remote on a surviving CPU.
// mlock_drain_remote calls __munlock_folio, which sees the page is not on LRU,
// skips taking the LRU lock, and calls __zone_stat_mod_folio with preemption enabled,
// triggering the warning.
fd = open("/sys/devices/system/cpu/cpu1/online", O_WRONLY);
if (fd < 0) {
printf("[-] Failed to open /sys/devices/system/cpu/cpu1/online: %s\n", strerror(errno));
exit(1);
}
printf("[+] open /sys/devices/system/cpu/cpu1/online successful.\n");
res = write(fd, "0\n", 2);
if (res < 0) {
printf("[-] Failed to write to /sys/devices/system/cpu/cpu1/online: %s\n", strerror(errno));
exit(1);
}
printf("[+] write to /sys/devices/system/cpu/cpu1/online successful (CPU 1 offlined).\n");
res = close(fd);
if (res < 0) {
printf("[-] Failed to close fd: %s\n", strerror(errno));
exit(1);
}
printf("[+] close fd successful.\n");
// Wait a bit to ensure the asynchronous warning is triggered and printed.
sleep(1);
SIGNAL(&step, 5);
res = pthread_join(t0, NULL);
if (res != 0) {
printf("[-] Failed to pthread_join t0: %s\n", strerror(res));
exit(1);
}
printf("[+] pthread_join t0 successful.\n");
res = pthread_join(t1, NULL);
if (res != 0) {
printf("[-] Failed to pthread_join t1: %s\n", strerror(res));
exit(1);
}
printf("[+] pthread_join t1 successful.\n");
// Restore CPU 1.
fd = open("/sys/devices/system/cpu/cpu1/online", O_WRONLY);
if (fd < 0) {
printf("[-] Failed to open /sys/devices/system/cpu/cpu1/online: %s\n", strerror(errno));
exit(1);
}
printf("[+] open /sys/devices/system/cpu/cpu1/online successful.\n");
res = write(fd, "1\n", 2);
if (res < 0) {
printf("[-] Failed to write to /sys/devices/system/cpu/cpu1/online: %s\n", strerror(errno));
exit(1);
}
printf("[+] write to /sys/devices/system/cpu/cpu1/online successful (CPU 1 restored).\n");
res = close(fd);
if (res < 0) {
printf("[-] Failed to close fd: %s\n", strerror(errno));
exit(1);
}
printf("[+] close fd successful.\n");
printf("[+] Done\n");
return 0;
}]
|
| 2/1 |
2026/08/29 00:01 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/d3e5e845ea1e86991b96377ebe5c58374353a72c]
|
| 3/1 |
2026/08/29 00:01 |
action |
kernel-builder |
0m
Results: map[KernelObj:/app/workdir/cache/build/87aaa677bb09399630dfb39dc4dfd962f565d50c]
|
| 4/1 |
2026/08/29 00:01 |
action |
codesearch-prepare |
0m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/08/29 00:01 |
agent |
expert |
2m
Model:
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:true VMHostTrigger:false]
Instruction: You are an experienced Linux kernel security engineer. Your task is to analyze given kernel bug report
and determine its security impact based on the following dimensions.
Use the provided tools to examine the source code, check for capability checks (e.g., capable(), ns_capable()),
and understand the nature of the bug. Analyze the given kernel build and configuration.
You can check the kernel config by grepping ".config" file; you can check kernel cmdline by grepping
".config" file for "CONFIG_CMDLINE=". Assume sysctl parameters have default values.
But analyze for the corresponding production build w/o debugging tools enabled (like KASAN, KMSAN, UBSAN).
Try different strategies when analyzing the bug:
- think of ways in which the vulnerable code is unreachable
- or the other way around: try to come up with different ideas of how an unprivileged user can reach the bug
If still unsure err on the side of the bug being non-exploitable/not-accessible.
In the final reply, provide a reasoning for your assessment.
Analysis dimensions:
* Exploitable:
Determine if the bug can result in memory corruption, elevated privileges, or an information leak.
Memory safety issues are almost always exploitable (KASAN or UBSAN reports for use-after-free, out-of-bounds;
refcounting issues, corrupted lists, etc). When kernel is crashing on a completely wild pointer access
(e.g. user-space address, or non-canonical address, but not on NULL or address corresponding to KASAN shadow
for NULL address), including both data accesses and control transfers, that also usually implies possibility
of exploitation. Such reports usually say "unable to handle kernel paging request".
Uses of uninitialized values detected by KMSAN may be exploitable b/c attacker frequently can affect uninit
values with spraying techniques. However, for these exploitability depends on how exactly the uninit value
is used in the code, and what it affects.
Information leaks are exploitable on their own and should be classified as such. A bug that copies kernel
memory contents to userspace (e.g. an out-of-bounds read whose result is returned to the caller, or
uninitialized stack/heap bytes written to a user buffer) is exploitable: it can reveal kernel pointer
values and defeat KASLR, expose sensitive data such as cryptographic keys or other processes' memory, and
serves as a necessary building block in most modern kernel privilege-escalation exploit chains. Do not classify
an information leak as non-exploitable solely because it does not directly cause a memory write or control-flow
hijack; the leak itself is the exploit primitive.
Think of what happens after the bug is triggered. Some bugs cause kernel panic and halt execution,
they are harder to exploit. For example, BUG reports halts the kernel. However, WARNING reports don't halt
execution in production builds. Debug bug detection tools (like KASAN, KMSAN, KCSAN, UBSAN) are also not enabled
in production builds, so attacker can freely exploit these bugs w/o being detected by these tools.
If you see an integer overflow, think how the overflowed value used later (if it's used as allocation size,
or an array index). If you see an out-of-bounds read, think if it's followed by an out-of-bounds write as well.
Some KCSAN data-races may be exploitable by skilled attackers as well. Think what data structures got corrupted
as the result of data races and how. However, note that kernel has lots of "benign" data races that don't lead
to any runtime misbehavior at all.
* Denial Of Service:
Determine if the bug can result in denial-of-service. Most bugs can, since they cause system crash,
hangs, deadlocks, or resource leaks. This is mostly applicable to WARNING bugs that won't cause system crash
in production. For these think what will be consequences of the violation of the kernel assumptions flagged
by the WARNING. In some cases the unexpected condition is also properly handled by the normal control flow
(e.g. with "if (WARN_ON(...))"), these won't cause denial-of-service. If the condition is not handled,
then it may or may not cause denial-of-service.
* Accessible From Unprivileged Processes:
Determine if the bug can be reached from a typical (non-root) user process that does NOT have any special capabilities
(like CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON) or access to device nodes restricted to root.
Assume that unprivileged_bpf_disabled=1, that is eBPF loading is not accessible. However, cBPF (classical BPF)
is still accessible to non-root processes.
Assume that user namespaces are not accessible, that is, the process cannot get the mentioned capabilities even
within a new user namespace (checked by ns_capable() function in the kernel sources).
* Accessible From User Namespaces:
Determine if the bug can be reached within a user-namespace where the process has all capabilities
(including CAP_SYS_ADMIN, CAP_NET_ADMIN, CAP_NET_RAW, CAP_PERFMON). Such capabilities are checked with ns_capable()
function in the kernel sources.
* VM Guest Trigger:
Determine if the bug can be triggered from the context of a typical KVM guest (e.g., set up by a QEMU VMM).
Consider accesses to standard Linux host paravirtualized features (virtio-blk, virtio-net, etc.),
and handling of VM exits in the KVM code.
* VM Host Trigger in The Confidential Computing Context:
Determine if the bug can be triggered in a confidential computing guest kernel from the context of a KVM host.
Consider access to standard Linux guest paravirtualized features (virtio-blk, virtio-net, etc.).
* Ethernet Network Trigger:
Determine if the bug can be triggered by processing ingress network Ethernet traffic, either directly (network stack)
or via drivers exposed to network data.
* Other Remote Trigger:
Determine if the bug can be triggered by processing remote traffic other than Ethernet (Wifi, Bluetooth, NFC, etc).
* Peripheral Trigger:
Determine if the bug can be triggered via an untrusted peripheral device that can be physically plugged
into a system, such as a USB device or a niche hardware driver handling external hardware inputs.
This is particularly important for mobile and desktop environments where users can plug in unknown devices.
* Malicious Filesystem Trigger:
Determine if the bug can be triggered by the kernel mounting and parsing a malicious filesystem image.
This is highly critical for Desktop and Mobile environments where external media or downloaded images
might be auto-mounted.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The kernel bug report is:
smpboot: CPU 1 is now offline
------------[ cut here ]------------
IS_ENABLED(CONFIG_PREEMPT_COUNT) && __lockdep_enabled && (preempt_count() == 0 && this_cpu_read(hardirqs_enabled))
WARNING: mm/vmstat.c:361 at __mod_zone_page_state+0x96/0x190 mm/vmstat.c:361, CPU#2: syz-executor412/6040
Modules linked in:
CPU: 2 UID: 0 PID: 6040 Comm: syz-executor412 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
RIP: 0010:__mod_zone_page_state+0x96/0x190 mm/vmstat.c:361
Code: d0 00 00 00 8b 05 3e 44 ea 0e 85 c0 74 1f 65 8b 05 87 e0 2e 12 65 0b 05 d0 99 2e 12 75 0f 65 8b 05 d3 db 2e 12 85 c0 74 04 90 <0f> 0b 90 48 c7 c7 c0 e7 ff 8b e8 3b b3 6d 09 65 48 0f be 5d 00 48
RSP: 0018:ffffc9000627f8e0 EFLAGS: 00010202
RAX: 0000000000000001 RBX: 0000000000000000 RCX: 1ffffffff2287150
RDX: 0000000000000000 RSI: 0000000000000008 RDI: ffff88807ffd79b8
RBP: ffffffff9489f448 R08: 0000000000000001 R09: 0000000000000000
R10: 0000000000000001 R11: 0000000000000000 R12: ffffffffffffffff
R13: 0000000000000008 R14: ffff88807ffd7940 R15: ffffffff9489f440
FS: 00005555785ce400(0000) GS:ffff8880d5da2000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00005608d531ffb8 CR3: 000000002cb7c000 CR4: 0000000000352ef0
Call Trace:
<TASK>
__zone_stat_mod_folio include/linux/vmstat.h:410 [inline]
__munlock_folio mm/mlock.c:144 [inline]
mlock_folio_batch+0xf97/0x36e0 mm/mlock.c:204
mlock_drain_remote+0xe5/0x140 mm/mlock.c:230
page_alloc_cpu_dead+0x16/0x130 mm/page_alloc.c:6468
cpuhp_invoke_callback+0x3b4/0x9a0 kernel/cpu.c:194
__cpuhp_invoke_callback_range+0x158/0x1d0 kernel/cpu.c:972
cpuhp_invoke_callback_range kernel/cpu.c:996 [inline]
cpuhp_down_callbacks kernel/cpu.c:1386 [inline]
_cpu_down+0x523/0x1020 kernel/cpu.c:1457
cpu_down_maps_locked kernel/cpu.c:1483 [inline]
cpu_down kernel/cpu.c:1491 [inline]
cpu_device_down+0x82/0xc0 kernel/cpu.c:1508
device_offline drivers/base/core.c:4283 [inline]
device_offline+0x2f4/0x400 drivers/base/core.c:4267
online_store+0xd1/0x180 drivers/base/core.c:2883
dev_attr_store+0x58/0x80 drivers/base/core.c:2509
sysfs_kf_write+0xf2/0x150 fs/sysfs/file.c:145
kernfs_fop_write_iter+0x3e0/0x5f0 fs/kernfs/file.c:345
new_sync_write fs/read_write.c:595 [inline]
vfs_write+0x6af/0x1050 fs/read_write.c:687
ksys_write+0x12a/0x250 fs/read_write.c:739
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x123/0x790 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f90996d005e
Code: 08 0f 85 f5 e2 ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 90 41 57 41 56 4d 89 c6 41 55 4d 89 cd 41 54 55 53 48 83 ec 08
RSP: 002b:00007ffd7eb94578 EFLAGS: 00000246 ORIG_RAX: 0000000000000001
RAX: ffffffffffffffda RBX: 00005555785ce400 RCX: 00007f90996d005e
RDX: 0000000000000002 RSI: 00007f90997150ff RDI: 0000000000000003
RBP: 00007f90997175e0 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 00007f9099717578
R13: 00007f909971509a R14: 00007f90997150b9 R15: 0000000000000002
</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.
// Copyright 2026 syzkaller project authors. All rights reserved.
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
// IMPORTANT: Do not copy the macros or definitions below directly into your reproducer.
// Instead, add the following line to your reproducer:
// #include "race_toolkit.h"
// --- Race Condition Toolkit ---
// Macros and snippets for CPU pinning, memory barriers, and userfaultfd.
#define _GNU_SOURCE
#include <errno.h>
#include <fcntl.h>
#include <linux/futex.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <time.h>
#include <unistd.h>
// Unbuffered I/O: Ensure logs are written immediately.
#define SETUP_UNBUFFERED_IO() setvbuf(stdout, NULL, _IONBF, 0)
// CPU Pinning: Pin the current thread to a specific CPU core.
#define PIN_TO_CPU(cpu) \
do { \
cpu_set_t mask; \
CPU_ZERO(&mask); \
CPU_SET(cpu, &mask); \
if (sched_setaffinity(0, sizeof(mask), &mask) == -1) { \
perror("sched_setaffinity"); \
} \
} while (0)
// Memory Barrier: Ensure memory ordering.
#define MB() __atomic_thread_fence(__ATOMIC_SEQ_CST)
// Spin-wait Barrier: Wait until a memory location has a specific value.
// Best for tight race windows (low latency, no context switches).
#define WAIT_ON(addr, val) \
do { \
while (__atomic_load_n(addr, __ATOMIC_ACQUIRE) != (val)) \
; \
} while (0)
// Signal: Set a memory location to a specific value to release a WAIT_ON.
#define SIGNAL(addr, val) __atomic_store_n(addr, val, __ATOMIC_RELEASE)
// --- Timing Primitives ---
// Robust timing loops in VM environments (using CLOCK_MONOTONIC to avoid time(NULL) jumps).
static inline double timer_elapsed_sec(struct timespec* start)
{
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
perror("clock_gettime(CLOCK_MONOTONIC) elapsed");
exit(1);
}
return (double)(now.tv_sec - start->tv_sec) + (double)(now.tv_nsec - start->tv_nsec) / 1e9;
}
// Initialize a monotonic timer variable.
#define TIMER_START(t) \
struct timespec t; \
if (clock_gettime(CLOCK_MONOTONIC, &t) == -1) { \
perror("clock_gettime(CLOCK_MONOTONIC) start"); \
exit(1); \
}
// Check if the elapsed time since 't' is less than 'sec' seconds.
#define TIMER_NOT_EXPIRED(t, sec) (timer_elapsed_sec(&(t)) < (double)(sec))
// Futex-based Event: Shared with syzkaller executor.
// Best for general synchronization or longer waits to save CPU.
typedef struct {
int state;
} event_t;
static void event_init(event_t* ev)
{
ev->state = 0;
}
static void event_reset(event_t* ev)
{
ev->state = 0;
}
static void event_set(event_t* ev)
{
if (__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE)) {
fprintf(stderr, "event already set\n");
exit(1);
}
__atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE);
syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000);
}
static void event_wait(event_t* ev)
{
while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE))
syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0);
}
// userfaultfd setup: Register a memory range for page fault handling.
static int setup_uffd(void* addr, size_t len)
{
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
if (uffd == -1)
return -1;
struct uffdio_api api = {.api = UFFD_API, .features = 0};
if (ioctl(uffd, UFFDIO_API, &api) == -1) {
close(uffd);
return -1;
}
struct uffdio_register reg = {
.range = {.start = (uintptr_t)addr, .len = len},
.mode = UFFDIO_REGISTER_MODE_MISSING};
if (ioctl(uffd, UFFDIO_REGISTER, ®) == -1) {
close(uffd);
return -1;
}
return uffd;
}
// --- Guidance on Usage ---
// 1. Use WAIT_ON/SIGNAL for tight race conditions to avoid scheduling overhead.
// 2. Use event_t (futexes) for general coordination or when waiting for longer periods.
// 3. Always use PIN_TO_CPU to increase race probability on multi-core systems.
// 4. Use setup_uffd to register a memory range for page fault handling. This allows you to
// pause a thread accessing that memory until you handle the fault, creating a reliable
// and controllable race window.
// 5. Call SETUP_UNBUFFERED_IO() at the start of main() to ensure that logs are printed
// immediately. This is essential for understanding the exact interleaving of events
// when debugging race conditions.
// 6. For timing-based loops (e.g., running a race for 10 seconds), do NOT use time(NULL)
// or loops relying on real-time clocks, as VM clocks are highly unreliable and can fail or drift.
// Instead, use the robust monotonic timing primitives TIMER_START and TIMER_NOT_EXPIRED:
// TIMER_START(start);
// while (TIMER_NOT_EXPIRED(start, 10.0)) {
// // Your race logic here
// }
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
volatile int step = 0;
void *addr;
void *thread0(void *arg) {
PIN_TO_CPU(0);
WAIT_ON(&step, 1);
// Fault in the page on CPU 0.
// It gets added to CPU 0's lru_add batch, but not yet on the LRU list.
*(volatile char *)addr = 1;
printf("[+] thread0: faulted in page on CPU 0\n");
SIGNAL(&step, 2);
// Spin to prevent CPU 0 from draining its lru_add batch.
WAIT_ON(&step, 5);
return NULL;
}
void *thread1(void *arg) {
PIN_TO_CPU(1);
WAIT_ON(&step, 2);
// mlock the page on CPU 1.
// It adds the page to CPU 1's mlock_fbatch with LRU_FOLIO, then drains it.
// __mlock_folio sees the page is not on LRU (still in CPU 0's batch) and does nothing.
int res = mlock(addr, 4096);
if (res < 0) {
printf("[-] Failed to mlock: %s\n", strerror(errno));
exit(1);
}
printf("[+] mlock successful.\n");
// munmap the page on CPU 1.
// Since the VMA is locked, it calls munlock_vma_folio, which adds the page
// to CPU 1's mlock_fbatch with NO flags. munmap does not drain the batch.
res = munmap(addr, 4096);
if (res < 0) {
printf("[-] Failed to munmap: %s\n", strerror(errno));
exit(1);
}
printf("[+] munmap successful.\n");
SIGNAL(&step, 3);
// Sleep until the test finishes.
WAIT_ON(&step, 5);
return NULL;
}
int main() {
SETUP_UNBUFFERED_IO();
// Pin main thread to CPU 2+ to avoid interfering with CPU 0 and 1.
// If CPU 2 is not available, it will just fail and run anywhere.
cpu_set_t set;
CPU_ZERO(&set);
for (int i = 2; i < 8; i++) {
CPU_SET(i, &set);
}
int res = sched_setaffinity(0, sizeof(set), &set);
if (res < 0) {
printf("[-] Failed to sched_setaffinity (ignoring): %s\n", strerror(errno));
} else {
printf("[+] sched_setaffinity successful.\n");
}
// Ensure CPU 1 is online initially.
int fd = open("/sys/devices/system/cpu/cpu1/online", O_WRONLY);
if (fd < 0) {
printf("[-] Failed to open /sys/devices/system/cpu/cpu1/online: %s\n", strerror(errno));
exit(1);
}
printf("[+] open /sys/devices/system/cpu/cpu1/online successful.\n");
res = write(fd, "1\n", 2);
if (res < 0) {
printf("[-] Failed to write to /sys/devices/system/cpu/cpu1/online: %s\n", strerror(errno));
exit(1);
}
printf("[+] write to /sys/devices/system/cpu/cpu1/online successful (CPU 1 is online).\n");
res = close(fd);
if (res < 0) {
printf("[-] Failed to close fd: %s\n", strerror(errno));
exit(1);
}
printf("[+] close fd successful.\n");
addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
printf("[-] Failed to mmap: %s\n", strerror(errno));
exit(1);
}
printf("[+] mmap successful.\n");
pthread_t t0, t1;
res = pthread_create(&t0, NULL, thread0, NULL);
if (res != 0) {
printf("[-] Failed to pthread_create t0: %s\n", strerror(res));
exit(1);
}
printf("[+] pthread_create t0 successful.\n");
res = pthread_create(&t1, NULL, thread1, NULL);
if (res != 0) {
printf("[-] Failed to pthread_create t1: %s\n", strerror(res));
exit(1);
}
printf("[+] pthread_create t1 successful.\n");
SIGNAL(&step, 1);
WAIT_ON(&step, 3);
// Offline CPU 1.
// This triggers page_alloc_cpu_dead -> mlock_drain_remote on a surviving CPU.
// mlock_drain_remote calls __munlock_folio, which sees the page is not on LRU,
// skips taking the LRU lock, and calls __zone_stat_mod_folio with preemption enabled,
// triggering the warning.
fd = open("/sys/devices/system/cpu/cpu1/online", O_WRONLY);
if (fd < 0) {
printf("[-] Failed to open /sys/devices/system/cpu/cpu1/online: %s\n", strerror(errno));
exit(1);
}
printf("[+] open /sys/devices/system/cpu/cpu1/online successful.\n");
res = write(fd, "0\n", 2);
if (res < 0) {
printf("[-] Failed to write to /sys/devices/system/cpu/cpu1/online: %s\n", strerror(errno));
exit(1);
}
printf("[+] write to /sys/devices/system/cpu/cpu1/online successful (CPU 1 offlined).\n");
res = close(fd);
if (res < 0) {
printf("[-] Failed to close fd: %s\n", strerror(errno));
exit(1);
}
printf("[+] close fd successful.\n");
// Wait a bit to ensure the asynchronous warning is triggered and printed.
sleep(1);
SIGNAL(&step, 5);
res = pthread_join(t0, NULL);
if (res != 0) {
printf("[-] Failed to pthread_join t0: %s\n", strerror(res));
exit(1);
}
printf("[+] pthread_join t0 successful.\n");
res = pthread_join(t1, NULL);
if (res != 0) {
printf("[-] Failed to pthread_join t1: %s\n", strerror(res));
exit(1);
}
printf("[+] pthread_join t1 successful.\n");
// Restore CPU 1.
fd = open("/sys/devices/system/cpu/cpu1/online", O_WRONLY);
if (fd < 0) {
printf("[-] Failed to open /sys/devices/system/cpu/cpu1/online: %s\n", strerror(errno));
exit(1);
}
printf("[+] open /sys/devices/system/cpu/cpu1/online successful.\n");
res = write(fd, "1\n", 2);
if (res < 0) {
printf("[-] Failed to write to /sys/devices/system/cpu/cpu1/online: %s\n", strerror(errno));
exit(1);
}
printf("[+] write to /sys/devices/system/cpu/cpu1/online successful (CPU 1 restored).\n");
res = close(fd);
if (res < 0) {
printf("[-] Failed to close fd: %s\n", strerror(errno));
exit(1);
}
printf("[+] close fd successful.\n");
printf("[+] Done\n");
return 0;
}
Reply: Based on the analysis of the bug report and the kernel source code, here is the assessment of the security impact:
**Bug Mechanism:**
The warning is triggered by `lockdep_assert_preemption_disabled()` (via the `preempt_disable_nested()` macro) inside `__mod_zone_page_state()`.
This occurs during a CPU offline event. When a CPU is offlined, the CPU hotplug callback `page_alloc_cpu_dead()` is invoked, which in turn calls `mlock_drain_remote()` to process the `mlock_fbatch` of the dying CPU.
Unlike other `mlock_folio_batch()` callers (which hold a `local_lock` that disables preemption), `mlock_drain_remote()` does not disable preemption. If a folio in the drained batch is not on the LRU list, `__munlock_folio()` skips locking the `lruvec` (which would have disabled interrupts/preemption) and directly calls `__zone_stat_mod_folio()`. This eventually calls `__mod_zone_page_state()`, which expects preemption to be disabled to safely update per-CPU statistics, thus triggering the warning.
**Exploitability & Denial of Service:**
The bug is a strict lockdep/preemption warning. The worst-case consequence of this bug is a minor accounting error in the `vm_stat_diff` for the `NR_MLOCK` counter if the thread is preempted and migrated to another CPU exactly during the per-CPU variable update. It does not cause memory corruption, information leaks, deadlocks, or system crashes. Therefore, it is **not exploitable** and does **not cause a Denial of Service (DoS)**.
**Access & Triggers:**
Triggering this bug requires the ability to offline a CPU (e.g., by writing `0` to `/sys/devices/system/cpu/cpu*/online`).
- **Unprivileged / User Namespaces:** CPU hotplug operations are strictly restricted to the global root user. Sysfs files for CPU management are owned by global root (`0644`) and are typically mounted read-only or are otherwise inaccessible with sufficient privileges inside user namespaces. Thus, it is **not accessible** to unprivileged users or from user namespaces.
- **VM Guest:** A root user inside a KVM guest can trigger this warning within the guest kernel by offlining a virtual CPU, but this has no impact on the host or other guests.
- **Remote / Peripheral / Filesystem:** The bug is tied to CPU hotplugging and memory locking, and cannot be triggered via network traffic, untrusted peripherals, or malicious filesystems.
|
| 6/2 |
2026/08/29 00:01 |
llm |
expert |
0m
Model:
Tokens:
input: 11783
output: 24
thoughts: 878
Thoughts: **Analyzing a Kernel Bug: CPU Offlining and Preemption**
Okay, so I've got this kernel bug report, and it looks like a warning is being triggered in `__mod_zone_page_state` when a CPU is being taken offline. The warning specifically mentions that preemption is enabled, which is a key observation. The stack trace points me towards `mlock_drain_remote` and, ultimately, the `page_alloc_cpu_dead` function.
My initial thought is that this is related to a CPU hotplug event, specifically a CPU being taken offline, as indicated by the "smpboot: CPU 1 is now offline" message. CPU offlining *should* require root privileges, writing to the `/sys/devices/system/cpu/cpu1/online` file, so the immediate question is whether this is something an unprivileged user can trigger. I need to be sure.
Since `page_alloc_cpu_dead` is a CPU hotplug callback, and CPU hotplug operations are generally restricted to root, the likely root cause is that this issue is related to CPU offlining done with the correct permissions. I'm going to double check, but I'm pretty sure that this is the only way this could be triggered.
Next, I need to check if there are other ways to trigger `mlock_drain_remote` or `page_alloc_cpu_dead` outside of the CPU offline path. If not, then this narrows down the focus considerably.
|
| 7/2 |
2026/08/29 00:01 |
tool |
codesearch-find-references |
0m
Args: map[Name:mlock_drain_remote]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:page_alloc_cpu_dead SourceFile:mm/page_alloc.c SourceLine:6468]] TruncatedOutput:false]
|
| 8/2 |
2026/08/29 00:01 |
llm |
expert |
0m
Model:
Tokens:
input: 13074
output: 22
thoughts: 172
Thoughts: **Analyzing the Root Access Vulnerability**
Okay, so `mlock_drain_remote` is exclusively called by `page_alloc_cpu_dead`. And, as I recall, `page_alloc_cpu_dead` is triggered by a CPU hotplug event, right? Right. That's a good starting point. My initial thought is to figure out whether an unprivileged user can somehow initiate this CPU hotplug scenario. If that's possible, then we have a much bigger problem.
My first check is the obvious: can you write to `/sys/devices/system/cpu/cpu*/online` without root privileges? Nope. That's a no-go. But I need to be thorough. Is there *any* other documented or known method to trigger CPU hotplug without root? I can't think of one off the top of my head, and my searches aren't turning anything up either. So, at least for now, it *seems* like this is root-only.
Wait a second. What about user namespaces? That's a classic trick for privilege escalation. I'll need to double check the user namespace implications. Ah, right, in user namespaces, the Sysfs is not fully writable, and that CPU hotplug is certainly restricted to global root. That's a relief. It looks like it is root-only, so that attack vector is blocked. Okay, I will now check the permission of `/sys/devices/system/cpu/cpu1/online` to verify my assumptions.
|
| 9/2 |
2026/08/29 00:01 |
tool |
grepper |
0m
Args: map[Expression:DEVICE_ATTR_RW\(online\)]
Results: map[Output:arch/s390/kernel/time.c=785=static ssize_t online_store(struct device *dev,
--
arch/s390/kernel/time.c-810- */
arch/s390/kernel/time.c:811:static DEVICE_ATTR_RW(online);
arch/s390/kernel/time.c-812-
--
drivers/base/core.c=2869=static ssize_t online_store(struct device *dev, const struct device_attribute *attr,
--
drivers/base/core.c-2886-}
drivers/base/core.c:2887:static const DEVICE_ATTR_RW(online);
drivers/base/core.c-2888-
--
drivers/s390/cio/device.c=592=static DEVICE_ATTR_RO(modalias);
drivers/s390/cio/device.c:593:static DEVICE_ATTR_RW(online);
drivers/s390/cio/device.c-594-static DEVICE_ATTR(availability, 0444, available_show, NULL);
--
drivers/s390/crypto/zcrypt_card.c=60=static ssize_t online_store(struct device *dev,
--
drivers/s390/crypto/zcrypt_card.c-112-
drivers/s390/crypto/zcrypt_card.c:113:static DEVICE_ATTR_RW(online);
drivers/s390/crypto/zcrypt_card.c-114-
--
drivers/s390/crypto/zcrypt_queue.c=50=static ssize_t online_store(struct device *dev,
--
drivers/s390/crypto/zcrypt_queue.c-79-
drivers/s390/crypto/zcrypt_queue.c:80:static DEVICE_ATTR_RW(online);
drivers/s390/crypto/zcrypt_queue.c-81-
--
drivers/xen/pcpu.c=108=static ssize_t online_store(struct device *dev,
--
drivers/xen/pcpu.c-136-}
drivers/xen/pcpu.c:137:static DEVICE_ATTR_RW(online);
drivers/xen/pcpu.c-138-
]
|
| 10/2 |
2026/08/29 00:01 |
llm |
expert |
0m
Model:
Tokens:
input: 14181
output: 38
thoughts: 467
Thoughts: **Analyzing a Kernel Warning**
Okay, so I'm looking at this `DEVICE_ATTR_RW` macro, and it expands to `0644`. That means read/write for root only, which immediately rules out unprivileged users. The real question is whether this is accessible within user namespaces. My experience tells me that `/sys` mounts are *usually* read-only in user namespaces, or at least heavily restricted. Even if it *were* writable, that file would be owned by the global root. So, even the root inside a user namespace wouldn't have write access unless there's an explicit `chown` or if the user namespace maps the global root, which defeats the purpose of being unprivileged. So, it's a dead end as far as accessibility goes.
Now, let's get into the warning itself. It's a `WARNING`: `IS_ENABLED(CONFIG_PREEMPT_COUNT) && __lockdep_enabled && (preempt_count() == 0 && this_cpu_read(hardirqs_enabled))`. This screams "preemption problem" to me. Specifically, it means `__mod_zone_page_state` is being called with preemption *enabled*, which is a no-no. That function uses per-CPU variables, and it *assumes* preemption is disabled. The issue here is that if a task gets preempted and migrated to another CPU while this function is running, we're going to have data races or, at a minimum, incorrect statistics.
Thinking about the impact, these incorrect stats would end up in `vmstat`, but it's probably mostly a benign warning or a minor accounting error. But is it exploitable? I'm thinking... no. It's a warning about preemption being inappropriately enabled, and that function alters per-CPU page state. If it gets preempted, it's like we might be changing the state of the wrong CPU, leading to messed up zone statistics. This isn't memory corruption, so it's not a direct path to exploitation.
Could it lead to a Denial of Service? Probably not in a typical production build. The warning itself won't crash the system. Those wonky statistics might lead to some minor hiccups, but not a full-blown DoS. I suppose, *potentially*, if the stats drift significantly, they *could* affect memory management decisions, possibly leading to an OOM situation or some performance degradation. But that's a long shot. I'll need to dig into `__mod_zone_page_state` a little deeper to really understand this.
|
| 11/2 |
2026/08/29 00:01 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/vmstat.c Name:__mod_zone_page_state]
Results: map[SourceCode: 341: /*
342: * For use when we know that interrupts are disabled,
343: * or when we know that preemption is disabled and that
344: * particular counter cannot be updated from interrupt context.
345: */
346: void __mod_zone_page_state(struct zone *zone, enum zone_stat_item item,
347: long delta)
348: {
349: struct per_cpu_zonestat __percpu *pcp = zone->per_cpu_zonestats;
350: s8 __percpu *p = pcp->vm_stat_diff + item;
351: long x;
352: long t;
353:
354: /*
355: * Accurate vmstat updates require a RMW. On !PREEMPT_RT kernels,
356: * atomicity is provided by IRQs being disabled -- either explicitly
357: * or via local_lock_irq. On PREEMPT_RT, local_lock_irq only disables
358: * CPU migrations and preemption potentially corrupts a counter so
359: * disable preemption.
360: */
361: preempt_disable_nested();
362:
363: x = delta + __this_cpu_read(*p);
364:
365: t = __this_cpu_read(pcp->stat_threshold);
366:
367: if (unlikely(abs(x) > t)) {
368: zone_page_state_add(x, zone, item);
369: x = 0;
370: }
371: __this_cpu_write(*p, x);
372:
373: preempt_enable_nested();
374: }
SourceFile:mm/vmstat.c]
|
| 12/2 |
2026/08/29 00:01 |
llm |
expert |
0m
Model:
Tokens:
input: 15781
output: 20
thoughts: 146
Thoughts: **Investigating a Preemption Issue**
Okay, hold on a second. I'm seeing that `__mod_zone_page_state` function uses `preempt_disable_nested()` and `preempt_enable_nested()`. That immediately piques my interest. My internal alarms are going off, thinking about the implications for preemption control in this area.
Now, the warning message mentions a specific line number, 361, within `mm/vmstat.c`. Let me take a look at that line. It seems to be related to the `CONFIG_PREEMPT_COUNT` configuration and lockdep. I need to understand the current preemption state within the function, so that I can figure out why the warning is being triggered in conjunction with the hard interrupts being enabled. Time to dig deeper into the code.
|
| 13/2 |
2026/08/29 00:02 |
tool |
grepper |
0m
Args: map[Expression:preempt_disable_nested]
Results: map[Output:Documentation/core-api/real-time/differences.rst=109=due to performance constraints, brevity of the code, or abstraction boundaries
Documentation/core-api/real-time/differences.rst:110:within an API then preempt_disable_nested() may be a suitable alternative. On
Documentation/core-api/real-time/differences.rst-111-non-PREEMPT_RT kernels, it verifies with lockdep that preemption is already
--
fs/dcache.c=2733=static inline unsigned start_dir_add(struct inode *dir)
fs/dcache.c-2734-{
fs/dcache.c:2735: preempt_disable_nested();
fs/dcache.c-2736- for (;;) {
--
include/linux/preempt.h=391=static inline void preempt_notifier_init(struct preempt_notifier *notifier,
--
include/linux/preempt.h-458-/**
include/linux/preempt.h:459: * preempt_disable_nested - Disable preemption inside a normally preempt disabled section
include/linux/preempt.h-460- *
--
include/linux/preempt.h-469- * interrupt context and regular interrupt handlers are preemptible and
include/linux/preempt.h:470: * only prevent migration. preempt_disable_nested() ensures that preemption
include/linux/preempt.h-471- * is disabled for cases which require CPU local serialization even on
--
include/linux/preempt.h-482-/* Macro to avoid header recursion hell vs. lockdep */
include/linux/preempt.h:483:#define preempt_disable_nested() \
include/linux/preempt.h-484-do { \
--
include/linux/preempt.h-491-/**
include/linux/preempt.h:492: * preempt_enable_nested - Undo the effect of preempt_disable_nested()
include/linux/preempt.h-493- */
--
include/linux/u64_stats_sync.h=169=static inline void __u64_stats_update_begin(struct u64_stats_sync *syncp)
include/linux/u64_stats_sync.h-170-{
include/linux/u64_stats_sync.h:171: preempt_disable_nested();
include/linux/u64_stats_sync.h-172- write_seqcount_begin(&syncp->seq);
--
mm/memcontrol-v1.c=619=void __memcg1_swapout(struct folio *folio, struct swap_cluster_info *ci)
--
mm/memcontrol-v1.c-671- */
mm/memcontrol-v1.c:672: preempt_disable_nested();
mm/memcontrol-v1.c-673- VM_WARN_ON_IRQS_ENABLED();
--
mm/vmstat.c=346=void __mod_zone_page_state(struct zone *zone, enum zone_stat_item item,
--
mm/vmstat.c-360- */
mm/vmstat.c:361: preempt_disable_nested();
mm/vmstat.c-362-
--
mm/vmstat.c=377=void __mod_node_page_state(struct pglist_data *pgdat, enum node_stat_item item,
--
mm/vmstat.c-396- /* See __mod_zone_page_state() */
mm/vmstat.c:397: preempt_disable_nested();
mm/vmstat.c-398-
--
mm/vmstat.c=436=void __inc_zone_state(struct zone *zone, enum zone_stat_item item)
--
mm/vmstat.c-442- /* See __mod_zone_page_state() */
mm/vmstat.c:443: preempt_disable_nested();
mm/vmstat.c-444-
--
mm/vmstat.c=457=void __inc_node_state(struct pglist_data *pgdat, enum node_stat_item item)
--
mm/vmstat.c-465- /* See __mod_zone_page_state() */
mm/vmstat.c:466: preempt_disable_nested();
mm/vmstat.c-467-
--
mm/vmstat.c=492=void __dec_zone_state(struct zone *zone, enum zone_stat_item item)
--
mm/vmstat.c-498- /* See __mod_zone_page_state() */
mm/vmstat.c:499: preempt_disable_nested();
mm/vmstat.c-500-
--
mm/vmstat.c=513=void __dec_node_state(struct pglist_data *pgdat, enum node_stat_item item)
--
mm/vmstat.c-521- /* See __mod_zone_page_state() */
mm/vmstat.c:522: preempt_disable_nested();
mm/vmstat.c-523-
--
net/core/gen_estimator.c=76=static void est_timer(struct timer_list *t)
--
net/core/gen_estimator.c-92-
net/core/gen_estimator.c:93: preempt_disable_nested();
net/core/gen_estimator.c-94- write_seqcount_begin(&est->seq);
--
net/ipv4/inet_connection_sock.c=1150=static bool reqsk_queue_hash_req(struct request_sock *req)
--
net/ipv4/inet_connection_sock.c-1160-
net/ipv4/inet_connection_sock.c:1161: preempt_disable_nested();
net/ipv4/inet_connection_sock.c-1162-
--
net/netfilter/ipvs/ip_vs_conn.c=643=void ip_vs_conn_fill_cport(struct ip_vs_conn *cp, __be16 cport)
--
net/netfilter/ipvs/ip_vs_conn.c-733- spin_lock(&t->lock[hash_key & t->lock_mask].l);
net/netfilter/ipvs/ip_vs_conn.c:734: preempt_disable_nested();
net/netfilter/ipvs/ip_vs_conn.c-735- write_seqcount_begin(&t->seqc[hash_key & t->seqc_mask]);
--
net/netfilter/ipvs/ip_vs_conn.c=896=static void conn_resize_work_handler(struct work_struct *work)
--
net/netfilter/ipvs/ip_vs_conn.c-966- spin_lock_bh(&t->lock[bucket & t->lock_mask].l);
net/netfilter/ipvs/ip_vs_conn.c:967: preempt_disable_nested();
net/netfilter/ipvs/ip_vs_conn.c-968- write_seqcount_begin(&t->seqc[bucket & t->seqc_mask]);
--
net/netfilter/ipvs/ip_vs_ctl.c=670=static void svc_resize_work_handler(struct work_struct *work)
--
net/netfilter/ipvs/ip_vs_ctl.c-749- local_bh_disable();
net/netfilter/ipvs/ip_vs_ctl.c:750: preempt_disable_nested();
net/netfilter/ipvs/ip_vs_ctl.c-751- write_seqcount_begin(sc);
]
|
| 14/2 |
2026/08/29 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 18088
output: 38
thoughts: 340
Thoughts: **Dissecting the `preempt_disable_nested()` Macro**
Okay, so `preempt_disable_nested()` is a macro. Nothing unexpected there. Let's see what's actually going on inside. Hmm, the code checks if `CONFIG_PREEMPT_COUNT` is enabled. If it is, then there's a `WARN_ON_ONCE` call. Right, I remember those β they're useful for debugging.
Now, let's break down that warning condition. It seems to be checking for `preempt_count() == 0` and also if hard interrupts are not enabled ( `!this_cpu_read(hardirqs_enabled)`). Hold on a second... isn't that condition the same as the one mentioned in the report? Yes, they both share the warning condition `IS_ENABLED(CONFIG_PREEMPT_COUNT) && __lockdep_enabled && (preempt_count() == 0 && this_cpu_read(hardirqs_enabled))`.
Let me quickly double-check that by looking back at `include/linux/preempt.h` to see how it's defined and how it matches up. Yup, it's identical, as expected. Alright, it seems I'm on the right track here! The macro is used to disable preemption, with a check for a potential issue where preemption is being disabled from a context that might lead to nested issues, assuming `CONFIG_PREEMPT_COUNT` is enabled and lockdep is.
|
| 15/2 |
2026/08/29 00:02 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:include/linux/preempt.h Name:preempt_disable_nested]
Results: map[SourceCode: SourceFile:]
|
| 16/2 |
2026/08/29 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 18813
output: 36
thoughts: 21
Thoughts: Here's a summary of my thinking, presented as if it were your own expert internal dialogue:
**Examining Preemption in the Linux Kernel**
Okay, time to dive into preemption details. I'm going to pull up `include/linux/preempt.h` and focus specifically around line 483. I need to refresh my understanding of how preemption is managed, especially considering the different contexts and scenarios it might be involved in. I suspect this area deals with the core mechanismsβperhaps the disabling and enabling routines, or maybe the specific data structures that are used to track preemption levels within a task. I also want to make sure I understand how these functions interact with the scheduler, and how they play a part in guaranteeing real-time responsiveness and system stability. I'll need to pay close attention to the lock usage and how it intersects with preemption handling, looking for any potential issues related to priority inversion or deadlocks. There could be some edge cases that I need to recall and re-examine.
|
| 17/2 |
2026/08/29 00:02 |
tool |
read-file |
0m
Args: map[File:include/linux/preempt.h FirstLine:470 LineCount:30]
Results: map[Contents: 470: * only prevent migration. preempt_disable_nested() ensures that preemption
471: * is disabled for cases which require CPU local serialization even on
472: * PREEMPT_RT. For non-PREEMPT_RT kernels this is a NOP.
473: *
474: * The use cases are code sequences which are not serialized by a
475: * particular lock instance, e.g.:
476: * - seqcount write side critical sections where the seqcount is not
477: * associated to a particular lock and therefore the automatic
478: * protection mechanism does not work. This prevents a live lock
479: * against a preempting high priority reader.
480: * - RMW per CPU variable updates like vmstat.
481: */
482: /* Macro to avoid header recursion hell vs. lockdep */
483: #define preempt_disable_nested() \
484: do { \
485: if (IS_ENABLED(CONFIG_PREEMPT_RT)) \
486: preempt_disable(); \
487: else \
488: lockdep_assert_preemption_disabled(); \
489: } while (0)
490:
491: /**
492: * preempt_enable_nested - Undo the effect of preempt_disable_nested()
493: */
494: static __always_inline void preempt_enable_nested(void)
495: {
496: if (IS_ENABLED(CONFIG_PREEMPT_RT))
497: preempt_enable();
498: }
499:
]
|
| 18/2 |
2026/08/29 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 19560
output: 36
thoughts: 330
Thoughts: **Debugging a Preemption Warning**
Okay, so the warning I'm seeing is stemming directly from a call to `lockdep_assert_preemption_disabled()`. That macro's definition is pretty straightforward β it's checking if preemption is enabled (`preempt_count() == 0`) while hard IRQs are enabled (`this_cpu_read(hardirqs_enabled)`) and lockdep is active. So, in this particular instance, `__mod_zone_page_state` is being invoked when preemption *shouldn't* be enabled, according to the code's expectations. Now the question is, why is preemption enabled in that context?
The call trace tells me that `mlock_drain_remote`, which calls `mlock_folio_batch`, is where this is coming from, all the way back to the page allocator when a CPU dies. Time to dive into the `mlock_drain_remote` and `mlock_folio_batch` functions. Let's see what's going on there.
|
| 19/2 |
2026/08/29 00:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/mlock.c Name:mlock_drain_remote]
Results: map[SourceCode: 223: void mlock_drain_remote(int cpu)
224: {
225: struct folio_batch *fbatch;
226:
227: WARN_ON_ONCE(cpu_online(cpu));
228: fbatch = &per_cpu(mlock_fbatch.fbatch, cpu);
229: if (folio_batch_count(fbatch))
230: mlock_folio_batch(fbatch);
231: }
SourceFile:mm/mlock.c]
|
| 20/2 |
2026/08/29 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 20317
output: 36
thoughts: 12
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into `mlock_folio_batch`**
Okay, time to dig into `mlock_folio_batch`. I'm familiar with the concept β bulk locking folios for performance. My brain's already running through the scenarios where this is used: memory management, particularly in NUMA architectures, and perhaps during critical paging operations. I need to refresh my understanding of the specific implementation though, and make sure it's the efficient approach. Are there any edge cases? I should also quickly check related functions. Are there any potential race conditions? I need to quickly analyze potential impact on concurrency and overall system performance. It's time to get a more granular view to make sure I'm up to speed on this specific implementation! Let's get to it.
|
| 21/2 |
2026/08/29 00:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/mlock.c Name:mlock_folio_batch]
Results: map[SourceCode: 179: /*
180: * mlock_folio_batch() is derived from folio_batch_move_lru(): perhaps that can
181: * make use of such folio pointer flags in future, but for now just keep it for
182: * mlock. We could use three separate folio batches instead, but one feels
183: * better (munlocking a full folio batch does not need to drain mlocking folio
184: * batches first).
185: */
186: static void mlock_folio_batch(struct folio_batch *fbatch)
187: {
188: struct lruvec *lruvec = NULL;
189: unsigned long mlock;
190: struct folio *folio;
191: int i;
192:
193: for (i = 0; i < folio_batch_count(fbatch); i++) {
194: folio = fbatch->folios[i];
195: mlock = (unsigned long)folio & (LRU_FOLIO | NEW_FOLIO);
196: folio = (struct folio *)((unsigned long)folio - mlock);
197: fbatch->folios[i] = folio;
198:
199: if (mlock & LRU_FOLIO)
200: lruvec = __mlock_folio(folio, lruvec);
201: else if (mlock & NEW_FOLIO)
202: lruvec = __mlock_new_folio(folio, lruvec);
203: else
204: lruvec = __munlock_folio(folio, lruvec);
205: }
206:
207: if (lruvec)
208: lruvec_unlock_irq(lruvec);
209: folios_put(fbatch);
210: }
SourceFile:mm/mlock.c]
|
| 22/2 |
2026/08/29 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 21098
output: 35
thoughts: 11
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Analyzing the `__munlock_folio` Function**
Alright, time to dive in. My first thought is to take a look at `__munlock_folio`. This suggests we're dealing with memory management at a pretty low level, likely kernel-side, or at least very intimately involved with the operating system's memory allocation scheme. The name itself, with the double underscore, immediately flags this as an internal or system-level function, not something I'd typically encounter in application-level code. "Munlock" tells me we're concerned with unlocking or unpinning a memory folio, which implies the folio had been previously locked, potentially to prevent swapping or ensure its availability for direct access. "Folio" itself, of course, is a direct pointer toward a block of memory pages, managed as a unit. So, the question is *why* we're calling this function and what context is prompting this investigation. I need to understand the larger context of what we're troubleshooting. Is this related to performance, a memory leak, a deadlock, or perhaps some security vulnerability linked to memory locking and unlocking? My mind is already racing through possible scenarios based on the nature of this function. I need to understand what this does, and why it is being called, and what impact it has.
|
| 23/2 |
2026/08/29 00:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/mlock.c Name:__munlock_folio]
Results: map[SourceCode: 122: static struct lruvec *__munlock_folio(struct folio *folio, struct lruvec *lruvec)
123: {
124: int nr_pages = folio_nr_pages(folio);
125: bool isolated = false;
126:
127: if (!folio_test_clear_lru(folio))
128: goto munlock;
129:
130: isolated = true;
131: lruvec = folio_lruvec_relock_irq(folio, lruvec);
132:
133: if (folio_test_unevictable(folio)) {
134: /* Then mlock_count is maintained, but might undercount */
135: if (folio->mlock_count)
136: folio->mlock_count--;
137: if (folio->mlock_count)
138: goto out;
139: }
140: /* else assume that was the last mlock: reclaim will fix it if not */
141:
142: munlock:
143: if (folio_test_clear_mlocked(folio)) {
144: __zone_stat_mod_folio(folio, NR_MLOCK, -nr_pages);
145: if (isolated || !folio_test_unevictable(folio))
146: __count_vm_events(UNEVICTABLE_PGMUNLOCKED, nr_pages);
147: else
148: __count_vm_events(UNEVICTABLE_PGSTRANDED, nr_pages);
149: }
150:
151: /* folio_evictable() has to be checked *after* clearing Mlocked */
152: if (isolated && folio_test_unevictable(folio) && folio_evictable(folio)) {
153: lruvec_del_folio(lruvec, folio);
154: folio_clear_unevictable(folio);
155: lruvec_add_folio(lruvec, folio);
156: __count_vm_events(UNEVICTABLE_PGRESCUED, nr_pages);
157: }
158: out:
159: if (isolated)
160: folio_set_lru(folio);
161: return lruvec;
162: }
SourceFile:mm/mlock.c]
|
| 24/2 |
2026/08/29 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 22138
output: 23
thoughts: 963
Thoughts: **Analyzing a Potential Preemption Bug in `__munlock_folio`**
Okay, let's break this down. It looks like we've got a preemption-related warning triggered in `__munlock_folio`. The core of the problem lies in this sequence: if a `folio` isn't on the LRU list, `__munlock_folio` skips locking the `lruvec`, and then calls `__zone_stat_mod_folio`. The latter function expects preemption to be disabled, but here it might still be enabled. This is the crux.
The reproducer shows this scenario: a page is initially touched on CPU 0 and added to its `lru_add` batch. Then CPU 1 mlocks and munmaps the page, so it's placed in CPU 1's `mlock_fbatch`. CPU 1 gets offlined, which triggers `page_alloc_cpu_dead` on another CPU (let's say CPU 2). This results in a call to `mlock_drain_remote`, which processes CPU 1's batch. Since the page still hasn't made its way to the LRU on CPU 0, `__munlock_folio` skips locking the `lruvec`, and then calls the function where preemption is unexpectedly enabled.
The consequence? Well, `__zone_stat_mod_folio` calls `__mod_zone_page_state`. The warning occurs when preemption is enabled. `__mod_zone_page_state` updates the per-cpu variable of the *current* CPU (CPU 2). If preemption is enabled, a context switch could occur between reading and updating this variable.
Now, is this exploitable? Not really. Itβs just a warning. This could lead to a minor accounting error in `vm_stat_diff`, but that's not critical. It's not memory corruption.
The question then becomes: can an unprivileged user trigger this? The reproducer offlines a CPU, which requires root privileges, so that's a no. Can we trigger `mlock_drain_remote` in any other way? It's only called from `page_alloc_cpu_dead`, which is a CPU hotplug callback. Is there any way for an unprivileged user to initiate CPU hotplug? No. Not directly, and not through user namespaces, either.
Essentially, there are no ways for an unprivileged user to trigger CPU hotplug.
However, just to be thorough, I should look for other potential call paths to `__munlock_folio` with preemption enabled.
|
| 25/2 |
2026/08/29 00:02 |
tool |
codesearch-find-references |
0m
Args: map[Name:__munlock_folio]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mlock_folio_batch SourceFile:mm/mlock.c SourceLine:204]] TruncatedOutput:false]
|
| 26/2 |
2026/08/29 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 23752
output: 24
thoughts: 36
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Investigating `__munlock_folio` Usage**
Right, `__munlock_folio` is only invoked from `mlock_folio_batch`. That's a crucial starting point. If I want to understand its overall impact, or potentially track down a bug, the next logical step is to trace the calling context of `mlock_folio_batch`. Where is *that* function being called from? Understanding the call chain will illuminate the circumstances under which `__munlock_folio` is actually being used and the implications of its execution. Time to dig into the call graph.
|
| 27/2 |
2026/08/29 00:02 |
tool |
codesearch-find-references |
0m
Args: map[Name:mlock_folio_batch]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mlock_drain_local SourceFile:mm/mlock.c SourceLine:219] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mlock_drain_remote SourceFile:mm/mlock.c SourceLine:230] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mlock_folio SourceFile:mm/mlock.c SourceLine:259] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mlock_new_folio SourceFile:mm/mlock.c SourceLine:282] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:munlock_folio SourceFile:mm/mlock.c SourceLine:303]] TruncatedOutput:false]
|
| 28/2 |
2026/08/29 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 24218
output: 36
thoughts: 71
Thoughts: **Analyzing the Folio Locking System**
Okay, so `mlock_folio_batch` is the key function here. I need to understand its role within this folio locking system. Let's see... it's being called from a few different places: `mlock_drain_local`, `mlock_drain_remote`, `mlock_folio`, `mlock_new_folio`, and `munlock_folio`. That's a decent range; it's used in both locking *and* unlocking, locally and remotely, plus folio creation.
I think the best place to start is with `mlock_drain_local`. That feels like a logical entry point for understanding the overall flow, especially given how `mlock_folio_batch` is at the center of all these operations. Diving into `mlock_drain_local` should help clarify how `mlock_folio_batch` fits into the broader picture of managing these memory folios, particularly in a local context.
|
| 29/2 |
2026/08/29 00:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/mlock.c Name:mlock_drain_local]
Results: map[SourceCode: 212: void mlock_drain_local(void)
213: {
214: struct folio_batch *fbatch;
215:
216: local_lock(&mlock_fbatch.lock);
217: fbatch = this_cpu_ptr(&mlock_fbatch.fbatch);
218: if (folio_batch_count(fbatch))
219: mlock_folio_batch(fbatch);
220: local_unlock(&mlock_fbatch.lock);
221: }
SourceFile:mm/mlock.c]
|
| 30/2 |
2026/08/29 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 24718
output: 34
thoughts: 79
Thoughts: Here's my summary, as I see it:
**Preemption Concerns in Memory Locking**
Okay, so I'm looking at `mlock_drain_local` and it's using `local_lock` to acquire the `mlock_fbatch.lock`. Now, I know `local_lock` is designed to disable preemption, or migration in a PREEMPT_RT system. That means, whenever `mlock_folio_batch` is called from within `mlock_drain_local`, preemption is effectively off. I need to keep that in mind. This has potential implications for real-time responsiveness and potential lock contention scenarios within that path. It's crucial to understand the duration of these non-preemptible regions. Next step, I need to investigate `mlock_folio` in more detail. I need to understand its impact on this preemption-disabled context.
|
| 31/2 |
2026/08/29 00:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/mlock.c Name:mlock_folio]
Results: map[SourceCode: 238: /**
239: * mlock_folio - mlock a folio already on (or temporarily off) LRU
240: * @folio: folio to be mlocked.
241: */
242: void mlock_folio(struct folio *folio)
243: {
244: struct folio_batch *fbatch;
245:
246: local_lock(&mlock_fbatch.lock);
247: fbatch = this_cpu_ptr(&mlock_fbatch.fbatch);
248:
249: if (!folio_test_set_mlocked(folio)) {
250: int nr_pages = folio_nr_pages(folio);
251:
252: zone_stat_mod_folio(folio, NR_MLOCK, nr_pages);
253: __count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
254: }
255:
256: folio_get(folio);
257: if (!folio_batch_add(fbatch, mlock_lru(folio)) ||
258: !folio_may_be_lru_cached(folio) || lru_cache_disabled())
259: mlock_folio_batch(fbatch);
260: local_unlock(&mlock_fbatch.lock);
261: }
SourceFile:mm/mlock.c]
|
| 32/2 |
2026/08/29 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 25438
output: 36
thoughts: 26
Thoughts: Here's my thinking:
**Examining Memory Locking in Folios**
Okay, so `mlock_folio` utilizes `local_lock`. Got it. That's a key interaction to understand. My immediate focus is on how new folios are being handled in relation to this locking mechanism. I need to dig into `mlock_new_folio` now to fully grasp the memory locking behavior at folio creation. I suspect it holds the answer to how concurrency is managed, specifically during the allocation and initialization of these memory structures.
|
| 33/2 |
2026/08/29 00:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/mlock.c Name:mlock_new_folio]
Results: map[SourceCode: 263: /**
264: * mlock_new_folio - mlock a newly allocated folio not yet on LRU
265: * @folio: folio to be mlocked, either normal or a THP head.
266: */
267: void mlock_new_folio(struct folio *folio)
268: {
269: struct folio_batch *fbatch;
270: int nr_pages = folio_nr_pages(folio);
271:
272: local_lock(&mlock_fbatch.lock);
273: fbatch = this_cpu_ptr(&mlock_fbatch.fbatch);
274: folio_set_mlocked(folio);
275:
276: zone_stat_mod_folio(folio, NR_MLOCK, nr_pages);
277: __count_vm_events(UNEVICTABLE_PGMLOCKED, nr_pages);
278:
279: folio_get(folio);
280: if (!folio_batch_add(fbatch, mlock_new(folio)) ||
281: !folio_may_be_lru_cached(folio) || lru_cache_disabled())
282: mlock_folio_batch(fbatch);
283: local_unlock(&mlock_fbatch.lock);
284: }
SourceFile:mm/mlock.c]
|
| 34/2 |
2026/08/29 00:02 |
llm |
expert |
0m
Model:
Tokens:
input: 26018
output: 34
thoughts: 26
Thoughts: Here's my thinking:
**Examining Memory Locking Mechanisms**
Okay, so I see that `mlock_new_folio` utilizes `local_lock`. That's interesting, and it suggests a connection between these two functions in terms of memory locking. I need to understand this better. Now, the natural next step is to investigate the counterpart of `mlock_new_folio`, which would be `munlock_folio`. I'll delve into the code for `munlock_folio` to grasp how memory unlocking is handled in relation to `local_lock`. My goal is to map out the memory locking and unlocking flows, especially any dependencies these functions have. This analysis of `munlock_folio` should reveal the complete lifecycle, which will help me understand the implications of the design decision in `mlock_new_folio`.
|
| 35/2 |
2026/08/29 00:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/mlock.c Name:munlock_folio]
Results: map[SourceCode: 286: /**
287: * munlock_folio - munlock a folio
288: * @folio: folio to be munlocked, either normal or a THP head.
289: */
290: void munlock_folio(struct folio *folio)
291: {
292: struct folio_batch *fbatch;
293:
294: local_lock(&mlock_fbatch.lock);
295: fbatch = this_cpu_ptr(&mlock_fbatch.fbatch);
296: /*
297: * folio_test_clear_mlocked(folio) must be left to __munlock_folio(),
298: * which will check whether the folio is multiply mlocked.
299: */
300: folio_get(folio);
301: if (!folio_batch_add(fbatch, folio) ||
302: !folio_may_be_lru_cached(folio) || lru_cache_disabled())
303: mlock_folio_batch(fbatch);
304: local_unlock(&mlock_fbatch.lock);
305: }
SourceFile:mm/mlock.c]
|
| 36/2 |
2026/08/29 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 26605
output: 25
thoughts: 178
Thoughts: **My Analysis of the `mlock_folio_batch` Vulnerability**
Okay, let's break this down. `munlock_folio` leverages `local_lock`, right? So, the only spot where `mlock_folio_batch` gets invoked *without* the `local_lock` β and crucially, without preemption disabled β is within `mlock_drain_remote`. Now, `mlock_drain_remote` is exclusively called from `page_alloc_cpu_dead`. And `page_alloc_cpu_dead`? That's a CPU hotplug callback. CPU hotplug operations, by design, require root privileges to initiate.
Therefore, considering all of this, the only actor that has a possibility to trigger this specific bug is root. I've examined the code carefully and I can't find any other path to `mlock_drain_remote` or `page_alloc_cpu_dead`. I will verify that assumption, but all indicators are that this is root-only.
|
| 37/2 |
2026/08/29 00:03 |
tool |
codesearch-find-references |
0m
Args: map[Name:page_alloc_cpu_dead]
Results: map[References:[map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:page_alloc_init_cpuhp SourceFile:mm/page_alloc.c SourceLine:6510]] TruncatedOutput:false]
|
| 38/2 |
2026/08/29 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 27103
output: 23
thoughts: 762
Thoughts: **Analyzing a Subtle Preemption Warning in `page_alloc_init_cpuhp`**
Okay, so I've pinpointed this potential issue down to its specific origin: `page_alloc_init_cpuhp`. The bug stems from how CPU hotplug operations, specifically CPU offlining, interact with memory management. It's a bit of a chain reaction: the CPU going offline triggers `page_alloc_cpu_dead`, which then calls `mlock_drain_remote(cpu)`. This is where it gets interesting β `mlock_drain_remote` works on the offlined CPU's `mlock_fbatch` without the proper preemption protections.
The crux of the matter is that when a folio within the batch isn't on the LRU list, `__munlock_folio` skips the `lruvec` lock and jumps straight to `__zone_stat_mod_folio`. This function, in turn, calls `__mod_zone_page_state`, which *expects* preemption to be disabled, and that's precisely where the warning gets triggered.
Now, the crucial question is, how bad is this, and who can trigger it? CPU offlining requires root privileges, so this isn't something an unprivileged user can exploit. User namespaces are out of the question because the control files for CPU online/offline are global root's domain.
Could ACPI events or hardware buttons cause CPU hotplug that triggers this? Unlikely, no unprivileged user could trigger this from software. What about KVM guests? Well, a guest root user *could* trigger it, but this bug resides in the *guest* kernel. Even if triggered in the guest, it's just a warning within the guest.
So, is it exploitable? No, not really. Itβs just a warning about the preemption state during an update to a per-CPU variable. The worst that could happen is a minor accounting error in `vm_stat_diff` for the `NR_MLOCK` counter, it's not memory corruption, no info leak and no priv escalation. No exploit.
Could it lead to a denial-of-service? No, in a production build, `WARN_ON_ONCE` will just print a warning, and the accounting error is unlikely to crash or hang the system, so no DoS.
Let me summarize:
* **Exploitable:** No. Minor accounting error, not security-related.
* **Denial of Service:** No. The warning doesn't crash the system, the accounting error is minor.
* **Privilege Required:** Requires root for CPU offline.
* **User Namespaces:** Not accessible.
* **VM Guest Trigger:** Yes, in guest kernel, not a host vulnerability.
* **Other Trigger Vectors:** Not accessible via: Confidential Computing, Ethernet Network, any remote method, via peripheral, via malicious file.
Hmm, wait, what about other paths to `__munlock_folio`? I should make sure I haven't missed any other possible call chains to `__munlock_folio`!
|
| 39/2 |
2026/08/29 00:03 |
tool |
codesearch-find-references |
0m
Args: map[Name:__munlock_folio]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:mlock_folio_batch SourceFile:mm/mlock.c SourceLine:204]] TruncatedOutput:false]
|
| 40/2 |
2026/08/29 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 28624
output: 26
thoughts: 26
Thoughts: Okay, here's my interpretation of those thoughts, summarized from a first-person expert perspective:
**Deep Dive on Memory Management Statistics**
Right, so I'm focusing on `mlock_folio_batch` for this particular analysis. But a nagging question keeps popping up. This function is just *one* caller of `__zone_stat_mod_folio`, right? It's crucial to understand how this code fits into the larger picture of memory management statistics. I can't just isolate one function. I need to be thinking holistically. What are the other call sites of `__zone_stat_mod_folio`? What kind of information are those functions using to affect the stats? Are there commonalities? Differences? Am I missing a bigger problem by just focusing on this particular function? I need to go back and consider the other callers too, or I'm going to miss something important about the system's behavior.
|
| 41/2 |
2026/08/29 00:03 |
tool |
codesearch-find-references |
0m
Args: map[Name:__zone_stat_mod_folio]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:__munlock_folio SourceFile:mm/mlock.c SourceLine:144] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:folio_account_dirtied SourceFile:mm/page-writeback.c SourceLine:2620]] TruncatedOutput:false]
|
| 42/2 |
2026/08/29 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 28993
output: 38
thoughts: 32
Thoughts: Here's my take on the thought process, as if it were my own:
**Diving into Folio Account Updates**
Okay, so `folio_account_dirtied` is the function that's being called. I need to understand what's triggering it, and what it does. The crucial next step appears to be investigating `__zone_stat_mod_folio`. I'm guessing `folio_account_dirtied` is responsible for setting something up, and `__zone_stat_mod_folio` is where the real work of modifying the zone's folio statistics occurs. I'm going to dig into `folio_account_dirtied` and trace what happens there and where it calls `__zone_stat_mod_folio`, likely to track dirty folio accounts.
|
| 43/2 |
2026/08/29 00:03 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:mm/page-writeback.c Name:folio_account_dirtied]
Results: map[SourceCode:2598: /*
2599: * Helper function for set_page_dirty family.
2600: *
2601: * NOTE: This relies on being atomic wrt interrupts.
2602: */
2603: static void folio_account_dirtied(struct folio *folio,
2604: struct address_space *mapping)
2605: {
2606: struct inode *inode = mapping->host;
2607:
2608: trace_writeback_dirty_folio(folio, mapping);
2609:
2610: if (mapping_can_writeback(mapping)) {
2611: struct bdi_writeback *wb;
2612: long nr = folio_nr_pages(folio);
2613:
2614: inode_attach_wb(inode, folio);
2615: wb = inode_to_wb(inode);
2616:
2617: lruvec_stat_mod_folio(folio, NR_FILE_DIRTY, nr);
2618: if (folio_test_dropbehind(folio))
2619: wb_stat_mod(wb, WB_DONTCACHE_DIRTY, nr);
2620: __zone_stat_mod_folio(folio, NR_ZONE_WRITE_PENDING, nr);
2621: __node_stat_mod_folio(folio, NR_DIRTIED, nr);
2622: wb_stat_mod(wb, WB_RECLAIMABLE, nr);
2623: wb_stat_mod(wb, WB_DIRTIED, nr);
2624: task_io_account_write(nr * PAGE_SIZE);
2625: current->nr_dirtied += nr;
2626: __this_cpu_add(bdp_ratelimits, nr);
2627:
2628: mem_cgroup_track_foreign_dirty(folio, wb);
2629: }
2630: }
SourceFile:mm/page-writeback.c]
|
| 44/2 |
2026/08/29 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 29794
output: 66
thoughts: 1416
Thoughts: **Analysis of the Folio Account Dirtying Warning**
Okay, so `folio_account_dirtied` clearly expects atomicity concerning interrupts, implying preemption needs to be disabled. The warning triggers if preemption is active, but the bug report specifically points to `__munlock_folio` -> `mlock_folio_batch` -> `mlock_drain_remote` -> `page_alloc_cpu_dead`. So, we're on the right track with our initial assessment.
Let's trace it again. `mlock_drain_remote` is called by `page_alloc_cpu_dead`, which is invoked during CPU offlining. CPU offlining is triggered by writing to `/sys/devices/system/cpu/cpu*/online`, owned by root with `0644` permissions. Only root can do this. Can an unprivileged user initiate CPU offlining some other way? No. User namespace root? Also no; `/sys/devices/system/cpu` isn't accessible, or at least not writable, due to the usual read-only mount in user namespaces or lack of permissions even if it was owned by the user namespace root.
So, other than root, there's no way to trigger `mlock_drain_remote`. We checked all references, and `page_alloc_cpu_dead` is only called as a CPU hotplug callback.
Therefore, this bug is root-only. It's just a preemption warning. Not exploitable. Not a DoS.
To double-check: Can `mlock_drain_remote` be called from another CPU hotplug callback? No, only `page_alloc_cpu_dead`. And is `page_alloc_cpu_dead` called elsewhere? No, it's only used in `page_alloc_init_cpuhp`. Is `mlock_folio_batch` called from some other location without a `local_lock`? We looked at all callers, and `mlock_drain_remote` is the *only* one.
Why doesn't `mlock_drain_remote` use `local_lock`? Because it deals with a *remote* CPU's batch, the one that is dying. It doesn't need to lock that remote CPU's data because that CPU is dead! But, `mlock_folio_batch` calls `__munlock_folio`, which in turn calls `__zone_stat_mod_folio`, which does call `__mod_zone_page_state`. This updates the *current* CPU's statistics and needs preemption disabled. Since `mlock_drain_remote` doesn't disable preemption, we get a warning.
The fix would either be to disable preemption in `mlock_drain_remote` or `mlock_folio_batch`, or use `zone_stat_mod_folio` if preemption isn't guaranteed to be disabled. Wait, `__munlock_folio` is usually called with `lruvec` locked, which disables interrupts (and thus preemption). It *only* calls `__zone_stat_mod_folio` without `lruvec` locked if the folio isn't on the LRU list. If it isn't on the LRU list it jumps to `munlock:` and calls `__zone_stat_mod_folio`. All other callers of `mlock_folio_batch` *do* have `local_lock` held, disabling preemption. In `mlock_drain_remote`, `local_lock` is NOT held. So, the fix must involve adding `local_lock(&mlock_fbatch.lock)` to the `mlock_drain_remote` function, or simply `preempt_disable()`. That looks right, and it makes sense: it would disable preemption.
```c
void mlock_drain_remote(int cpu)
{
struct folio_batch *fbatch;
WARN_ON_ONCE(cpu_online(cpu));
fbatch = &per_cpu(mlock_fbatch.fbatch, cpu);
if (folio_batch_count(fbatch)) {
local_lock(&mlock_fbatch.lock);
mlock_folio_batch(fbatch);
local_unlock(&mlock_fbatch.lock);
}
}
```
This confirms our analysis: the bug is exactly as described. It requires CPU hotplug (root). It's a warning, not an exploit, no DoS.
Time for the results:
* Exploitable: No
* DenialOfService: No
* Unprivileged: No
* UserNamespace: No
* VMGuestTrigger: Yes (
|
| 45/2 |
2026/08/29 00:03 |
tool |
set-results |
0m
Args: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:true VMHostTrigger:false]
Results: map[DenialOfService:false Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:false UserNamespace:false VMGuestTrigger:true VMHostTrigger:false]
|
| 46/2 |
2026/08/29 00:03 |
llm |
expert |
0m
Model:
Tokens:
input: 32365
output: 573
thoughts: 374
Thoughts: **My Analysis of the Kernel Warning**
Okay, the results are in, and now I need to articulate this clearly. Let's see... the root cause is pretty well-defined: this warning stems from `lockdep_assert_preemption_disabled()` firing within `__mod_zone_page_state()`. This is happening because `mlock_drain_remote()` isn't properly disabling preemption when processing the `mlock_fbatch` of a CPU that's been taken offline. The kicker is, if a folio in that batch *isn't* on the LRU list, `__munlock_folio()` skips the `lruvec` lock (which *would* have disabled interrupts/preemption) and jumps straight to `__zone_stat_mod_folio()`. That, in turn, calls `__mod_zone_page_state()`, and that's where things go sideways because it expects preemption to be off for safe per-CPU stat updates.
Now, the trigger. `mlock_drain_remote()` is only ever invoked by the CPU hotplug callback, `page_alloc_cpu_dead()`. So, the only way to actually hit this bug is by offlining a CPU. And as we know, CPU hotplug operations, like fiddling with `/sys/devices/system/cpu/cpu*/online`, require full root privileges, `CAP_SYS_ADMIN`, and root ownership of those sysfs files. That means this is not something Joe User can trigger from their unprivileged account or even within their user namespace. Good.
In terms of impact, it's pretty limited. We're looking at a kernel warning, and a potential minor accounting error in `vm_stat_diff` for the `NR_MLOCK` counter, if the process is preempted and migrated to another CPU mid-update. Nothing critical, thankfully. No memory corruption, no info leaks, no system crashes. So, not exploitable, not a Denial of Service. It's more of a cosmetic issue, really.
The only real caveat I see is that a root user inside a VM guest *could* trigger this by offlining a virtual CPU in the guest kernel, but that's isolated to the guest, and doesn't affect the host.
|
| 47/1 |
2026/08/29 00:03 |
action |
format-explanation |
0m
Results: map[Explanation:Based on the analysis of the bug report and the kernel source code, here is the
assessment of the security impact:
**Bug Mechanism:**
The warning is triggered by `lockdep_assert_preemption_disabled()` (via the
`preempt_disable_nested()` macro) inside `__mod_zone_page_state()`.
This occurs during a CPU offline event. When a CPU is offlined, the CPU hotplug
callback `page_alloc_cpu_dead()` is invoked, which in turn calls
`mlock_drain_remote()` to process the `mlock_fbatch` of the dying CPU.
Unlike other `mlock_folio_batch()` callers (which hold a `local_lock` that
disables preemption), `mlock_drain_remote()` does not disable preemption. If a
folio in the drained batch is not on the LRU list, `__munlock_folio()` skips
locking the `lruvec` (which would have disabled interrupts/preemption) and
directly calls `__zone_stat_mod_folio()`. This eventually calls
`__mod_zone_page_state()`, which expects preemption to be disabled to safely
update per-CPU statistics, thus triggering the warning.
**Exploitability & Denial of Service:**
The bug is a strict lockdep/preemption warning. The worst-case consequence of
this bug is a minor accounting error in the `vm_stat_diff` for the `NR_MLOCK`
counter if the thread is preempted and migrated to another CPU exactly during
the per-CPU variable update. It does not cause memory corruption, information
leaks, deadlocks, or system crashes. Therefore, it is **not exploitable** and
does **not cause a Denial of Service (DoS)**.
**Access & Triggers:**
Triggering this bug requires the ability to offline a CPU (e.g., by writing `0`
to `/sys/devices/system/cpu/cpu*/online`).
- **Unprivileged / User Namespaces:** CPU hotplug operations are strictly
restricted to the global root user. Sysfs files for CPU management are owned by
global root (`0644`) and are typically mounted read-only or are otherwise
inaccessible with sufficient privileges inside user namespaces. Thus, it is
**not accessible** to unprivileged users or from user namespaces.
- **VM Guest:** A root user inside a KVM guest can trigger this warning within
the guest kernel by offlining a virtual CPU, but this has no impact on the host
or other guests.
- **Remote / Peripheral / Filesystem:** The bug is tied to CPU hotplugging and
memory locking, and cannot be triggered via network traffic, untrusted
peripherals, or malicious filesystems.]
|