| 0/0 |
2026/08/09 00:11 |
flow |
assessment-security |
4m
Results: map[DenialOfService:true Explanation:**Analysis of the Bug**
The bug is a "sleeping function called from invalid context" warning that
specifically affects `PREEMPT_RT` kernels.
**Root Cause:**
The issue occurs in the KVM Xen emulation code. The function
`kvm_xen_set_evtchn_fast()` attempts to acquire `gpc->lock`, which is defined as
an `rwlock_t`. On standard kernels, `rwlock_t` is a spinlock and disables
interrupts, making it safe to use in atomic contexts. However, on `PREEMPT_RT`
kernels, `rwlock_t` is mapped to a sleeping lock (`rt_rw_lock`).
The bug is triggered because `kvm_xen_set_evtchn_fast()` is called by
`xen_timer_callback()`, which is an `hrtimer` callback that executes in hardirq
(atomic) context. Attempting to acquire a sleeping lock while in hardirq context
is invalid and triggers the kernel warning.
**Security Impact:**
- **Denial of Service (DoS):** If `gpc->lock` is contended when the timer
interrupt fires, the `PREEMPT_RT` locking primitives will call `schedule()` to
put the task to sleep. Calling `schedule()` from hardirq context is highly
destructive:
1. **Deadlocks:** If the timer interrupt preempted a task that was holding a
`raw_spinlock_t`, scheduling away leaves that lock held. Any other task
attempting to acquire the same `raw_spinlock_t` will spin indefinitely with
preemption disabled, resulting in a hard CPU deadlock.
2. **Interrupt Masking:** Scheduling away leaves the CPU's APIC In-Service
Register (ISR) bit set for the timer interrupt. This permanently masks all
interrupts of the same or lower priority on that CPU until the original task
is scheduled back and finishes the interrupt handler, leading to severe system
stalls or hardware watchdog panics.
3. **Kernel Panic:** If the kernel is booted with `panic_on_warn=1` (common in
syzkaller and some production environments), the warning itself will
immediately halt the system.
- **Exploitability:** The bug does not cause memory corruption (like
use-after-free or out-of-bounds accesses) or leak kernel memory to userspace.
The state of the interrupted task is safely preserved on the stack. Therefore,
it is not exploitable for privilege escalation or information disclosure.
**Trigger Vectors:**
- **Unprivileged Processes:** An unprivileged user on the host with access to
`/dev/kvm` (which is commonly granted via the `kvm` group or local udev ACLs)
can trigger this by creating a VM, setting up the Xen timer via the
`KVM_XEN_VCPU_SET_ATTR` ioctl, and waiting for the timer to expire.
- **VM Guest Trigger:** A malicious guest running under a VMM that has enabled
KVM Xen emulation (e.g., QEMU) can trigger this bug on the host by programming
the Xen timer via guest hypercalls (e.g., `set_timer_op`). When the timer
expires, the host kernel executes the hrtimer callback and hits the bug. Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:true VMHostTrigger:false]
|
| 1/1 |
2026/08/09 00:11 |
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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
uint64_t r[3] = {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff};
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000, /*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=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
intptr_t res = 0;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// sendmsg$NFT_BATCH arguments: [
// fd: sock_nl_netfilter (resource)
// msg: ptr[in, msghdr_netlink[nft_batch_msg]] {
// msghdr_netlink[nft_batch_msg] {
// addr: nil
// addrlen: len = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// vec: ptr[in, iovec[in, nft_batch_msg]] {
// iovec[in, nft_batch_msg] {
// addr: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {14 00 00 00 10 00 01 00 00 00 00 00 00 00 00 00 00 00 00 0a} (length 0x14)
// }
// }
// }
// len: len = 0xd4 (8 bytes)
// }
// }
// vlen: const = 0x1 (8 bytes)
// ctrl: const = 0x0 (8 bytes)
// ctrllen: const = 0x0 (8 bytes)
// f: send_flags = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// }
// f: send_flags = 0x4001 (8 bytes)
// ]
*(uint64_t*)0x2000000000c0 = 0;
*(uint32_t*)0x2000000000c8 = 0;
*(uint64_t*)0x2000000000d0 = 0x200000000000;
*(uint64_t*)0x200000000000 = 0x200000000100;
memcpy((void*)0x200000000100, "\x14\x00\x00\x00\x10\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a", 20);
*(uint64_t*)0x200000000008 = 0xd4;
*(uint64_t*)0x2000000000d8 = 1;
*(uint64_t*)0x2000000000e0 = 0;
*(uint64_t*)0x2000000000e8 = 0;
*(uint32_t*)0x2000000000f0 = 0;
syscall(__NR_sendmsg, /*fd=*/(intptr_t)-1, /*msg=*/0x2000000000c0ul, /*f=MSG_OOB|MSG_NOSIGNAL*/0x4001ul);
// openat$kvm arguments: [
// fd: const = 0xffffffffffffff9c (8 bytes)
// file: ptr[in, buffer] {
// buffer: {2f 64 65 76 2f 6b 76 6d 00} (length 0x9)
// }
// flags: open_flags = 0x0 (4 bytes)
// mode: const = 0x0 (2 bytes)
// ]
// returns fd_kvm
memcpy((void*)0x200000000000, "/dev/kvm\000", 9);
res = syscall(__NR_openat, /*fd=*/0xffffffffffffff9cul, /*file=*/0x200000000000ul, /*flags=*/0, /*mode=*/0);
if (res != -1)
r[0] = res;
// ioctl$KVM_CREATE_VM arguments: [
// fd: fd_kvm (resource)
// cmd: const = 0xae01 (4 bytes)
// type: intptr = 0x0 (8 bytes)
// ]
// returns fd_kvmvm
res = syscall(__NR_ioctl, /*fd=*/r[0], /*cmd=*/0xae01, /*type=*/0ul);
if (res != -1)
r[1] = res;
// ioctl$KVM_CREATE_VCPU arguments: [
// fd: fd_kvmvm (resource)
// cmd: const = 0xae41 (4 bytes)
// id: intptr = 0x0 (8 bytes)
// ]
// returns fd_kvmcpu
res = syscall(__NR_ioctl, /*fd=*/r[1], /*cmd=*/0xae41, /*id=*/0ul);
if (res != -1)
r[2] = res;
// ioctl arguments: [
// fd: fd (resource)
// cmd: int32 = 0x4048aecb (4 bytes)
// arg: ptr[in, buffer] {
// buffer: {07 00 00 00 00 00 00 00 01 00 00 00 ff ff ff ff} (length 0x10)
// }
// ]
memcpy((void*)0x200000000100, "\x07\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff", 16);
syscall(__NR_ioctl, /*fd=*/r[2], /*cmd=*/0x4048aecb, /*arg=*/0x200000000100ul);
return 0;
}
]
|
| 2/1 |
2026/08/09 00:11 |
action |
kernel-checkouter |
0m
Results: map[KernelSrc:/app/workdir/cache/src/26766716579cdcff13b5e3416aa2d1ff891e8337]
|
| 3/1 |
2026/08/09 00:11 |
action |
kernel-builder |
0m
Results: map[KernelObj:/app/workdir/cache/build/f8a628c752ebc7bff60482a8688df256d2cc50bc]
|
| 4/1 |
2026/08/09 00:11 |
action |
codesearch-prepare |
0m
Results: map[Index:codesearch-index]
|
| 5/1 |
2026/08/09 00:11 |
agent |
expert |
4m
Model:
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true 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:
BUG: sleeping function called from invalid context at kernel/locking/spinlock_rt.c:248
in_atomic(): 1, irqs_disabled(): 1, non_block: 0, pid: 5622, name: syz-executor363
preempt_count: 10000, expected: 0
RCU nest depth: 1, expected: 1
locks held by syz-executor363/5622: 4, last CPU#0:
#0: ffff88804093d380 (&sb->s_type->i_mutex_key#8/2){+.+.}-{4:4}, at: inode_lock_nested include/linux/fs.h:1069 [inline]
#0: ffff88804093d380 (&sb->s_type->i_mutex_key#8/2){+.+.}-{4:4}, at: __simple_recursive_removal+0x19a/0x520 fs/libfs.c:619
#1: ffffffff8e53c398 (pin_fs_lock){+.+.}-{3:3}, at: spin_lock include/linux/spinlock_rt.h:45 [inline]
#1: ffffffff8e53c398 (pin_fs_lock){+.+.}-{3:3}, at: simple_release_fs+0x2f/0xd0 fs/libfs.c:1120
#2: ffffffff8e3c3880 (rcu_read_lock){....}-{1:3}, at: rtlock_slowlock_locked+0x2ab/0x3c20 kernel/locking/rtmutex.c:1874
#3: ffff888038241b48 (&kvm->srcu){.?.+}-{0:0}, at: srcu_lock_acquire include/linux/srcu.h:187 [inline]
#3: ffff888038241b48 (&kvm->srcu){.?.+}-{0:0}, at: srcu_read_lock include/linux/srcu.h:294 [inline]
#3: ffff888038241b48 (&kvm->srcu){.?.+}-{0:0}, at: kvm_xen_set_evtchn_fast+0x1be/0x990 arch/x86/kvm/xen.c:1827
irq event stamp: 13272
hardirqs last enabled at (13271): [<ffffffff8b53b4b3>] __raw_spin_unlock_irq include/linux/spinlock_api_smp.h:187 [inline]
hardirqs last enabled at (13271): [<ffffffff8b53b4b3>] _raw_spin_unlock_irq+0x23/0x50 kernel/locking/spinlock.c:206
hardirqs last disabled at (13272): [<ffffffff8b501aee>] sysvec_apic_timer_interrupt+0xe/0xc0 arch/x86/kernel/apic/apic.c:1062
softirqs last enabled at (6940): [<ffffffff81882fe0>] __local_bh_enable_ip+0x1a0/0x2b0 kernel/softirq.c:305
softirqs last disabled at (6932): [<ffffffff891cda09>] local_bh_disable include/linux/bottom_half.h:20 [inline]
softirqs last disabled at (6932): [<ffffffff891cda09>] __alloc_skb+0x189/0x7a0 net/core/skbuff.c:697
Preemption disabled at:
[<ffffffff81883b8e>] irq_enter_rcu+0x1e/0x1f0 kernel/softirq.c:668
CPU: 0 UID: 0 PID: 5622 Comm: syz-executor363 Not tainted syzkaller #0 PREEMPT_{RT,(full)}
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026
Call Trace:
<IRQ>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
__might_resched+0x329/0x480 kernel/sched/core.c:9194
rt_read_lock+0xa9/0x4b0 kernel/locking/spinlock_rt.c:248
kvm_xen_set_evtchn_fast+0x1f4/0x990 arch/x86/kvm/xen.c:1829
xen_timer_callback+0x109/0x220 arch/x86/kvm/xen.c:141
__run_hrtimer kernel/time/hrtimer.c:2065 [inline]
__hrtimer_run_queues+0x3a0/0xaf0 kernel/time/hrtimer.c:2122
hrtimer_interrupt+0x44a/0x900 kernel/time/hrtimer.c:2241
local_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1051 [inline]
__sysvec_apic_timer_interrupt+0x102/0x430 arch/x86/kernel/apic/apic.c:1068
instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
sysvec_apic_timer_interrupt+0xa1/0xc0 arch/x86/kernel/apic/apic.c:1062
</IRQ>
<TASK>
asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
RIP: 0010:rt_mutex_owner include/linux/rtmutex.h:52 [inline]
RIP: 0010:rtmutex_spin_on_owner kernel/locking/rtmutex.c:1504 [inline]
RIP: 0010:rtlock_slowlock_locked+0x34e5/0x3c20 kernel/locking/rtmutex.c:1893
Code: 80 3c 01 00 4c 8b b4 24 80 00 00 00 4c 8b 2c 24 74 08 4c 89 ef e8 db 0e df f6 48 8d 84 24 60 01 00 00 49 39 45 00 75 6c f3 90 <48> b8 00 00 00 00 00 fc ff df 48 8b 8c 24 a8 00 00 00 80 3c 01 00
RSP: 0018:ffffc90004e6f7a0 EFLAGS: 00000246
RAX: ffffc90004e6f900 RBX: 1ffff11006ff9bbe RCX: 1ffffffff1ca7871
RDX: 00000000e6b90d15 RSI: ffffffff8bcbd060 RDI: 0000000000000001
RBP: ffffc90004e6f9f0 R08: 1d00000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: ffffffff8e3c3880 R12: ffff888037fcddc0
R13: ffffffff8e53c388 R14: ffffffff8e53c390 R15: 1ffff11006ff9bba
rtlock_slowlock kernel/locking/rtmutex.c:1922 [inline]
rtlock_lock kernel/locking/spinlock_rt.c:43 [inline]
__rt_spin_lock kernel/locking/spinlock_rt.c:49 [inline]
rt_spin_lock+0x165/0x400 kernel/locking/spinlock_rt.c:57
spin_lock include/linux/spinlock_rt.h:45 [inline]
simple_release_fs+0x2f/0xd0 fs/libfs.c:1120
__simple_recursive_removal+0x215/0x520 fs/libfs.c:623
debugfs_remove+0x5b/0x70 fs/debugfs/inode.c:781
kvm_destroy_vm_debugfs virt/kvm/kvm_main.c:988 [inline]
kvm_destroy_vm virt/kvm/kvm_main.c:1261 [inline]
kvm_put_kvm+0x147/0xae0 virt/kvm/kvm_main.c:1331
kvm_vm_release+0x46/0x50 virt/kvm/kvm_main.c:1354
__fput+0x42a/0xa80 fs/file_table.c:512
task_work_run+0x1d9/0x270 kernel/task_work.c:233
exit_task_work include/linux/task_work.h:40 [inline]
do_exit+0x73a/0x2360 kernel/exit.c:1009
do_group_exit+0x22d/0x2f0 kernel/exit.c:1152
__do_sys_exit_group kernel/exit.c:1163 [inline]
__se_sys_exit_group kernel/exit.c:1161 [inline]
__x64_sys_exit_group+0x3f/0x40 kernel/exit.c:1161
x64_sys_call+0x18bf/0x18c0 arch/x86/include/generated/asm/syscalls_64.h:232
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x17b/0x530 arch/x86/entry/syscall_64.c:85
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f132ec60d05
Code: Unable to access opcode bytes at 0x7f132ec60cdb.
RSP: 002b:00007ffe413e3968 EFLAGS: 00000202 ORIG_RAX: 00000000000000e7
RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 00007f132ec60d05
RDX: 00000000000000e7 RSI: ffffffffffffffd8 RDI: 0000000000000000
RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000009 R11: 0000000000000202 R12: 0000000000000000
R13: 00007f132ecfa118 R14: 00007f132ecfc3c8 R15: 00007f132ecfc3e0
</TASK>
=============================
[ BUG: Invalid wait context ]
syzkaller #0 Tainted: G W
-----------------------------
syz-executor363/5622 is trying to lock:
ffff888038241530 (&gpc->lock){....}-{3:3}, at: kvm_xen_set_evtchn_fast+0x1f4/0x990 arch/x86/kvm/xen.c:1829
other info that might help us debug this:
context-{2:2}
locks held by syz-executor363/5622: 4, last CPU#0:
#0: ffff88804093d380 (&sb->s_type->i_mutex_key#8/2){+.+.}-{4:4}, at: inode_lock_nested include/linux/fs.h:1069 [inline]
#0: ffff88804093d380 (&sb->s_type->i_mutex_key#8/2){+.+.}-{4:4}, at: __simple_recursive_removal+0x19a/0x520 fs/libfs.c:619
#1: ffffffff8e53c398 (pin_fs_lock){+.+.}-{3:3}, at: spin_lock include/linux/spinlock_rt.h:45 [inline]
#1: ffffffff8e53c398 (pin_fs_lock){+.+.}-{3:3}, at: simple_release_fs+0x2f/0xd0 fs/libfs.c:1120
#2: ffffffff8e3c3880 (rcu_read_lock){....}-{1:3}, at: rtlock_slowlock_locked+0x2ab/0x3c20 kernel/locking/rtmutex.c:1874
#3: ffff888038241b48 (&kvm->srcu){.?.+}-{0:0}, at: srcu_lock_acquire include/linux/srcu.h:187 [inline]
#3: ffff888038241b48 (&kvm->srcu){.?.+}-{0:0}, at: srcu_read_lock include/linux/srcu.h:294 [inline]
#3: ffff888038241b48 (&kvm->srcu){.?.+}-{0:0}, at: kvm_xen_set_evtchn_fast+0x1be/0x990 arch/x86/kvm/xen.c:1827
stack backtrace:
CPU: 0 UID: 0 PID: 5622 Comm: syz-executor363 Tainted: G W syzkaller #0 PREEMPT_{RT,(full)}
Tainted: [W]=WARN
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 07/24/2026
Call Trace:
<IRQ>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
print_lock_invalid_wait_context kernel/locking/lockdep.c:4846 [inline]
check_wait_context kernel/locking/lockdep.c:4918 [inline]
__lock_acquire+0xef0/0x2e40 kernel/locking/lockdep.c:5204
lock_acquire+0x115/0x350 kernel/locking/lockdep.c:5906
rt_read_lock+0xcc/0x4b0 kernel/locking/spinlock_rt.c:249
kvm_xen_set_evtchn_fast+0x1f4/0x990 arch/x86/kvm/xen.c:1829
xen_timer_callback+0x109/0x220 arch/x86/kvm/xen.c:141
__run_hrtimer kernel/time/hrtimer.c:2065 [inline]
__hrtimer_run_queues+0x3a0/0xaf0 kernel/time/hrtimer.c:2122
hrtimer_interrupt+0x44a/0x900 kernel/time/hrtimer.c:2241
local_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1051 [inline]
__sysvec_apic_timer_interrupt+0x102/0x430 arch/x86/kernel/apic/apic.c:1068
instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
sysvec_apic_timer_interrupt+0xa1/0xc0 arch/x86/kernel/apic/apic.c:1062
</IRQ>
<TASK>
asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
RIP: 0010:rt_mutex_owner include/linux/rtmutex.h:52 [inline]
RIP: 0010:rtmutex_spin_on_owner kernel/locking/rtmutex.c:1504 [inline]
RIP: 0010:rtlock_slowlock_locked+0x34e5/0x3c20 kernel/locking/rtmutex.c:1893
Code: 80 3c 01 00 4c 8b b4 24 80 00 00 00 4c 8b 2c 24 74 08 4c 89 ef e8 db 0e df f6 48 8d 84 24 60 01 00 00 49 39 45 00 75 6c f3 90 <48> b8 00 00 00 00 00 fc ff df 48 8b 8c 24 a8 00 00 00 80 3c 01 00
RSP: 0018:ffffc90004e6f7a0 EFLAGS: 00000246
RAX: ffffc90004e6f900 RBX: 1ffff11006ff9bbe RCX: 1ffffffff1ca7871
RDX: 00000000e6b90d15 RSI: ffffffff8bcbd060 RDI: 0000000000000001
RBP: ffffc90004e6f9f0 R08: 1d00000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: ffffffff8e3c3880 R12: ffff888037fcddc0
R13: ffffffff8e53c388 R14: ffffffff8e53c390 R15: 1ffff11006ff9bba
rtlock_slowlock kernel/locking/rtmutex.c:1922 [inline]
rtlock_lock kernel/locking/spinlock_rt.c:43 [inline]
__rt_spin_lock kernel/locking/spinlock_rt.c:49 [inline]
rt_spin_lock+0x165/0x400 kernel/locking/spinlock_rt.c:57
spin_lock include/linux/spinlock_rt.h:45 [inline]
simple_release_fs+0x2f/0xd0 fs/libfs.c:1120
__simple_recursive_removal+0x215/0x520 fs/libfs.c:623
debugfs_remove+0x5b/0x70 fs/debugfs/inode.c:781
kvm_destroy_vm_debugfs virt/kvm/kvm_main.c:988 [inline]
kvm_destroy_vm virt/kvm/kvm_main.c:1261 [inline]
kvm_put_kvm+0x147/0xae0 virt/kvm/kvm_main.c:1331
kvm_vm_release+0x46/0x50 virt/kvm/kvm_main.c:1354
__fput+0x42a/0xa80 fs/file_table.c:512
task_work_run+0x1d9/0x270 kernel/task_work.c:233
exit_task_work include/linux/task_work.h:40 [inline]
do_exit+0x73a/0x2360 kernel/exit.c:1009
do_group_exit+0x22d/0x2f0 kernel/exit.c:1152
__do_sys_exit_group kernel/exit.c:1163 [inline]
__se_sys_exit_group kernel/exit.c:1161 [inline]
__x64_sys_exit_group+0x3f/0x40 kernel/exit.c:1161
x64_sys_call+0x18bf/0x18c0 arch/x86/include/generated/asm/syscalls_64.h:232
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x17b/0x530 arch/x86/entry/syscall_64.c:85
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f132ec60d05
Code: Unable to access opcode bytes at 0x7f132ec60cdb.
RSP: 002b:00007ffe413e3968 EFLAGS: 00000202 ORIG_RAX: 00000000000000e7
RAX: ffffffffffffffda RBX: 0000000000000001 RCX: 00007f132ec60d05
RDX: 00000000000000e7 RSI: ffffffffffffffd8 RDI: 0000000000000000
RBP: 0000000000000000 R08: 0000000000000000 R09: 0000000000000009
R10: 0000000000000009 R11: 0000000000000202 R12: 0000000000000000
R13: 00007f132ecfa118 R14: 00007f132ecfc3c8 R15: 00007f132ecfc3e0
</TASK>
----------------
Code disassembly (best guess):
0: 80 3c 01 00 cmpb $0x0,(%rcx,%rax,1)
4: 4c 8b b4 24 80 00 00 mov 0x80(%rsp),%r14
b: 00
c: 4c 8b 2c 24 mov (%rsp),%r13
10: 74 08 je 0x1a
12: 4c 89 ef mov %r13,%rdi
15: e8 db 0e df f6 call 0xf6df0ef5
1a: 48 8d 84 24 60 01 00 lea 0x160(%rsp),%rax
21: 00
22: 49 39 45 00 cmp %rax,0x0(%r13)
26: 75 6c jne 0x94
28: f3 90 pause
* 2a: 48 b8 00 00 00 00 00 movabs $0xdffffc0000000000,%rax <-- trapping instruction
31: fc ff df
34: 48 8b 8c 24 a8 00 00 mov 0xa8(%rsp),%rcx
3b: 00
3c: 80 3c 01 00 cmpb $0x0,(%rcx,%rax,1)
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 <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
uint64_t r[3] = {0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff};
int main(void)
{
syscall(__NR_mmap, /*addr=*/0x1ffffffff000ul, /*len=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
syscall(__NR_mmap, /*addr=*/0x200000000000ul, /*len=*/0x1000000, /*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=*/0x1000, /*prot=*/0ul, /*flags=MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE*/0x32ul, /*fd=*/(intptr_t)-1, /*offset=*/0ul);
const char* reason;
(void)reason;
intptr_t res = 0;
if (write(1, "executing program\n", sizeof("executing program\n") - 1)) {}
// sendmsg$NFT_BATCH arguments: [
// fd: sock_nl_netfilter (resource)
// msg: ptr[in, msghdr_netlink[nft_batch_msg]] {
// msghdr_netlink[nft_batch_msg] {
// addr: nil
// addrlen: len = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// vec: ptr[in, iovec[in, nft_batch_msg]] {
// iovec[in, nft_batch_msg] {
// addr: ptr[inout, array[ANYUNION]] {
// array[ANYUNION] {
// union ANYUNION {
// ANYBLOB: buffer: {14 00 00 00 10 00 01 00 00 00 00 00 00 00 00 00 00 00 00 0a} (length 0x14)
// }
// }
// }
// len: len = 0xd4 (8 bytes)
// }
// }
// vlen: const = 0x1 (8 bytes)
// ctrl: const = 0x0 (8 bytes)
// ctrllen: const = 0x0 (8 bytes)
// f: send_flags = 0x0 (4 bytes)
// pad = 0x0 (4 bytes)
// }
// }
// f: send_flags = 0x4001 (8 bytes)
// ]
*(uint64_t*)0x2000000000c0 = 0;
*(uint32_t*)0x2000000000c8 = 0;
*(uint64_t*)0x2000000000d0 = 0x200000000000;
*(uint64_t*)0x200000000000 = 0x200000000100;
memcpy((void*)0x200000000100, "\x14\x00\x00\x00\x10\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0a", 20);
*(uint64_t*)0x200000000008 = 0xd4;
*(uint64_t*)0x2000000000d8 = 1;
*(uint64_t*)0x2000000000e0 = 0;
*(uint64_t*)0x2000000000e8 = 0;
*(uint32_t*)0x2000000000f0 = 0;
syscall(__NR_sendmsg, /*fd=*/(intptr_t)-1, /*msg=*/0x2000000000c0ul, /*f=MSG_OOB|MSG_NOSIGNAL*/0x4001ul);
// openat$kvm arguments: [
// fd: const = 0xffffffffffffff9c (8 bytes)
// file: ptr[in, buffer] {
// buffer: {2f 64 65 76 2f 6b 76 6d 00} (length 0x9)
// }
// flags: open_flags = 0x0 (4 bytes)
// mode: const = 0x0 (2 bytes)
// ]
// returns fd_kvm
memcpy((void*)0x200000000000, "/dev/kvm\000", 9);
res = syscall(__NR_openat, /*fd=*/0xffffffffffffff9cul, /*file=*/0x200000000000ul, /*flags=*/0, /*mode=*/0);
if (res != -1)
r[0] = res;
// ioctl$KVM_CREATE_VM arguments: [
// fd: fd_kvm (resource)
// cmd: const = 0xae01 (4 bytes)
// type: intptr = 0x0 (8 bytes)
// ]
// returns fd_kvmvm
res = syscall(__NR_ioctl, /*fd=*/r[0], /*cmd=*/0xae01, /*type=*/0ul);
if (res != -1)
r[1] = res;
// ioctl$KVM_CREATE_VCPU arguments: [
// fd: fd_kvmvm (resource)
// cmd: const = 0xae41 (4 bytes)
// id: intptr = 0x0 (8 bytes)
// ]
// returns fd_kvmcpu
res = syscall(__NR_ioctl, /*fd=*/r[1], /*cmd=*/0xae41, /*id=*/0ul);
if (res != -1)
r[2] = res;
// ioctl arguments: [
// fd: fd (resource)
// cmd: int32 = 0x4048aecb (4 bytes)
// arg: ptr[in, buffer] {
// buffer: {07 00 00 00 00 00 00 00 01 00 00 00 ff ff ff ff} (length 0x10)
// }
// ]
memcpy((void*)0x200000000100, "\x07\x00\x00\x00\x00\x00\x00\x00\x01\x00\x00\x00\xff\xff\xff\xff", 16);
syscall(__NR_ioctl, /*fd=*/r[2], /*cmd=*/0x4048aecb, /*arg=*/0x200000000100ul);
return 0;
}
Reply: **Analysis of the Bug**
The bug is a "sleeping function called from invalid context" warning that specifically affects `PREEMPT_RT` kernels.
**Root Cause:**
The issue occurs in the KVM Xen emulation code. The function `kvm_xen_set_evtchn_fast()` attempts to acquire `gpc->lock`, which is defined as an `rwlock_t`. On standard kernels, `rwlock_t` is a spinlock and disables interrupts, making it safe to use in atomic contexts. However, on `PREEMPT_RT` kernels, `rwlock_t` is mapped to a sleeping lock (`rt_rw_lock`).
The bug is triggered because `kvm_xen_set_evtchn_fast()` is called by `xen_timer_callback()`, which is an `hrtimer` callback that executes in hardirq (atomic) context. Attempting to acquire a sleeping lock while in hardirq context is invalid and triggers the kernel warning.
**Security Impact:**
- **Denial of Service (DoS):** If `gpc->lock` is contended when the timer interrupt fires, the `PREEMPT_RT` locking primitives will call `schedule()` to put the task to sleep. Calling `schedule()` from hardirq context is highly destructive:
1. **Deadlocks:** If the timer interrupt preempted a task that was holding a `raw_spinlock_t`, scheduling away leaves that lock held. Any other task attempting to acquire the same `raw_spinlock_t` will spin indefinitely with preemption disabled, resulting in a hard CPU deadlock.
2. **Interrupt Masking:** Scheduling away leaves the CPU's APIC In-Service Register (ISR) bit set for the timer interrupt. This permanently masks all interrupts of the same or lower priority on that CPU until the original task is scheduled back and finishes the interrupt handler, leading to severe system stalls or hardware watchdog panics.
3. **Kernel Panic:** If the kernel is booted with `panic_on_warn=1` (common in syzkaller and some production environments), the warning itself will immediately halt the system.
- **Exploitability:** The bug does not cause memory corruption (like use-after-free or out-of-bounds accesses) or leak kernel memory to userspace. The state of the interrupted task is safely preserved on the stack. Therefore, it is not exploitable for privilege escalation or information disclosure.
**Trigger Vectors:**
- **Unprivileged Processes:** An unprivileged user on the host with access to `/dev/kvm` (which is commonly granted via the `kvm` group or local udev ACLs) can trigger this by creating a VM, setting up the Xen timer via the `KVM_XEN_VCPU_SET_ATTR` ioctl, and waiting for the timer to expire.
- **VM Guest Trigger:** A malicious guest running under a VMM that has enabled KVM Xen emulation (e.g., QEMU) can trigger this bug on the host by programming the Xen timer via guest hypercalls (e.g., `set_timer_op`). When the timer expires, the host kernel executes the hrtimer callback and hits the bug.
|
| 6/2 |
2026/08/09 00:11 |
llm |
expert |
0m
Model:
Tokens:
input: 14260
output: 25
thoughts: 403
Thoughts: **Analyzing a KVM/Xen Bug Report**
Okay, so I've got this bug report – a pretty nasty one. The message is clear: "sleeping function called from invalid context at kernel/locking/spinlock_rt.c:248." Immediately, my expert instincts kick in; this sounds like a potential PREEMPT_RT conflict. The report says `kvm_xen_set_evtchn_fast` is the culprit, calling `srcu_read_lock`, which, alarmingly, is a sleeping lock, but it's happening in an atomic context – specifically a hardirq context. That's a big no-no.
The stack trace is right there, showing the path: `rt_read_lock` (which I immediately recognize as part of the RT spinlock implementation), then `kvm_xen_set_evtchn_fast`, which, in turn, is called by `xen_timer_callback` and eventually, within the hrtimer interrupt path. This really hones in on the potential issue within the timer interrupt handling.
Wait a second. `srcu_read_lock` takes a lock? I'm double-checking the code flow.
I have to look at this line in the stack trace, now, in more detail: `rt_read_lock+0xcc/0x4b0 kernel/locking/spinlock_rt.c:249`. That's where the problem really seems to lie.
Alright, let's open `arch/x86/kvm/xen.c:1829` and dig into `kvm_xen_set_evtchn_fast`. This is the crux of the issue. I need to understand what this function is doing and how it's interacting with the SRCU locks in a hardirq context. This is going to require careful code inspection.
|
| 7/2 |
2026/08/09 00:11 |
tool |
grepper |
0m
Args: map[Expression:kvm_xen_set_evtchn_fast]
Results: map[Output:arch/x86/kvm/irq.c=241=int kvm_arch_set_irq_inatomic(struct kvm_kernel_irq_routing_entry *e,
--
arch/x86/kvm/irq.c-269-
arch/x86/kvm/irq.c:270: return kvm_xen_set_evtchn_fast(&e->xen_evtchn, kvm);
arch/x86/kvm/irq.c-271-#endif
--
arch/x86/kvm/xen.c=126=static enum hrtimer_restart xen_timer_callback(struct hrtimer *timer)
--
arch/x86/kvm/xen.c-140-
arch/x86/kvm/xen.c:141: rc = kvm_xen_set_evtchn_fast(&e, vcpu->kvm);
arch/x86/kvm/xen.c-142- if (rc != -EWOULDBLOCK) {
--
arch/x86/kvm/xen.c=1781=static void kvm_xen_check_poller(struct kvm_vcpu *vcpu, int port)
--
arch/x86/kvm/xen.c-1801- */
arch/x86/kvm/xen.c:1802:int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *xe, struct kvm *kvm)
arch/x86/kvm/xen.c-1803-{
--
arch/x86/kvm/xen.c=1908=static int kvm_xen_set_evtchn(struct kvm_xen_evtchn *xe, struct kvm *kvm)
--
arch/x86/kvm/xen.c-1912-
arch/x86/kvm/xen.c:1913: rc = kvm_xen_set_evtchn_fast(xe, kvm);
arch/x86/kvm/xen.c-1914- if (rc != -EWOULDBLOCK)
--
arch/x86/kvm/xen.c-1946-
arch/x86/kvm/xen.c:1947: rc = kvm_xen_set_evtchn_fast(xe, kvm);
arch/x86/kvm/xen.c-1948- if (rc != -EWOULDBLOCK)
--
arch/x86/kvm/xen.h=33=void kvm_xen_destroy_vcpu(struct kvm_vcpu *vcpu);
arch/x86/kvm/xen.h:34:int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *xe,
arch/x86/kvm/xen.h-35- struct kvm *kvm);
]
|
| 8/2 |
2026/08/09 00:11 |
llm |
expert |
0m
Model:
Tokens:
input: 15842
output: 41
thoughts: 26
Thoughts: Here's my take on the thought process, as requested:
**Diving into the KVM-Xen Interaction**
Okay, so I'm digging into this KVM-Xen integration, and I have a hunch about something. I need to pinpoint where a specific interaction is happening, so I'm going to start by taking a look at `arch/x86/kvm/xen.c`. The architecture of how they interact is usually documented or implemented in this location. It feels like the specific area I'm after will likely be in or around line 1829. Time to crack open the source and see what's going on there. I'm hoping that particular code block will shed some light on the issue.
|
| 9/2 |
2026/08/09 00:12 |
tool |
read-file |
0m
Args: map[File:arch/x86/kvm/xen.c FirstLine:1802 LineCount:50]
Results: map[Contents:1802: int kvm_xen_set_evtchn_fast(struct kvm_xen_evtchn *xe, struct kvm *kvm)
1803: {
1804: struct gfn_to_pfn_cache *gpc = &kvm->arch.xen.shinfo_cache;
1805: struct kvm_vcpu *vcpu;
1806: unsigned long *pending_bits, *mask_bits;
1807: unsigned long flags;
1808: int port_word_bit;
1809: bool kick_vcpu = false;
1810: int vcpu_idx, idx, rc;
1811:
1812: vcpu_idx = READ_ONCE(xe->vcpu_idx);
1813: if (vcpu_idx >= 0)
1814: vcpu = kvm_get_vcpu(kvm, vcpu_idx);
1815: else {
1816: vcpu = kvm_get_vcpu_by_id(kvm, xe->vcpu_id);
1817: if (!vcpu)
1818: return -EINVAL;
1819: WRITE_ONCE(xe->vcpu_idx, vcpu->vcpu_idx);
1820: }
1821:
1822: if (xe->port >= max_evtchn_port(kvm))
1823: return -EINVAL;
1824:
1825: rc = -EWOULDBLOCK;
1826:
1827: idx = srcu_read_lock(&kvm->srcu);
1828:
1829: read_lock_irqsave(&gpc->lock, flags);
1830: if (!kvm_gpc_check(gpc, PAGE_SIZE))
1831: goto out_rcu;
1832:
1833: if (IS_ENABLED(CONFIG_64BIT) && kvm->arch.xen.long_mode) {
1834: struct shared_info *shinfo = gpc->khva;
1835: pending_bits = (unsigned long *)&shinfo->evtchn_pending;
1836: mask_bits = (unsigned long *)&shinfo->evtchn_mask;
1837: port_word_bit = xe->port / 64;
1838: } else {
1839: struct compat_shared_info *shinfo = gpc->khva;
1840: pending_bits = (unsigned long *)&shinfo->evtchn_pending;
1841: mask_bits = (unsigned long *)&shinfo->evtchn_mask;
1842: port_word_bit = xe->port / 32;
1843: }
1844:
1845: /*
1846: * If this port wasn't already set, and if it isn't masked, then
1847: * we try to set the corresponding bit in the in-kernel shadow of
1848: * evtchn_pending_sel for the target vCPU. And if *that* wasn't
1849: * already set, then we kick the vCPU in question to write to the
1850: * *real* evtchn_pending_sel in its own guest vcpu_info struct.
1851: */
]
|
| 10/2 |
2026/08/09 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 16971
output: 27
thoughts: 177
Thoughts: **Debugging a Potential Hardirq Context Issue**
Okay, so I'm digging into this bug and the core of the problem seems to be the call to `read_lock_irqsave(&gpc->lock, flags)`. Immediately, I see a red flag. On PREEMPT_RT systems, `read_lock_irqsave` resolves to `rt_read_lock`, which is a sleeping lock, right? And here we are, potentially inside hardirq context! The function `xen_timer_callback` is called from `__run_hrtimer`, so that puts me right in hardirq context, doesn't it?
Hold on... I'm pretty sure `read_lock_irqsave` is *designed* to be safe in a hardirq. That's the point, isn't it? That's what `_irqsave` is all about. Ah, but then I remember the PREEMPT_RT exception. On those systems, `rwlock_t` maps to `rt_rw_lock`, which is indeed a sleeping lock. And now I remember, `gpc->lock` *is* an `rwlock_t`. So, this is a real problem. Sleeping in hardirq is a recipe for disaster. I need to understand what this `gpc->lock` is all about, and why it's being used here. Now, I need to check the definition of `struct gfn_to_pfn_cache`.
|
| 11/2 |
2026/08/09 00:12 |
tool |
codesearch-struct-layout |
0m
Args: map[Name:gfn_to_pfn_cache]
Results: map[Fields:[map[Name:generation OffsetBits:0 SizeBits:64] map[Name:gpa OffsetBits:64 SizeBits:64] map[Name:uhva OffsetBits:128 SizeBits:64] map[Name:memslot OffsetBits:192 SizeBits:64] map[Name:kvm OffsetBits:256 SizeBits:64] map[Name:list OffsetBits:320 SizeBits:128] map[Name:lock OffsetBits:448 SizeBits:1152] map[Name:refresh_lock OffsetBits:1600 SizeBits:1024] map[Name:khva OffsetBits:2624 SizeBits:64] map[Name:pfn OffsetBits:2688 SizeBits:64] map[Name:active OffsetBits:2752 SizeBits:8] map[Name:valid OffsetBits:2760 SizeBits:8]]]
|
| 12/2 |
2026/08/09 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 17751
output: 28
thoughts: 763
Thoughts: **Analyzing a PREEMPT_RT Locking Issue in KVM-Xen Integration**
Okay, so let's break this down. First, the core problem centers around `gpc->lock`, which is an `rwlock_t`. Under PREEMPT_RT, an `rwlock_t` is a *sleeping* lock, right? And the reports show that `read_lock_irqsave` is being called on this `rwlock_t` from within a hardirq context. That's immediately a red flag. That's invalid because a sleeping lock can't be taken in hardirq context.
The bug report confirms this: "BUG: sleeping function called from invalid context" in `spinlock_rt.c`, pointing directly to `rt_read_lock`, which translates to `read_lock_irqsave(&gpc->lock, flags)`. A second report highlights an "Invalid wait context" within `kvm_xen_set_evtchn_fast`, so that reinforces that the lock is being taken, in a context where it's not allowed.
So, the fundamental issue is this: a sleeping `rwlock_t` (`gpc->lock`) is being accessed from a hardirq context. I have to think, is this a KVM bug? Aha! The stack traces suggest it originates within KVM’s Xen integration. It looks like `xen_timer_callback` is the culprit, which then calls code that takes `gpc->lock`. The problem is that `xen_timer_callback` is an hrtimer callback, and those *run* in hardirq context.
Okay, is this exploitable? Can an unprivileged user trigger this? Let's look at the reproducer. It uses `/dev/kvm` to create a VM and VCPU and then calls `KVM_XEN_HVM_SET_ATTR` or something similar. The reproducer sequence:
```c
res = syscall(__NR_openat, /*fd=*/0xffffffffffffff9cul, /*file=*/0x200000000000ul, /*flags=*/0, /*mode=*/0);
if (res != -1)
r[0] = res;
res = syscall(__NR_ioctl, /*fd=*/r[0], /*cmd=*/0xae01, /*type=*/0ul); // KVM_CREATE_VM
if (res != -1)
r[1] = res;
res = syscall(__NR_ioctl, /*fd=*/r[1], /*cmd=*/0xae41, /*id=*/0ul); // KVM_CREATE_VCPU
if (res != -1)
r[2] = res;
syscall(__NR_ioctl, /*fd=*/r[2], /*cmd=*/0x4048aecb, /*arg=*/0x200000000100ul); // KVM_XEN_VCPU_SET_ATTR
```
This looks like a standard KVM setup, followed by a call to `KVM_XEN_VCPU_SET_ATTR`! Right, `0x4048aecb` is `KVM_XEN_VCPU_SET_ATTR`. We need to dive deeper into that ioctl and see exactly how it's interacting with the Xen timer callbacks. My assumption is that the ioctl is involved in scheduling the hrtimer that triggers the issue.
|
| 13/2 |
2026/08/09 00:12 |
tool |
grepper |
0m
Args: map[Expression:#define KVM_XEN_VCPU_SET_ATTR]
Results: map[Output:include/uapi/linux/kvm.h=1442=struct kvm_enc_region {
--
include/uapi/linux/kvm.h-1484-#define KVM_XEN_VCPU_GET_ATTR _IOWR(KVMIO, 0xca, struct kvm_xen_vcpu_attr)
include/uapi/linux/kvm.h:1485:#define KVM_XEN_VCPU_SET_ATTR _IOW(KVMIO, 0xcb, struct kvm_xen_vcpu_attr)
include/uapi/linux/kvm.h-1486-
--
tools/include/uapi/linux/kvm.h=1432=struct kvm_enc_region {
--
tools/include/uapi/linux/kvm.h-1474-#define KVM_XEN_VCPU_GET_ATTR _IOWR(KVMIO, 0xca, struct kvm_xen_vcpu_attr)
tools/include/uapi/linux/kvm.h:1475:#define KVM_XEN_VCPU_SET_ATTR _IOW(KVMIO, 0xcb, struct kvm_xen_vcpu_attr)
tools/include/uapi/linux/kvm.h-1476-
]
|
| 14/2 |
2026/08/09 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 19660
output: 47
thoughts: 21
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into KVM_XEN_VCPU_SET_ATTR**
Okay, so I'm looking at `KVM_XEN_VCPU_SET_ATTR`. I know it's related to KVM, specifically Xen integration, but I need to quickly refresh my memory on what it's *actually* doing. I'm guessing it's about setting specific attributes for a virtual CPU, but the "Xen" part is the key. That implies it's either enabling or disabling a Xen-specific feature within a KVM virtual machine, configuring Xen hypercall interfaces, or potentially handling the mapping of Xen vCPU structures within the KVM environment. I'll need to dig into the relevant code, probably starting with the KVM kernel module and then seeing how it interacts with the Xen hypervisor interface, if at all. Hopefully, the naming is somewhat descriptive of its function. My immediate goal is to understand precisely which Xen-related vCPU parameters can be manipulated with this call, and what the potential implications (performance, security, compatibility) might be. Let's see...
|
| 15/2 |
2026/08/09 00:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:arch/x86/kvm/xen.c Name:kvm_xen_vcpu_set_attr]
Results: map[SourceCode: 907: int kvm_xen_vcpu_set_attr(struct kvm_vcpu *vcpu, struct kvm_xen_vcpu_attr *data)
908: {
909: int idx, r = -ENOENT;
910:
911: mutex_lock(&vcpu->kvm->arch.xen.xen_lock);
912: idx = srcu_read_lock(&vcpu->kvm->srcu);
913:
914: switch (data->type) {
915: case KVM_XEN_VCPU_ATTR_TYPE_VCPU_INFO:
916: case KVM_XEN_VCPU_ATTR_TYPE_VCPU_INFO_HVA:
917: /* No compat necessary here. */
918: BUILD_BUG_ON(sizeof(struct vcpu_info) !=
919: sizeof(struct compat_vcpu_info));
920: BUILD_BUG_ON(offsetof(struct vcpu_info, time) !=
921: offsetof(struct compat_vcpu_info, time));
922:
923: if (data->type == KVM_XEN_VCPU_ATTR_TYPE_VCPU_INFO) {
924: if (data->u.gpa == KVM_XEN_INVALID_GPA) {
925: kvm_gpc_deactivate(&vcpu->arch.xen.vcpu_info_cache);
926: r = 0;
927: break;
928: }
929:
930: r = kvm_gpc_activate(&vcpu->arch.xen.vcpu_info_cache,
931: data->u.gpa, sizeof(struct vcpu_info));
932: } else {
933: if (data->u.hva == 0) {
934: kvm_gpc_deactivate(&vcpu->arch.xen.vcpu_info_cache);
935: r = 0;
936: break;
937: }
938:
939: r = kvm_gpc_activate_hva(&vcpu->arch.xen.vcpu_info_cache,
940: data->u.hva, sizeof(struct vcpu_info));
941: }
942:
943: if (!r)
944: kvm_make_request(KVM_REQ_CLOCK_UPDATE, vcpu);
945:
946: break;
947:
948: case KVM_XEN_VCPU_ATTR_TYPE_VCPU_TIME_INFO:
949: if (data->u.gpa == KVM_XEN_INVALID_GPA) {
950: kvm_gpc_deactivate(&vcpu->arch.xen.vcpu_time_info_cache);
951: r = 0;
952: break;
953: }
954:
955: r = kvm_gpc_activate(&vcpu->arch.xen.vcpu_time_info_cache,
956: data->u.gpa,
957: sizeof(struct pvclock_vcpu_time_info));
958: if (!r)
959: kvm_make_request(KVM_REQ_CLOCK_UPDATE, vcpu);
960: break;
961:
962: case KVM_XEN_VCPU_ATTR_TYPE_RUNSTATE_ADDR: {
963: size_t sz, sz1, sz2;
964:
965: if (!sched_info_on()) {
966: r = -EOPNOTSUPP;
967: break;
968: }
969: if (data->u.gpa == KVM_XEN_INVALID_GPA) {
970: r = 0;
971: deactivate_out:
972: kvm_gpc_deactivate(&vcpu->arch.xen.runstate_cache);
973: kvm_gpc_deactivate(&vcpu->arch.xen.runstate2_cache);
974: break;
975: }
976:
977: /*
978: * If the guest switches to 64-bit mode after setting the runstate
979: * address, that's actually OK. kvm_xen_update_runstate_guest()
980: * will cope.
981: */
982: if (IS_ENABLED(CONFIG_64BIT) && vcpu->kvm->arch.xen.long_mode)
983: sz = sizeof(struct vcpu_runstate_info);
984: else
985: sz = sizeof(struct compat_vcpu_runstate_info);
986:
987: /* How much fits in the (first) page? */
988: sz1 = PAGE_SIZE - (data->u.gpa & ~PAGE_MASK);
989: r = kvm_gpc_activate(&vcpu->arch.xen.runstate_cache,
990: data->u.gpa, sz1);
991: if (r)
992: goto deactivate_out;
993:
994: /* Either map the second page, or deactivate the second GPC */
995: if (sz1 >= sz) {
996: kvm_gpc_deactivate(&vcpu->arch.xen.runstate2_cache);
997: } else {
998: sz2 = sz - sz1;
999: BUG_ON((data->u.gpa + sz1) & ~PAGE_MASK);
1000: r = kvm_gpc_activate(&vcpu->arch.xen.runstate2_cache,
1001: data->u.gpa + sz1, sz2);
1002: if (r)
1003: goto deactivate_out;
1004: }
1005:
1006: kvm_xen_update_runstate_guest(vcpu, false);
1007: break;
1008: }
1009: case KVM_XEN_VCPU_ATTR_TYPE_RUNSTATE_CURRENT:
1010: if (!sched_info_on()) {
1011: r = -EOPNOTSUPP;
1012: break;
1013: }
1014: if (data->u.runstate.state > RUNSTATE_offline) {
1015: r = -EINVAL;
1016: break;
1017: }
1018:
1019: kvm_xen_update_runstate(vcpu, data->u.runstate.state);
1020: r = 0;
1021: break;
1022:
1023: case KVM_XEN_VCPU_ATTR_TYPE_RUNSTATE_DATA:
1024: if (!sched_info_on()) {
1025: r = -EOPNOTSUPP;
1026: break;
1027: }
1028: if (data->u.runstate.state > RUNSTATE_offline) {
1029: r = -EINVAL;
1030: break;
1031: }
1032: if (data->u.runstate.state_entry_time !=
1033: (data->u.runstate.time_running +
1034: data->u.runstate.time_runnable +
1035: data->u.runstate.time_blocked +
1036: data->u.runstate.time_offline)) {
1037: r = -EINVAL;
1038: break;
1039: }
1040: if (get_kvmclock_ns(vcpu->kvm) <
1041: data->u.runstate.state_entry_time) {
1042: r = -EINVAL;
1043: break;
1044: }
1045:
1046: vcpu->arch.xen.current_runstate = data->u.runstate.state;
1047: vcpu->arch.xen.runstate_entry_time =
1048: data->u.runstate.state_entry_time;
1049: vcpu->arch.xen.runstate_times[RUNSTATE_running] =
1050: data->u.runstate.time_running;
1051: vcpu->arch.xen.runstate_times[RUNSTATE_runnable] =
1052: data->u.runstate.time_runnable;
1053: vcpu->arch.xen.runstate_times[RUNSTATE_blocked] =
1054: data->u.runstate.time_blocked;
1055: vcpu->arch.xen.runstate_times[RUNSTATE_offline] =
1056: data->u.runstate.time_offline;
1057: vcpu->arch.xen.last_steal = current->sched_info.run_delay;
1058: r = 0;
1059: break;
1060:
1061: case KVM_XEN_VCPU_ATTR_TYPE_RUNSTATE_ADJUST:
1062: if (!sched_info_on()) {
1063: r = -EOPNOTSUPP;
1064: break;
1065: }
1066: if (data->u.runstate.state > RUNSTATE_offline &&
1067: data->u.runstate.state != (u64)-1) {
1068: r = -EINVAL;
1069: break;
1070: }
1071: /* The adjustment must add up */
1072: if (data->u.runstate.state_entry_time !=
1073: (data->u.runstate.time_running +
1074: data->u.runstate.time_runnable +
1075: data->u.runstate.time_blocked +
1076: data->u.runstate.time_offline)) {
1077: r = -EINVAL;
1078: break;
1079: }
1080:
1081: if (get_kvmclock_ns(vcpu->kvm) <
1082: (vcpu->arch.xen.runstate_entry_time +
1083: data->u.runstate.state_entry_time)) {
1084: r = -EINVAL;
1085: break;
1086: }
1087:
1088: vcpu->arch.xen.runstate_entry_time +=
1089: data->u.runstate.state_entry_time;
1090: vcpu->arch.xen.runstate_times[RUNSTATE_running] +=
1091: data->u.runstate.time_running;
1092: vcpu->arch.xen.runstate_times[RUNSTATE_runnable] +=
1093: data->u.runstate.time_runnable;
1094: vcpu->arch.xen.runstate_times[RUNSTATE_blocked] +=
1095: data->u.runstate.time_blocked;
1096: vcpu->arch.xen.runstate_times[RUNSTATE_offline] +=
1097: data->u.runstate.time_offline;
1098:
1099: if (data->u.runstate.state <= RUNSTATE_offline)
1100: kvm_xen_update_runstate(vcpu, data->u.runstate.state);
1101: else if (vcpu->arch.xen.runstate_cache.active)
1102: kvm_xen_update_runstate_guest(vcpu, false);
1103: r = 0;
1104: break;
1105:
1106: case KVM_XEN_VCPU_ATTR_TYPE_VCPU_ID:
1107: BUILD_BUG_ON(XEN_VCPU_ID_INVALID < KVM_MAX_VCPUS);
1108:
1109: if (data->u.vcpu_id >= KVM_MAX_VCPUS)
1110: r = -EINVAL;
1111: else {
1112: vcpu->arch.xen.vcpu_id = data->u.vcpu_id;
1113: r = 0;
1114: }
1115: break;
1116:
1117: case KVM_XEN_VCPU_ATTR_TYPE_TIMER:
1118: if (data->u.timer.port &&
1119: data->u.timer.priority != KVM_IRQ_ROUTING_XEN_EVTCHN_PRIO_2LEVEL) {
1120: r = -EINVAL;
1121: break;
1122: }
1123:
1124: /* Stop the timer (if it's running) before changing the vector */
1125: kvm_xen_stop_timer(vcpu);
1126: vcpu->arch.xen.timer_virq = data->u.timer.port;
1127:
1128: /* Start the timer if the new value has a valid vector+expiry. */
1129: if (data->u.timer.port && data->u.timer.expires_ns)
1130: kvm_xen_start_timer(vcpu, data->u.timer.expires_ns, false);
1131:
1132: r = 0;
1133: break;
1134:
1135: case KVM_XEN_VCPU_ATTR_TYPE_UPCALL_VECTOR:
1136: if (data->u.vector && data->u.vector < 0x10)
1137: r = -EINVAL;
1138: else {
1139: vcpu->arch.xen.upcall_vector = data->u.vector;
1140: r = 0;
1141: }
1142: break;
1143:
1144: default:
1145: break;
1146: }
1147:
1148: srcu_read_unlock(&vcpu->kvm->srcu, idx);
1149: mutex_unlock(&vcpu->kvm->arch.xen.xen_lock);
1150: return r;
1151: }
SourceFile:arch/x86/kvm/xen.c]
|
| 16/2 |
2026/08/09 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 24061
output: 41
thoughts: 510
Thoughts: **Analyzing the KVM Xen Timer Configuration**
Okay, let's break this down. The reproducer's intention here is pretty clear: it's leveraging `KVM_XEN_VCPU_ATTR_TYPE_TIMER` (type 7) to manipulate a virtual CPU's timer behavior within the Xen environment. My initial observation is the direct `memcpy` into the user-space buffer. Specifically, it's setting the `KVM_XEN_VCPU_ATTR_TYPE_TIMER` attribute's configuration.
Looking closer, the `ioctl` call sets the attributes of this timer. The data structure, pointed to by the `arg` pointer, is initialized with specific values. I'm focusing on the implications of the timer configuration now.
The kernel-side code confirms this, the switch statement handles `KVM_XEN_VCPU_ATTR_TYPE_TIMER`. The important bits here are the checks on `port` and `priority`. Specifically, `priority` *must* be `KVM_IRQ_ROUTING_XEN_EVTCHN_PRIO_2LEVEL`. Otherwise, it returns an error. The crucial part, however, is the handling of the timer. First, it stops any existing timer (`kvm_xen_stop_timer`). Then, it sets the `timer_virq` to the new `port` value. The next step is critical – if `port` is valid *and* `expires_ns` is non-zero, it starts the timer using `kvm_xen_start_timer`.
The reproducer is deliberately setting `port = 1` and `expires_ns = 0xffffffffffffffff`. This essentially starts a timer with a very long expiry time. What I'm really interested in is the implications of these settings. When the timer *does* expire (or, in practice, because of the extremely long expiry, when the system decides the timer has "expired" even if the nanosecond value isn't technically reached), the callback function `xen_timer_callback` is executed. The vulnerability likely resides within the execution flow of the `xen_timer_callback`, as the goal here is to get there via an unexpected timer configuration.
|
| 17/2 |
2026/08/09 00:12 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:arch/x86/kvm/xen.c Name:xen_timer_callback]
Results: map[SourceCode: 126: static enum hrtimer_restart xen_timer_callback(struct hrtimer *timer)
127: {
128: struct kvm_vcpu *vcpu = container_of(timer, struct kvm_vcpu,
129: arch.xen.timer);
130: struct kvm_xen_evtchn e;
131: int rc;
132:
133: if (atomic_read(&vcpu->arch.xen.timer_pending))
134: return HRTIMER_NORESTART;
135:
136: e.vcpu_id = vcpu->vcpu_id;
137: e.vcpu_idx = vcpu->vcpu_idx;
138: e.port = vcpu->arch.xen.timer_virq;
139: e.priority = KVM_IRQ_ROUTING_XEN_EVTCHN_PRIO_2LEVEL;
140:
141: rc = kvm_xen_set_evtchn_fast(&e, vcpu->kvm);
142: if (rc != -EWOULDBLOCK) {
143: vcpu->arch.xen.timer_expires = 0;
144: return HRTIMER_NORESTART;
145: }
146:
147: atomic_inc(&vcpu->arch.xen.timer_pending);
148: kvm_make_request(KVM_REQ_UNBLOCK, vcpu);
149: kvm_vcpu_kick(vcpu);
150:
151: return HRTIMER_NORESTART;
152: }
SourceFile:arch/x86/kvm/xen.c]
|
| 18/2 |
2026/08/09 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 25582
output: 19
thoughts: 2091
Thoughts: **Analysis of the `kvm_xen_set_evtchn_fast` Bug**
Okay, so here's what I'm thinking about this `kvm_xen_set_evtchn_fast` bug. It's triggered by a `xen_timer_callback`, which runs in a hardirq context. The problem is that within this context, `kvm_xen_set_evtchn_fast` attempts to acquire a lock, specifically `gpc->lock`, using `read_lock_irqsave`. The kicker is, on PREEMPT_RT, this `gpc->lock` is a sleeping read/write lock (`rwlock_t`). Calling a sleeping function like a read/write lock in hardirq is a big no-no because it can cause the kernel to schedule in an atomic context.
Initially, I also noted the mention of `srcu_read_lock(&kvm->srcu)` in the bug report and I needed to verify if this also causes an issue in a hardirq context, and the bug report seems to confirm it. The provided backtrace shows that the error occurs within `rt_read_lock`, which is equivalent to `read_lock_irqsave` in this context. The lock is held within `srcu_read_lock`.
I'm seeing two distinct bug reports here; the first shows the `srcu_read_lock` being held and a sleeping function called from an invalid context, the second is lockdep complaining about a sleeping lock (`gpc->lock`) being taken in hardirq context. So it's confirmed: the core issue is that KVM-Xen takes a sleeping lock, `gpc->lock`, in hardirq context when it shouldn't.
Now, the crucial question is exploitability. This is a "sleeping function called from invalid context" bug. On PREEMPT_RT, calling a sleeping function like a read/write lock from an hardirq can potentially lead to a system that deadlocks, is in a corrupted state, or kernel panics. From what I know about these types of bugs, they're typically classified as Denial of Service (DoS) bugs, rather than privilege escalation vulnerabilities. They'll often cause the kernel to BUG or panic when it attempts to schedule.
The next thing I'm considering is whether an unprivileged user can trigger this. The reproducer uses `/dev/kvm`, which is a key part of the puzzle. Typically, a regular user, not `root`, would have to be in the `kvm` group. However, in many modern desktop distributions, local console users get access to `/dev/kvm` via udev ACLs.
Therefore, since the `/dev/kvm` device node is often accessible to non-root users (like those in the `kvm` group or with specific ACLs), it's fair to say this bug can be triggered by an unprivileged process. KVM bugs like this are usually considered accessible to regular users if they only need `/dev/kvm` access.
So, this looks like a KVM host DoS bug. This is triggered by a user-space process on the host, a VMM like QEMU, or even something malicious, like the syzkaller reproducer.
Now, let's explore if this is exploitable in practice. If the system schedules while atomic, it might lead to memory corruption if the scheduler corrupts a state, which may trigger a `BUG()` or `panic()`. In a production build, where `CONFIG_DEBUG_ATOMIC_SLEEP` is disabled, the system can call `rtlock_slowlock`, which will block, and then call `schedule()`. In the scenario where `schedule()` is called within hardirq context, `__schedule()` calls `__schedule_bug()`, which can lead to a `BUG()` or kernel panic.
|
| 19/2 |
2026/08/09 00:12 |
tool |
grepper |
0m
Args: map[Expression:void __schedule_bug]
Results: map[Output:kernel/sched/core.c=6027=static inline unsigned long get_preempt_disable_ip(struct task_struct *p)
--
kernel/sched/core.c-6038- */
kernel/sched/core.c:6039:static noinline void __schedule_bug(struct task_struct *prev)
kernel/sched/core.c-6040-{
]
|
| 20/2 |
2026/08/09 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 28627
output: 36
thoughts: 10
Thoughts: Okay, here's my interpretation of that thought process, tailored for an expert audience, and written in the first person:
**Initial Assessment: Navigating the Debugging Landscape**
Alright, the initial prompt is straightforward: let's dive into the function `__schedule_bug`. I see this as a preliminary instruction. We're clearly dealing with a debugging scenario, probably performance or a scheduling issue if the name is as it seems. My immediate goal is to understand the context and the potential scope of this "bug". The double underscore suggests this is a low-level, internal function, which narrows down the area of focus considerably. Time to start the deep dive. I'm expecting this to either be in the kernel or a heavily-used library.
|
| 21/2 |
2026/08/09 00:12 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:6039 LineCount:20]
Results: map[Contents:6039: static noinline void __schedule_bug(struct task_struct *prev)
6040: {
6041: /* Save this before calling printk(), since that will clobber it */
6042: unsigned long preempt_disable_ip = get_preempt_disable_ip(current);
6043:
6044: if (oops_in_progress)
6045: return;
6046:
6047: printk(KERN_ERR "BUG: scheduling while atomic: %s/%d/0x%08x\n",
6048: prev->comm, prev->pid, preempt_count());
6049:
6050: debug_show_held_locks(prev);
6051: print_modules();
6052: if (irqs_disabled())
6053: print_irqtrace_events(prev);
6054: if (IS_ENABLED(CONFIG_DEBUG_PREEMPT)) {
6055: pr_err("Preemption disabled at:");
6056: print_ip_sym(KERN_ERR, preempt_disable_ip);
6057: }
6058: check_panic_on_warn("scheduling while atomic");
]
|
| 22/2 |
2026/08/09 00:12 |
llm |
expert |
0m
Model:
Tokens:
input: 29167
output: 36
thoughts: 66
Thoughts: Okay, let's break this down.
**Debugging a Kernel Scheduling Issue**
Right, so I'm looking at this `__schedule_bug` function. It seems like the first thing it does is spit out a message and then call `check_panic_on_warn` with the specific message "scheduling while atomic". Okay, pretty standard so far, a sanity check.
Now, here's the interesting part. If `panic_on_warn` isn't set, it doesn't just halt – it moves on to call `dump_stack()`. That's smart. Gives you a stack trace to help diagnose the problem, even if we're not immediately panicking. It's essentially a controlled failure or warning. This indicates that it's designed to be used in non-fatal situations, where understanding what happened is more important than an immediate crash.
Okay, let me trace through what happens *after* `__schedule_bug` is called. Need to see the context surrounding this function call. I need the bigger picture to understand the implications of this. I'm going to follow the execution flow from that point.
|
| 23/2 |
2026/08/09 00:13 |
tool |
read-file |
0m
Args: map[File:kernel/sched/core.c FirstLine:6059 LineCount:20]
Results: map[Contents:6059:
6060: dump_stack();
6061: add_taint(TAINT_WARN, LOCKDEP_STILL_OK);
6062: }
6063:
6064: /*
6065: * Various schedule()-time debugging checks and statistics:
6066: */
6067: static inline void schedule_debug(struct task_struct *prev, bool preempt)
6068: {
6069: #ifdef CONFIG_SCHED_STACK_END_CHECK
6070: if (task_stack_end_corrupted(prev))
6071: panic("corrupted stack end detected inside scheduler\n");
6072:
6073: if (task_scs_end_corrupted(prev))
6074: panic("corrupted shadow stack detected inside scheduler\n");
6075: #endif
6076:
6077: #ifdef CONFIG_DEBUG_ATOMIC_SLEEP
6078: if (!preempt && READ_ONCE(prev->__state) && prev->non_block_count) {
]
|
| 24/2 |
2026/08/09 00:13 |
llm |
expert |
0m
Model:
Tokens:
input: 29817
output: 43
thoughts: 825
Thoughts: **Analyzing the Implications of Scheduling from Hardirq Context**
Okay, so `__schedule_bug` just throws a warning and dumps the stack. That's not the core problem. The problem is that `__schedule` *continues* after that. My mind immediately races ahead... If we're scheduling while in a hardirq context, what are the cascading effects?
The thought hits me: the hardirq context gets interrupted, and a new task is scheduled. My initial concern is with stack integrity. Would the new task inherit the hardirq's stack? No, thankfully, `__schedule` handles the stack switch. Still, a key issue remains: the hardirq is running on its own stack (or the task's stack if no hardirq stack), and we're switching away. This means that the interrupt controller (APIC, for example) still thinks the hardirq is pending or active.
This is where it gets critical. No other hardirqs of the same or lower priority can be delivered to this CPU until the original hardirq completes. If this new task blocks, or even just runs for a long time, that CPU is effectively deadlocked for those interrupts. That's a classic Denial of Service. And when the original task is scheduled back, it'll resume right in hardirq context!
My immediate question: privilege escalation? But no, generally scheduling from hardirq is a DoS, not privilege escalation, because it doesn't directly corrupt memory. It just messes with the CPU's interrupt and scheduler state.
Then I focus on `preempt_count`. I remember that when `__schedule` is called from hardirq, `preempt_count` has `HARDIRQ_OFFSET` set. Does the new task inherit this? No, it's per-task. Phew. But... if we switch to a new task, it's `preempt_count` is likely zero. The CPU thinks it's no longer in a hardirq, but the APIC still thinks it is! That's it, that's the hang. No further interrupts of the same or lower priority will be delivered. System hang or misbehavior, guaranteed. The original task gets scheduled back, restores the original `preempt_count`, finishes the hardirq, and sends EOI.
The critical question: is there any chance of memory corruption? If the hardirq was modifying some per-CPU variable, and we schedule away, then another task could run on the same CPU, run the same code path, corrupting that data. Hardirqs are not supposed to be preempted. If one is, any per-CPU data it was modifying is liable to be corrupted if another task or softirq runs on the same CPU and accesses the same per-CPU data.
But here, specifically, the hardirq is `hrtimer_interrupt`. It iterates over expired timers. Can another `hrtimer_interrupt` happen? No, because the APIC ISR bit is set, so the timer interrupt is masked on this CPU. So, `hrtimer_interrupt` won't be re-entered on this CPU.
What about other per-CPU data? The scheduled task *could* access per-CPU data the hardirq was modifying. But after reviewing the code, I see that the `hrtimer_interrupt` *doesn't* leave per-CPU data in an inconsistent state before calling the timer callback. The timer callback itself (`xen_timer_callback`) is the one doing the scheduling. And *that* callback doesn't modify any per-CPU data before scheduling. So the likelihood of memory corruption is low.
I should double-check that `gpc->lock` is an `rwlock_t`, just to be certain.
|
| 25/2 |
2026/08/09 00:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:include/linux/kvm_host.h Name:gfn_to_pfn_cache]
Results: map[SourceCode: 84: struct gfn_to_pfn_cache {
85: u64 generation;
86: gpa_t gpa;
87: unsigned long uhva;
88: struct kvm_memory_slot *memslot;
89: struct kvm *kvm;
90: struct list_head list;
91: rwlock_t lock;
92: struct mutex refresh_lock;
93: void *khva;
94: kvm_pfn_t pfn;
95: bool active;
96: bool valid;
97: };
SourceFile:include/linux/kvm_types.h]
|
| 26/2 |
2026/08/09 00:13 |
llm |
expert |
0m
Model:
Tokens:
input: 31693
output: 38
thoughts: 219
Thoughts: **Analyzing a Potential Kernel Bug**
Okay, so I'm looking at this code and I see `rwlock_t lock;`. Right, a read-write lock. On a PREEMPT_RT kernel, that's a sleeping lock, I know that. Then I see `kvm_xen_set_evtchn_fast` and it's calling `read_lock_irqsave(&gpc->lock, flags);`. Hmm, interesting. I need to figure out the context of this call. Is it possible this is triggered on a regular kernel? Let me see… No, on a non-RT kernel, `rwlock_t` is a spinlock, so `read_lock_irqsave` disables interrupts and doesn't sleep. So, this bug is specific to PREEMPT_RT kernels.
Let me see what the bug report says… aha! The bug report says `syzkaller #0 PREEMPT_{RT,(full)}`, so yes, the report is related to RT kernels.
So, is this exploitable? On PREEMPT_RT, taking a sleeping lock in hardirq context is a definite bug. Wait, does `read_lock_irqsave` actually sleep on PREEMPT_RT? I need to verify that. Let me look at the implementation of `rt_read_lock` and see exactly what's happening.
|
| 27/2 |
2026/08/09 00:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/locking/spinlock_rt.c Name:rt_read_lock]
Results: map[SourceCode: 246: void __sched rt_read_lock(rwlock_t *rwlock) __acquires(RCU)
247: {
248: rtlock_might_resched();
249: rwlock_acquire_read(&rwlock->dep_map, 0, 0, _RET_IP_);
250: rwbase_read_lock(&rwlock->rwbase, TASK_RTLOCK_WAIT);
251: rcu_read_lock();
252: migrate_disable();
253: }
SourceFile:kernel/locking/spinlock_rt.c]
|
| 28/2 |
2026/08/09 00:13 |
llm |
expert |
0m
Model:
Tokens:
input: 32422
output: 40
thoughts: 54
Thoughts: Here's my understanding of the situation, framed as my own thought process:
**Potential Context Issue in the Read Lock Function**
Okay, so `rtlock_might_resched()` is called. That immediately points me to the internal `__might_resched()` function. This is key, as I know that it checks whether or not we are currently executing in an atomic context. If we *are* in an atomic context, and it detects that `__might_resched` is about to sleep, it will trigger that "BUG: sleeping function called from invalid context" warning. This is a red flag. Atomic contexts, by definition, shouldn't block.
Following that, the code calls `rwbase_read_lock`. So, the concern here is that a potential call to `rwbase_read_lock`, through its internals, might lead to some type of scheduling activity from the `__might_resched` path in an invalid context. This is something I'll need to investigate further to determine how `rwbase_read_lock` interacts with potentially conflicting scheduling calls. I'm wondering if a more precise check is needed within the `rwbase_read_lock` call itself, or perhaps a different approach is necessary to ensure the atomic context isn't violated.
|
| 29/2 |
2026/08/09 00:13 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/locking/rwbase_rt.c Name:rwbase_read_lock]
Results: map[SourceCode: 139: static __always_inline int rwbase_read_lock(struct rwbase_rt *rwb,
140: unsigned int state)
141: {
142: lockdep_assert(!current->pi_blocked_on);
143:
144: if (rwbase_read_trylock(rwb))
145: return 0;
146:
147: return __rwbase_read_lock(rwb, state);
148: }
SourceFile:kernel/locking/rwbase_rt.c]
|
| 30/2 |
2026/08/09 00:13 |
llm |
expert |
0m
Model:
Tokens:
input: 32963
output: 40
thoughts: 103
Thoughts: **Analyzing the Read-Write Lock Contention Behavior**
Okay, so I'm digging into this read-write lock implementation. Let's see... the `rwbase_read_trylock` function. Right, if the lock is free, it just grabs it and returns 0. That makes sense, quick and efficient. Hold on a second... if it returns 0, it *doesn't* actually put the calling thread to sleep! It's only going to sleep if there's contention. Ah, that's the expected behavior.
But then I see this call to `rtlock_might_resched()` and it's unconditionally printing the warning if it's in an atomic context, regardless of contention. Now that's not quite what I want. Let's dig deeper. The `rtlock_might_resched` calls `__might_resched`. I need to figure out why this is called unconditionally. I want to check `rtlock_might_resched` to find the issue.
|
| 31/2 |
2026/08/09 00:13 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:kernel/locking/spinlock_rt.c Name:rtlock_might_resched]
Results: map[SourceCode: SourceFile:]
|
| 32/2 |
2026/08/09 00:13 |
llm |
expert |
0m
Model:
Tokens:
input: 33346
output: 21
thoughts: 14
Thoughts: Okay, here's my take on those thoughts, tailored for an expert audience:
**Analyzing Potential Scheduling Implications**
Alright, my internal process is starting with a classic, bread-and-butter approach: let's jump straight to grepping for the function `rtlock_might_resched`. That function name itself is a pretty big clue. It strongly suggests that we're dealing with a point in the kernel's real-time locking code where a reschedule *might* happen. The "might" is key here – it's not a guarantee, but it's a critical point to scrutinize.
I'm thinking this is likely a good starting point for investigating potential latencies or contention issues in our real-time system. I'm imagining scenarios in my head: What conditions trigger this function? What are the potential consequences if a reschedule *does* occur at this point? Does it impact lock holding times, interrupt latency, or, perhaps, thread prioritization? Knowing how and why `rtlock_might_resched` is called is going to give me my first glimpse into potential scheduling vulnerabilities or bottlenecks in this particular code path. I'm going to start digging into the callsites and its implementation; it'll give me a mental map of what could be impacted, and what to investigate next. Let's see what we can find!
|
| 33/2 |
2026/08/09 00:13 |
tool |
grepper |
0m
Args: map[Expression:rtlock_might_resched]
Results: map[Output:kernel/locking/spinlock_rt.c-34-
kernel/locking/spinlock_rt.c:35:#define rtlock_might_resched() \
kernel/locking/spinlock_rt.c-36- __might_resched(__FILE__, __LINE__, RTLOCK_RESCHED_OFFSETS)
--
kernel/locking/spinlock_rt.c=46=static __always_inline void __rt_spin_lock(spinlock_t *lock)
kernel/locking/spinlock_rt.c-47-{
kernel/locking/spinlock_rt.c:48: rtlock_might_resched();
kernel/locking/spinlock_rt.c-49- rtlock_lock(&lock->lock);
--
kernel/locking/spinlock_rt.c=246=void __sched rt_read_lock(rwlock_t *rwlock) __acquires(RCU)
kernel/locking/spinlock_rt.c-247-{
kernel/locking/spinlock_rt.c:248: rtlock_might_resched();
kernel/locking/spinlock_rt.c-249- rwlock_acquire_read(&rwlock->dep_map, 0, 0, _RET_IP_);
--
kernel/locking/spinlock_rt.c=256=void __sched rt_write_lock(rwlock_t *rwlock) __acquires(RCU)
kernel/locking/spinlock_rt.c-257-{
kernel/locking/spinlock_rt.c:258: rtlock_might_resched();
kernel/locking/spinlock_rt.c-259- rwlock_acquire(&rwlock->dep_map, 0, 0, _RET_IP_);
--
kernel/locking/spinlock_rt.c=267=void __sched rt_write_lock_nested(rwlock_t *rwlock, int subclass) __acquires(RCU)
kernel/locking/spinlock_rt.c-268-{
kernel/locking/spinlock_rt.c:269: rtlock_might_resched();
kernel/locking/spinlock_rt.c-270- rwlock_acquire(&rwlock->dep_map, subclass, 0, _RET_IP_);
]
|
| 34/2 |
2026/08/09 00:13 |
llm |
expert |
0m
Model:
Tokens:
input: 34210
output: 38
thoughts: 1240
Thoughts: **Analyzing a Potential Denial-of-Service Vulnerability in KVM Xen Event Channel Handling**
Okay, so here's what I'm seeing. `__might_resched` gives a warning in atomic context. At first glance, it doesn't seem like a problem if the lock isn't contended since it won't sleep. However, if the lock *is* contended, it goes to `__rwbase_read_lock`, which will call `schedule()`. If this happens from a hard interrupt context, `__schedule` will print a warning and *continue* (effectively scheduling away from the hardirq). This sounds like trouble.
The core issue is a potential DoS. By forcing contention on `gpc->lock`, an attacker can trigger the hard interrupt to sleep. This stalls the CPU for interrupts of the same or lower priority, and that *is* a DoS. The attacker can control contention, since `kvm_gpc_refresh` takes `gpc->lock` for writing.
No immediate memory corruption jumps out. The interrupt context is interrupted, then resumes where it left off. Let's trace it. It grabs `srcu_read_lock` and then blocks on `gpc->lock`, calling `schedule()`. SRCU allows sleeping, so that's fine. After the writer releases the lock, the hard interrupt context wakes up, continues in `kvm_xen_set_evtchn_fast`, releases the locks, and returns to `xen_timer_callback` which returns `HRTIMER_NORESTART`. The ISR eventually finishes and sends EOI.
Wait, is it a permanent DoS? It might seem that way, but if the writer releases the lock reasonably quickly, the interrupted task resumes. But the interrupted task is just the one that was blocked on the interrupt. The scheduler will put the "interrupted" task on the runqueue, so it just resumes where it was. So it's *not* a permanent DoS. The interrupted task is put on the runqueue and eventually returns to its normal execution. So not a permanent DoS.
However, if the interrupted task was holding a spinlock, this could be bad. Sleeping while holding a spinlock would mean another task could deadlock trying to acquire the same lock. Now, can the attacker control what gets interrupted? The timer fires at expiration. They can try to make a timer interrupt fire at a particular moment.
Now, wait...on PREEMPT_RT, most spinlocks are sleeping locks so they don't disable interrupts. Only `raw_spinlock_t` disables interrupts. So if a task is holding a `raw_spinlock_t`, interrupts *are* disabled. But *if* a task is holding a `raw_spinlock_t`, the timer interrupt CANNOT fire! Because `raw_spin_lock` disables hard interrupts on PREEMPT_RT!
So now let's see, what about just `raw_spin_lock`? Does it also disable interrupts?
|
| 35/2 |
2026/08/09 00:13 |
tool |
codesearch-definition-source |
0m
Error:
Args: map[ContextFile:include/linux/spinlock_rt.h Name:raw_spin_lock]
Results: map[SourceCode: SourceFile:]
|
| 36/2 |
2026/08/09 00:13 |
llm |
expert |
0m
Model:
Tokens:
input: 36151
output: 22
thoughts: 22
Thoughts: Here's my thought process, as requested:
**Grepping for Spinlock Implementation**
Okay, so I need to understand the low-level workings of these spinlocks. I think I'll start by diving into the header file where they're *defined*. Let's go straight to the source. Time to grep for that `raw_spin_lock` symbol. That'll be in the `include/linux/spinlock.h` file, I'm pretty sure. This should give me a good initial view of how the `raw_spin_lock` primitives are actually implemented at the kernel level. From there I can track down how they are used, and what context they are used within. Let's see what we can find!
|
| 37/2 |
2026/08/09 00:13 |
tool |
grepper |
0m
Args: map[Expression:#define raw_spin_lock\(]
Results: map[Output:include/linux/spinlock.h=202=static inline void do_raw_spin_unlock(raw_spinlock_t *lock) __releases(lock)
--
include/linux/spinlock.h-217-
include/linux/spinlock.h:218:#define raw_spin_lock(lock) _raw_spin_lock(lock)
include/linux/spinlock.h-219-
]
|
| 38/2 |
2026/08/09 00:13 |
llm |
expert |
0m
Model:
Tokens:
input: 36462
output: 25
thoughts: 22
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience:
**Diving into the Spinlock Implementation**
Alright, let's get down to brass tacks. I'm going to jump into the `kernel/locking/spinlock.c` file and specifically hone in on the `_raw_spin_lock` function. I need to refresh my understanding of the fundamental low-level spinlock mechanisms, as there's always a level of nuanced behavior in the interaction between the CPU's atomic instructions and the kernel's lock implementation that's easy to lose track of when working at this level. I want to see the exact assembly and memory ordering used, and how it handles potential contention. I'll be looking to see if there's any cleverness around preemption or interrupt disabling in the core critical section.
|
| 39/2 |
2026/08/09 00:14 |
tool |
grepper |
0m
Args: map[Expression:void __lockfunc _raw_spin_lock\(]
Results: map[Output:include/linux/spinlock_api_smp.h=18=int in_lock_functions(unsigned long addr);
--
include/linux/spinlock_api_smp.h-21-
include/linux/spinlock_api_smp.h:22:void __lockfunc _raw_spin_lock(raw_spinlock_t *lock) __acquires(lock);
include/linux/spinlock_api_smp.h-23-void __lockfunc _raw_spin_lock_nested(raw_spinlock_t *lock, int subclass)
--
kernel/locking/spinlock.c=152=EXPORT_SYMBOL(_raw_spin_trylock_bh);
--
kernel/locking/spinlock.c-155-#ifndef CONFIG_INLINE_SPIN_LOCK
kernel/locking/spinlock.c:156:noinline void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
kernel/locking/spinlock.c-157-{
]
|
| 40/2 |
2026/08/09 00:14 |
llm |
expert |
0m
Model:
Tokens:
input: 36929
output: 35
thoughts: 12
Thoughts: Okay, let's dive into this. Here's my take on those thoughts, as if they're my own:
**Analyzing the Raw Spinlock Function**
Alright, time to get to it. Let's see what this `_raw_spin_lock` function is all about. This likely forms the core of a critical section management, so my interest is piqued. I'm already anticipating what type of underlying hardware synchronization mechanisms are used. Is it test-and-set, compare-and-swap, or something more specialized? I'm hoping to see a good balance between simplicity and performance. Given its raw nature, I expect minimal overhead, maybe direct atomic instructions. Let's see if it's got proper memory ordering considerations in place, that's crucial for avoiding subtle bugs in a multi-core environment. I want to see how it handles contention – is there backoff, or will we see busy-waiting? I'm curious about the potential lock acquisition failure paths and any related error handling. I'm also looking for clear comments, of course, to understand the intent and any potential caveats. This should be interesting; I want to understand what's *really* going on.
|
| 41/2 |
2026/08/09 00:14 |
tool |
read-file |
0m
Args: map[File:kernel/locking/spinlock.c FirstLine:156 LineCount:10]
Results: map[Contents: 156: noinline void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
157: {
158: __raw_spin_lock(lock);
159: }
160: EXPORT_SYMBOL(_raw_spin_lock);
161: #endif
162:
163: #ifndef CONFIG_INLINE_SPIN_LOCK_IRQSAVE
164: noinline unsigned long __lockfunc _raw_spin_lock_irqsave(raw_spinlock_t *lock)
165: {
]
|
| 42/2 |
2026/08/09 00:14 |
llm |
expert |
0m
Model:
Tokens:
input: 37399
output: 20
thoughts: 605
Thoughts: **My Analysis of a Potential Hard Deadlock Vulnerability**
Okay, so here's what I'm thinking. This `__raw_spin_lock` call, it disables preemption, but not interrupts. That's the key. So a task could be in the middle of holding the lock and get interrupted by a hardirq. Now, if that hardirq for some reason tries to sleep – which, according to the bug report, is happening – the task that was holding the lock gets scheduled out.
The problem arises when another task comes along and tries to grab that same `raw_spinlock_t`. It's going to spin forever, right? Well, not exactly forever because we're on PREEMPT_RT, so it's a bit more nuanced. While the initial task *had* preemption disabled when it called the lock function, it was effectively enabled by the schedule as that task was scheduled out. So the other task *will* be able to spin while having preemption disabled. The initial task can *never* run again!
Ah, right, if a hardirq is happening, and it tries to call `schedule()`, the code bypasses the normal checks and prints a warning about "scheduling while atomic." Then it schedules something else. The second task might try to grab the lock and is going to spin with preemption disabled, so it will never give up the CPU. The original task, which has the lock, is stuck waiting to run! That's a hard deadlock, a permanent DoS – a nasty one.
So, deadlocks, that's what we're looking at here. Privilege escalation? No, I don't see it. This is a DoS, plain and simple.
Now, the bug report mentions this is specifically related to KVM Xen on PREEMPT_RT, and the error points to `kernel/locking/spinlock_rt.c:248`. Okay, so it's a known issue, which makes it less interesting from a novel exploit perspective, but still important to understand.
But, I'm getting ahead of myself, let's explore this further. Could this lead to memory corruption? Hmm, when `schedule()` is called from a hardirq, it saves registers to the stack and then switches stack pointers. It's normally designed for task context, but calling from hardirq is a different beast. x86_64 uses a separate hardirq stack (`struct irq_stack`).
Okay, here's where it gets interesting: on x86_64, hardirqs *do* run on their own stack, `struct irq_stack`. If `schedule()` is called *from* this hardirq stack, it switches away. Another task starts running on its own task stack, but what happens when *another* hardirq fires? The CPU is going to try and use that same hardirq stack again!
Wait a second… If the CPU is using that same stack again, it's going to start overwriting the saved state of that first, sleeping hardirq, right from the top. Oh boy! That's a massive problem. That's a major potential for memory corruption. Let me check how x86_64 handles hardirq stacks in more detail… I need to see what's actually happening on a low level here. I need to dive into the architecture and the interrupt handling to fully understand the implications.
|
| 43/2 |
2026/08/09 00:14 |
tool |
grepper |
0m
Args: map[Expression:irq_stack_ptr]
Results: map[Output:arch/arm/kernel/entry-armv.S-43- mov r1, sp
arch/arm/kernel/entry-armv.S:44: ldr_this_cpu r2, irq_stack_ptr, r2, r3
arch/arm/kernel/entry-armv.S-45- .if \from_user == 0
--
arch/arm/kernel/entry-v7m.S=36=__irq_entry:
--
arch/arm/kernel/entry-v7m.S-42- mov r0, sp
arch/arm/kernel/entry-v7m.S:43: ldr_this_cpu sp, irq_stack_ptr, r1, r2
arch/arm/kernel/entry-v7m.S-44-
--
arch/arm/kernel/irq.c=48=unsigned long irq_err_count;
--
arch/arm/kernel/irq.c-51-
arch/arm/kernel/irq.c:52:asmlinkage DEFINE_PER_CPU_READ_MOSTLY(u8 *, irq_stack_ptr);
arch/arm/kernel/irq.c-53-
arch/arm/kernel/irq.c=54=static void __init init_irq_stacks(void)
--
arch/arm/kernel/irq.c-69- break;
arch/arm/kernel/irq.c:70: per_cpu(irq_stack_ptr, cpu) = &stack[THREAD_SIZE];
arch/arm/kernel/irq.c-71- }
--
arch/arm/kernel/irq.c=80=void do_softirq_own_stack(void)
--
arch/arm/kernel/irq.c-82- call_with_stack(____do_softirq, NULL,
arch/arm/kernel/irq.c:83: __this_cpu_read(irq_stack_ptr));
arch/arm/kernel/irq.c-84-}
--
arch/arm/kernel/traps.c=890=void __init early_trap_init(void *vectors_base)
--
arch/arm/kernel/traps.c-901-
arch/arm/kernel/traps.c:902:DECLARE_PER_CPU(u8 *, irq_stack_ptr);
arch/arm/kernel/traps.c-903-
--
arch/arm/kernel/traps.c=921=asmlinkage void handle_bad_stack(struct pt_regs *regs)
--
arch/arm/kernel/traps.c-924-#ifdef CONFIG_IRQSTACKS
arch/arm/kernel/traps.c:925: unsigned long irq_stk = (unsigned long)raw_cpu_read(irq_stack_ptr);
arch/arm/kernel/traps.c-926-#endif
--
arch/arm64/include/asm/stacktrace.h=20=extern void dump_backtrace(struct pt_regs *regs, struct task_struct *tsk,
--
arch/arm64/include/asm/stacktrace.h-22-
arch/arm64/include/asm/stacktrace.h:23:DECLARE_PER_CPU(unsigned long *, irq_stack_ptr);
arch/arm64/include/asm/stacktrace.h-24-
arch/arm64/include/asm/stacktrace.h=25=static inline struct stack_info stackinfo_get_irq(void)
arch/arm64/include/asm/stacktrace.h-26-{
arch/arm64/include/asm/stacktrace.h:27: unsigned long low = (unsigned long)raw_cpu_read(irq_stack_ptr);
arch/arm64/include/asm/stacktrace.h-28- unsigned long high = low + IRQ_STACK_SIZE;
--
arch/arm64/kernel/entry.S=872=SYM_FUNC_START(call_on_irq_stack)
--
arch/arm64/kernel/entry.S-883-
arch/arm64/kernel/entry.S:884: ldr_this_cpu x16, irq_stack_ptr, x17
arch/arm64/kernel/entry.S-885-
--
arch/arm64/kernel/irq.c=32=DEFINE_PER_CPU(struct nmi_ctx, nmi_contexts);
arch/arm64/kernel/irq.c-33-
arch/arm64/kernel/irq.c:34:DEFINE_PER_CPU(unsigned long *, irq_stack_ptr);
arch/arm64/kernel/irq.c-35-
--
arch/arm64/kernel/irq.c=60=static int __init init_irq_stacks(void)
--
arch/arm64/kernel/irq.c-68- return -ENOMEM;
arch/arm64/kernel/irq.c:69: per_cpu(irq_stack_ptr, cpu) = p;
arch/arm64/kernel/irq.c-70- }
--
arch/arm64/kernel/traps.c=903=void __noreturn panic_bad_stack(struct pt_regs *regs, unsigned long esr, unsigned long far)
--
arch/arm64/kernel/traps.c-905- unsigned long tsk_stk = (unsigned long)current->stack;
arch/arm64/kernel/traps.c:906: unsigned long irq_stk = (unsigned long)this_cpu_read(irq_stack_ptr);
arch/arm64/kernel/traps.c-907- unsigned long ovf_stk = (unsigned long)this_cpu_ptr(overflow_stack);
--
arch/riscv/include/asm/irq_stack.h-12-
arch/riscv/include/asm/irq_stack.h:13:DECLARE_PER_CPU(ulong *, irq_stack_ptr);
arch/riscv/include/asm/irq_stack.h-14-
--
arch/riscv/kernel/entry.S=383=SYM_FUNC_START(call_on_irq_stack)
--
arch/riscv/kernel/entry.S-394- /* Switch to the per-CPU IRQ stack and call the handler */
arch/riscv/kernel/entry.S:395: load_per_cpu t0, irq_stack_ptr, t1
arch/riscv/kernel/entry.S-396- li t1, IRQ_STACK_SIZE
--
arch/riscv/kernel/irq.c=78=static void __init init_irq_scs(void)
--
arch/riscv/kernel/irq.c-93-
arch/riscv/kernel/irq.c:94:DEFINE_PER_CPU(ulong *, irq_stack_ptr);
arch/riscv/kernel/irq.c-95-
--
arch/riscv/kernel/irq.c=97=static void __init init_irq_stacks(void)
--
arch/riscv/kernel/irq.c-105- panic("Failed to allocate IRQ stack resources\n");
arch/riscv/kernel/irq.c:106: per_cpu(irq_stack_ptr, cpu) = p;
arch/riscv/kernel/irq.c-107- }
--
arch/riscv/kernel/irq.c=113=static void __init init_irq_stacks(void)
--
arch/riscv/kernel/irq.c-117- for_each_possible_cpu(cpu)
arch/riscv/kernel/irq.c:118: per_cpu(irq_stack_ptr, cpu) = per_cpu(irq_stack, cpu);
arch/riscv/kernel/irq.c-119-}
--
arch/x86/include/asm/irq_stack.h-118-#define call_on_irqstack(func, asm_call, argconstr...) \
arch/x86/include/asm/irq_stack.h:119: call_on_stack(__this_cpu_read(hardirq_stack_ptr), \
arch/x86/include/asm/irq_stack.h-120- func, asm_call, argconstr)
--
arch/x86/include/asm/processor.h=421=struct irq_stack {
--
arch/x86/include/asm/processor.h-424-
arch/x86/include/asm/processor.h:425:DECLARE_PER_CPU_CACHE_HOT(struct irq_stack *, hardirq_stack_ptr);
arch/x86/include/asm/processor.h-426-#ifdef CONFIG_X86_64
arch/x86/include/asm/processor.h=427=DECLARE_PER_CPU_CACHE_HOT(bool, hardirq_stack_inuse);
arch/x86/include/asm/processor.h-428-#else
arch/x86/include/asm/processor.h:429:DECLARE_PER_CPU_CACHE_HOT(struct irq_stack *, softirq_stack_ptr);
arch/x86/include/asm/processor.h-430-#endif
--
arch/x86/kernel/dumpstack_32.c=38=static bool in_hardirq_stack(unsigned long *stack, struct stack_info *info)
arch/x86/kernel/dumpstack_32.c-39-{
arch/x86/kernel/dumpstack_32.c:40: unsigned long *begin = (unsigned long *)this_cpu_read(hardirq_stack_ptr);
arch/x86/kernel/dumpstack_32.c-41- unsigned long *end = begin + (THREAD_SIZE / sizeof(long));
--
arch/x86/kernel/dumpstack_32.c=63=static bool in_softirq_stack(unsigned long *stack, struct stack_info *info)
arch/x86/kernel/dumpstack_32.c-64-{
arch/x86/kernel/dumpstack_32.c:65: unsigned long *begin = (unsigned long *)this_cpu_read(softirq_stack_ptr);
arch/x86/kernel/dumpstack_32.c-66- unsigned long *end = begin + (THREAD_SIZE / sizeof(long));
--
arch/x86/kernel/dumpstack_64.c=135=static __always_inline bool in_irq_stack(unsigned long *stack, struct stack_info *info)
arch/x86/kernel/dumpstack_64.c-136-{
arch/x86/kernel/dumpstack_64.c:137: unsigned long *end = (unsigned long *)this_cpu_read(hardirq_stack_ptr);
arch/x86/kernel/dumpstack_64.c-138- unsigned long *begin;
--
arch/x86/kernel/irq.c=38=EXPORT_PER_CPU_SYMBOL(__softirq_pending);
arch/x86/kernel/irq.c-39-
arch/x86/kernel/irq.c:40:DEFINE_PER_CPU_CACHE_HOT(struct irq_stack *, hardirq_stack_ptr);
arch/x86/kernel/irq.c-41-
--
arch/x86/kernel/irq_32.c=49=static inline void print_stack_overflow(void) { }
--
arch/x86/kernel/irq_32.c-51-
arch/x86/kernel/irq_32.c:52:DEFINE_PER_CPU_CACHE_HOT(struct irq_stack *, softirq_stack_ptr);
arch/x86/kernel/irq_32.c-53-
--
arch/x86/kernel/irq_32.c=69=static inline bool execute_on_irq_stack(bool overflow, struct irq_desc *desc)
--
arch/x86/kernel/irq_32.c-74- curstk = (struct irq_stack *) current_stack();
arch/x86/kernel/irq_32.c:75: irqstk = __this_cpu_read(hardirq_stack_ptr);
arch/x86/kernel/irq_32.c-76-
--
arch/x86/kernel/irq_32.c=107=int irq_init_percpu_irqstack(unsigned int cpu)
--
arch/x86/kernel/irq_32.c-111-
arch/x86/kernel/irq_32.c:112: if (per_cpu(hardirq_stack_ptr, cpu))
arch/x86/kernel/irq_32.c-113- return 0;
--
arch/x86/kernel/irq_32.c-123-
arch/x86/kernel/irq_32.c:124: per_cpu(hardirq_stack_ptr, cpu) = page_address(ph);
arch/x86/kernel/irq_32.c:125: per_cpu(softirq_stack_ptr, cpu) = page_address(ps);
arch/x86/kernel/irq_32.c-126- return 0;
--
arch/x86/kernel/irq_32.c=130=void do_softirq_own_stack(void)
--
arch/x86/kernel/irq_32.c-134-
arch/x86/kernel/irq_32.c:135: irqstk = __this_cpu_read(softirq_stack_ptr);
arch/x86/kernel/irq_32.c-136-
--
arch/x86/kernel/irq_64.c=36=static int map_irq_stack(unsigned int cpu)
--
arch/x86/kernel/irq_64.c-53- /* Store actual TOS to avoid adjustment in the hotpath */
arch/x86/kernel/irq_64.c:54: per_cpu(hardirq_stack_ptr, cpu) = va + IRQ_STACK_SIZE - 8;
arch/x86/kernel/irq_64.c-55- return 0;
--
arch/x86/kernel/irq_64.c=62=static int map_irq_stack(unsigned int cpu)
--
arch/x86/kernel/irq_64.c-66- /* Store actual TOS to avoid adjustment in the hotpath */
arch/x86/kernel/irq_64.c:67: per_cpu(hardirq_stack_ptr, cpu) = va + IRQ_STACK_SIZE - 8;
arch/x86/kernel/irq_64.c-68- return 0;
--
arch/x86/kernel/irq_64.c=72=int irq_init_percpu_irqstack(unsigned int cpu)
arch/x86/kernel/irq_64.c-73-{
arch/x86/kernel/irq_64.c:74: if (per_cpu(hardirq_stack_ptr, cpu))
arch/x86/kernel/irq_64.c-75- return 0;
]
|
| 44/2 |
2026/08/09 00:14 |
llm |
expert |
0m
Model:
Tokens:
input: 42727
output: 22
thoughts: 71
Thoughts: Okay, let's break this down.
**Investigating Hardirq Stack Usage on x86_64**
Right, so I need to figure out how the kernel tracks whether the hardirq stack is currently in use on x86_64 systems. The `hardirq_stack_inuse` variable sounds like the key, so I'll need to understand its role and how it's being used within the interrupt handling logic. Given this is x86 architecture-specific, I'll start by diving into the relevant assembly header, `arch/x86/include/asm/irq_stack.h`. That file should give me the details I need on how `hardirq_stack_inuse` is defined, used, and manipulated within the interrupt context. Hopefully, this will provide insight into how the kernel manages these stack resources to prevent corruption or unexpected behavior during hardware interrupt handling.
|
| 45/2 |
2026/08/09 00:14 |
tool |
grepper |
0m
Args: map[Expression:hardirq_stack_inuse]
Results: map[Output:arch/x86/include/asm/irq_stack.h-137- */ \
arch/x86/include/asm/irq_stack.h:138: if (user_mode(regs) || __this_cpu_read(hardirq_stack_inuse)) { \
arch/x86/include/asm/irq_stack.h-139- irq_enter_rcu(); \
--
arch/x86/include/asm/irq_stack.h-148- */ \
arch/x86/include/asm/irq_stack.h:149: __this_cpu_write(hardirq_stack_inuse, true); \
arch/x86/include/asm/irq_stack.h-150- call_on_irqstack(func, asm_call, constr); \
arch/x86/include/asm/irq_stack.h:151: __this_cpu_write(hardirq_stack_inuse, false); \
arch/x86/include/asm/irq_stack.h-152- } \
--
arch/x86/include/asm/irq_stack.h-214-{ \
arch/x86/include/asm/irq_stack.h:215: __this_cpu_write(hardirq_stack_inuse, true); \
arch/x86/include/asm/irq_stack.h-216- call_on_irqstack(__do_softirq, ASM_CALL_ARG0); \
arch/x86/include/asm/irq_stack.h:217: __this_cpu_write(hardirq_stack_inuse, false); \
arch/x86/include/asm/irq_stack.h-218-}
--
arch/x86/include/asm/processor.h=425=DECLARE_PER_CPU_CACHE_HOT(struct irq_stack *, hardirq_stack_ptr);
arch/x86/include/asm/processor.h-426-#ifdef CONFIG_X86_64
arch/x86/include/asm/processor.h:427:DECLARE_PER_CPU_CACHE_HOT(bool, hardirq_stack_inuse);
arch/x86/include/asm/processor.h-428-#else
--
arch/x86/kernel/irq_64.c-28-
arch/x86/kernel/irq_64.c:29:DEFINE_PER_CPU_CACHE_HOT(bool, hardirq_stack_inuse);
arch/x86/kernel/irq_64.c-30-DEFINE_PER_CPU_PAGE_ALIGNED(struct irq_stack, irq_stack_backing_store) __visible;
--
arch/x86/kernel/process_64.c=610=__switch_to(struct task_struct *prev_p, struct task_struct *next_p)
--
arch/x86/kernel/process_64.c-616- WARN_ON_ONCE(IS_ENABLED(CONFIG_DEBUG_ENTRY) &&
arch/x86/kernel/process_64.c:617: this_cpu_read(hardirq_stack_inuse));
arch/x86/kernel/process_64.c-618-
]
|
| 46/2 |
2026/08/09 00:14 |
llm |
expert |
0m
Model:
Tokens:
input: 43888
output: 39
thoughts: 1100
Thoughts: **Analyzing a Potential Kernel Bug**
Okay, so I'm looking at this `__switch_to` function, and I see this `WARN_ON_ONCE` about `hardirq_stack_inuse`. That's interesting, because it seems like if `hardirq_stack_inuse` is true during a context switch, it's going to trigger a warning. My initial thought was that if it switches and `hardirq_stack_inuse` remains true, then the next task would run with that flag set. If another interrupt occurs, the kernel *wouldn't* switch to the hardirq stack, potentially leading to stack corruption, but I realized this isn't the case. If `hardirq_stack_inuse` is true, the code uses the *current* task's stack.
Wait, if it uses the current task's stack, it *should* be fine, right? However, I need to consider all cases. Suppose the newly scheduled task is a user-mode task? Wouldn't the interrupt handler then switch to the hardirq stack? No, that is not how it works! Looking at the actual check:
```c
if (user_mode(regs) || __this_cpu_read(hardirq_stack_inuse)) {
irq_enter_rcu();
func(regs, vector);
irq_exit_rcu();
} else {
irq_enter_rcu();
__this_cpu_write(hardirq_stack_inuse, true);
call_on_irqstack(func, asm_call, constr);
__this_cpu_write(hardirq_stack_inuse, false);
irq_exit_rcu();
}
```
The interrupt *won't* switch to the hardirq stack if `hardirq_stack_inuse` is true. That means the current stack (which in this case is the kernel stack of the new task) will be used. No stack corruption, that's good. It just sounds like a potential denial of service, but that is temporary as the original task will set `hardirq_stack_inuse` to false once it wakes up. So it is just a temporary DoS, and is, therefore, probably not exploitable, right? Just a bug that triggers a warning.
Wait, if `CONFIG_PANIC_ON_WARN` is set, it panics. Ah, Syzkaller runs with `panic_on_warn=1`, so it crashes there. Okay, so that's why it's showing up. In production, however, this isn't usually set, so it would just print a warning and continue.
Is there any other impact? Ah! It could be holding an RCU read lock, if it schedules while atomic. Wait, the stack trace shows:
```
#2: ffffffff8e3c3880 (rcu_read_lock){....}-{1:3}, at: rtlock_slowlock_locked+0x2ab/0x3c20 kernel/locking/rtmutex.c:1874
```
Ah! It's holding `rcu_read_lock`! That's concerning, but let's see how that lock is taken, or released. The `rt_read_lock` function calls `rcu_read_lock()`.
```c
void __sched rt_read_lock(rwlock_t *rwlock) __acquires(RCU)
{
rtlock_might_resched();
rwlock_acquire_read(&rwlock->dep_map, 0, 0, _RET_IP_);
rwbase_read_lock(&rwlock->rwbase, TASK_RTLOCK_WAIT);
rcu_read_lock();
migrate_disable();
}
```
Okay, the `rcu_read_lock()` is *called after* `rwbase_read_lock()` in `rt_read_lock()`. So, while sleeping in `rwbase_read_lock()`, it is NOT holding the `rcu_read_lock()`. However, the stack trace indicates that it IS holding the `rcu_read_lock`. So, this means `rtlock_slowlock_locked` must be holding `rcu_read_lock()`. I need to investigate that.
|
| 47/2 |
2026/08/09 00:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/locking/rtmutex.c Name:rtlock_slowlock_locked]
Results: map[SourceCode:1851: /**
1852: * rtlock_slowlock_locked - Slow path lock acquisition for RT locks
1853: * @lock: The underlying RT mutex
1854: * @wake_q: The wake_q to wake tasks after we release the wait_lock
1855: */
1856: static void __sched rtlock_slowlock_locked(struct rt_mutex_base *lock,
1857: struct wake_q_head *wake_q)
1858: __releases(&lock->wait_lock) __acquires(&lock->wait_lock)
1859: {
1860: struct rt_mutex_waiter waiter;
1861: struct task_struct *owner;
1862:
1863: lockdep_assert_held(&lock->wait_lock);
1864: lockevent_inc(rtlock_slowlock);
1865:
1866: if (try_to_take_rt_mutex(lock, current, NULL)) {
1867: lockevent_inc(rtlock_slow_acq1);
1868: return;
1869: }
1870:
1871: rt_mutex_init_rtlock_waiter(&waiter);
1872:
1873: /* Save current state and set state to TASK_RTLOCK_WAIT */
1874: current_save_and_set_rtlock_wait_state();
1875:
1876: trace_contention_begin(lock, LCB_F_RT);
1877:
1878: task_blocks_on_rt_mutex(lock, &waiter, current, NULL, RT_MUTEX_MIN_CHAINWALK, wake_q);
1879:
1880: for (;;) {
1881: /* Try to acquire the lock again */
1882: if (try_to_take_rt_mutex(lock, current, &waiter)) {
1883: lockevent_inc(rtlock_slow_acq2);
1884: break;
1885: }
1886:
1887: if (&waiter == rt_mutex_top_waiter(lock))
1888: owner = rt_mutex_owner(lock);
1889: else
1890: owner = NULL;
1891: raw_spin_unlock_irq_wake(&lock->wait_lock, wake_q);
1892:
1893: if (!owner || !rtmutex_spin_on_owner(lock, &waiter, owner)) {
1894: lockevent_inc(rtlock_slow_sleep);
1895: schedule_rtlock();
1896: }
1897:
1898: raw_spin_lock_irq(&lock->wait_lock);
1899: set_current_state(TASK_RTLOCK_WAIT);
1900: }
1901:
1902: /* Restore the task state */
1903: current_restore_rtlock_saved_state();
1904:
1905: /*
1906: * try_to_take_rt_mutex() sets the waiter bit unconditionally.
1907: * We might have to fix that up:
1908: */
1909: fixup_rt_mutex_waiters(lock, true);
1910: debug_rt_mutex_free_waiter(&waiter);
1911:
1912: trace_contention_end(lock, 0);
1913: lockevent_cond_inc(rtlock_slow_wake, !wake_q_empty(wake_q));
1914: }
SourceFile:kernel/locking/rtmutex.c]
|
| 48/2 |
2026/08/09 00:14 |
llm |
expert |
0m
Model:
Tokens:
input: 47044
output: 40
thoughts: 53
Thoughts: **Investigating the RT Mutex Spin Loop**
Okay, so I see `rtmutex_spin_on_owner` is being called. That's interesting, a potential point of contention. My initial thought is to quickly verify the locking mechanisms involved. Wait a minute... is that `rcu_read_lock` being used within `rtmutex_spin_on_owner`? That seems a bit unusual, and definitely warrants a closer look. I need to dive into the implementation of `rtmutex_spin_on_owner` itself. I need to understand how it's interacting with RCU in this context. Let's get to the source code and figure out what's really happening here.
|
| 49/2 |
2026/08/09 00:14 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:kernel/locking/rtmutex.c Name:rtmutex_spin_on_owner]
Results: map[SourceCode:1495: static bool rtmutex_spin_on_owner(struct rt_mutex_base *lock,
1496: struct rt_mutex_waiter *waiter,
1497: struct task_struct *owner)
1498: {
1499: bool res = true;
1500:
1501: rcu_read_lock();
1502: for (;;) {
1503: /* If owner changed, trylock again. */
1504: if (owner != rt_mutex_owner(lock))
1505: break;
1506: /*
1507: * Ensure that @owner is dereferenced after checking that
1508: * the lock owner still matches @owner. If that fails,
1509: * @owner might point to freed memory. If it still matches,
1510: * the rcu_read_lock() ensures the memory stays valid.
1511: */
1512: barrier();
1513: /*
1514: * Stop spinning when:
1515: * - the lock owner has been scheduled out
1516: * - current is not longer the top waiter
1517: * - current is requested to reschedule (redundant
1518: * for CONFIG_PREEMPT_RCU=y)
1519: * - the VCPU on which owner runs is preempted
1520: */
1521: if (!owner_on_cpu(owner) || need_resched() ||
1522: !data_race(rt_mutex_waiter_is_top_waiter(lock, waiter))) {
1523: res = false;
1524: break;
1525: }
1526: cpu_relax();
1527: }
1528: rcu_read_unlock();
1529: return res;
1530: }
SourceFile:kernel/locking/rtmutex.c]
|
| 50/2 |
2026/08/09 00:14 |
llm |
expert |
0m
Model:
Tokens:
input: 47838
output: 25
thoughts: 2238
Thoughts: **Analysis of a "Sleeping Function Called From Invalid Context" Warning in a PREEMPT_RT Kernel**
Okay, so I'm looking at this bug report. My initial thought was that `rtmutex_spin_on_owner` was the problem, maybe due to an `rcu_read_lock()` call. But wait, the stack trace shows the warning occurring *within* `rtmutex_spin_on_owner`, not necessarily at the *call* site. And then, there's a KASAN check in the code, or at least a KASAN shadow address in the instruction. But no, the instruction isn't what's trapping... usually, the problematic instruction is the one *after*. It looks like a KASAN shadow address load.
Hold on, the `BUG` warning... it's a "sleeping function called from invalid context." This isn't a crash *per se*, but it's important. The kernel prints this warning and then dumps the registers *of the current task*. Could that task be the one in `rtmutex_spin_on_owner`? Yes, the warning originates from a call chain that includes `rt_read_lock`, `__might_resched`, and `rtlock_might_resched`.
Aha! The stack trace of the `<TASK>` confirms it. The task was *in* `rtmutex_spin_on_owner` when the timer interrupt hit. The interrupt interrupted the task, and then the interrupt handler (in this case, triggered by `xen_timer_callback` -> `kvm_xen_set_evtchn_fast` -> `rt_read_lock`) printed the warning. The `RIP` isn't a crash location, it's just the context that was interrupted. I need to verify that.
Checking the call trace confirms my thinking. The interrupted task was indeed inside `rtlock_slowlock_locked` -> `rtmutex_spin_on_owner`, attempting to acquire a lock—likely `pin_fs_lock` or `i_mutex_key`. The timer interrupt fired. The interrupt handler then proceeded through `xen_timer_callback` -> `kvm_xen_set_evtchn_fast`, ultimately calling `rt_read_lock`. `rt_read_lock` called `__might_resched`, which noticed it was in hardirq context and printed the warning.
So, it's just a warning. No actual crash or memory corruption, simply a PREEMPT_RT issue of attempting to acquire a sleeping lock (`rwlock_t`) within a timer interrupt handler, which is invalid.
Okay, the more important question: is this a Denial of Service? Yes, because lock contention with `rt_read_lock` could lead to a `schedule()` call from within hardirq context, leading to deadlocks or CPU stalls, as we know is the PREEMPT_RT way. If `panic_on_warn` is set, the kernel will panic.
Can an unprivileged user trigger this? Potentially, any user with access to `/dev/kvm` can create a VM, set up a Xen timer, and trigger the warning. That access is often available to unprivileged users, especially on desktop environments. So this is a VM Guest Trigger, because `/dev/kvm` is a user-space process using KVM ioctls.
Can a *guest* trigger this? Yes, because a guest can use hypercalls to program the Xen timer. This results in the KVM host setting up an hrtimer. When the hrtimer expires, the `xen_timer_callback` is executed, and since that's where the problem arises, the guest can absolutely trigger this warning. We need to verify that a guest can program the Xen timer, but the theory checks out.
|
| 51/2 |
2026/08/09 00:15 |
tool |
grepper |
0m
Args: map[Expression:kvm_xen_hvm_set_attr]
Results: map[Output:arch/x86/kvm/x86.c=4374=int kvm_arch_vm_ioctl(struct file *filp, unsigned int ioctl, unsigned long arg)
--
arch/x86/kvm/x86.c-4629- goto out;
arch/x86/kvm/x86.c:4630: r = kvm_xen_hvm_set_attr(kvm, &xha);
arch/x86/kvm/x86.c-4631- break;
--
arch/x86/kvm/xen.c=698=int __kvm_xen_has_interrupt(struct kvm_vcpu *v)
--
arch/x86/kvm/xen.c-746-
arch/x86/kvm/xen.c:747:int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_xen_hvm_attr *data)
arch/x86/kvm/xen.c-748-{
--
arch/x86/kvm/xen.h=24=int kvm_xen_vcpu_get_attr(struct kvm_vcpu *vcpu, struct kvm_xen_vcpu_attr *data);
arch/x86/kvm/xen.h:25:int kvm_xen_hvm_set_attr(struct kvm *kvm, struct kvm_xen_hvm_attr *data);
arch/x86/kvm/xen.h-26-int kvm_xen_hvm_get_attr(struct kvm *kvm, struct kvm_xen_hvm_attr *data);
]
|
| 52/2 |
2026/08/09 00:15 |
llm |
expert |
0m
Model:
Tokens:
input: 51350
output: 21
thoughts: 28
Thoughts: Okay, here's my interpretation of those thoughts, tailored for an expert audience, presented in the first person:
**Kernel Interaction Considerations**
Hmm, so I'm looking at `kvm_xen_hvm_set_attr`. Right, that's a Virtual Machine I/O control operation, an ioctl. But that naturally makes me think: what about guest hypercalls? Does this directly relate to or overlap with those mechanisms? I'm wondering if there's a potential area for optimization or potential conflicts given both are methods for the guest and host to interact. I need to keep that hypercall context in mind as I delve further.
|
| 53/2 |
2026/08/09 00:15 |
tool |
grepper |
0m
Args: map[Expression:kvm_xen_hypercall]
Results: map[Output:arch/x86/kvm/hyperv.c=1387=static int kvm_hv_set_msr_pw(struct kvm_vcpu *vcpu, u32 msr, u64 data,
--
arch/x86/kvm/hyperv.c-1421- */
arch/x86/kvm/hyperv.c:1422: if (kvm_xen_hypercall_enabled(kvm)) {
arch/x86/kvm/hyperv.c-1423- /* orl $0x80000000, %eax */
--
arch/x86/kvm/trace.h=114=TRACE_EVENT(kvm_hv_hypercall_done,
--
arch/x86/kvm/trace.h-131- */
arch/x86/kvm/trace.h:132:TRACE_EVENT(kvm_xen_hypercall,
arch/x86/kvm/trace.h-133- TP_PROTO(u8 cpl, unsigned long nr,
--
arch/x86/kvm/x86.c=7431=int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
arch/x86/kvm/x86.c-7432-{
arch/x86/kvm/x86.c:7433: if (kvm_xen_hypercall_enabled(vcpu->kvm))
arch/x86/kvm/x86.c:7434: return kvm_xen_hypercall(vcpu);
arch/x86/kvm/x86.c-7435-
--
arch/x86/kvm/xen.c=1279=int kvm_xen_write_hypercall_page(struct kvm_vcpu *vcpu, u64 data)
--
arch/x86/kvm/xen.c-1309- */
arch/x86/kvm/xen.c:1310: if (kvm_xen_hypercall_enabled(kvm)) {
arch/x86/kvm/xen.c-1311- u8 instructions[32];
--
arch/x86/kvm/xen.c=1365=int kvm_xen_hvm_config(struct kvm *kvm, struct kvm_xen_hvm_config *xhc)
--
arch/x86/kvm/xen.c-1411-
arch/x86/kvm/xen.c:1412:static int kvm_xen_hypercall_set_result(struct kvm_vcpu *vcpu, u64 result)
arch/x86/kvm/xen.c-1413-{
--
arch/x86/kvm/xen.c-1417-
arch/x86/kvm/xen.c:1418:static int kvm_xen_hypercall_complete_userspace(struct kvm_vcpu *vcpu)
arch/x86/kvm/xen.c-1419-{
--
arch/x86/kvm/xen.c-1424-
arch/x86/kvm/xen.c:1425: return kvm_xen_hypercall_set_result(vcpu, run->xen.u.hcall.result);
arch/x86/kvm/xen.c-1426-}
--
arch/x86/kvm/xen.c=1666=static bool kvm_xen_hcall_set_timer_op(struct kvm_vcpu *vcpu, uint64_t timeout,
--
arch/x86/kvm/xen.c-1680-
arch/x86/kvm/xen.c:1681:int kvm_xen_hypercall(struct kvm_vcpu *vcpu)
arch/x86/kvm/xen.c-1682-{
--
arch/x86/kvm/xen.c-1717- cpl = kvm_x86_call(get_cpl)(vcpu);
arch/x86/kvm/xen.c:1718: trace_kvm_xen_hypercall(cpl, input, params[0], params[1], params[2],
arch/x86/kvm/xen.c-1719- params[3], params[4], params[5]);
--
arch/x86/kvm/xen.c-1759- if (handled)
arch/x86/kvm/xen.c:1760: return kvm_xen_hypercall_set_result(vcpu, r);
arch/x86/kvm/xen.c-1761-
--
arch/x86/kvm/xen.c-1775- vcpu->arch.complete_userspace_io =
arch/x86/kvm/xen.c:1776: kvm_xen_hypercall_complete_userspace;
arch/x86/kvm/xen.c-1777-
--
arch/x86/kvm/xen.h=67=static inline bool kvm_xen_is_hypercall_page_msr(struct kvm *kvm, u32 msr)
--
arch/x86/kvm/xen.h-74-
arch/x86/kvm/xen.h:75:static inline bool kvm_xen_hypercall_enabled(struct kvm *kvm)
arch/x86/kvm/xen.h-76-{
--
arch/x86/kvm/xen.h=103=static inline int kvm_xen_has_pending_timer(struct kvm_vcpu *vcpu)
arch/x86/kvm/xen.h-104-{
arch/x86/kvm/xen.h:105: if (kvm_xen_hypercall_enabled(vcpu->kvm) && kvm_xen_timer_enabled(vcpu))
arch/x86/kvm/xen.h-106- return atomic_read(&vcpu->arch.xen.timer_pending);
--
arch/x86/kvm/xen.h=143=static inline bool kvm_xen_is_hypercall_page_msr(struct kvm *kvm, u32 msr)
--
arch/x86/kvm/xen.h-147-
arch/x86/kvm/xen.h:148:static inline bool kvm_xen_hypercall_enabled(struct kvm *kvm)
arch/x86/kvm/xen.h-149-{
--
arch/x86/kvm/xen.h=181=static inline bool kvm_xen_is_tsc_leaf(struct kvm_vcpu *vcpu, u32 function)
--
arch/x86/kvm/xen.h-186-
arch/x86/kvm/xen.h:187:int kvm_xen_hypercall(struct kvm_vcpu *vcpu);
arch/x86/kvm/xen.h-188-
]
|
| 54/2 |
2026/08/09 00:15 |
llm |
expert |
0m
Model:
Tokens:
input: 53371
output: 49
thoughts: 19
Thoughts: Okay, here's my take on those thoughts, framed as an expert's internal monologue:
**Diving into `kvm_xen_hcall_set_timer_op`**
Alright, time to dig into this `kvm_xen_hcall_set_timer_op` function. I recognize that naming convention, that's definitely KVM-on-Xen territory. Knowing that, my immediate instinct is to figure out the architectural implications. This likely involves some level of hypercall manipulation, probably dealing with virtual timers from the KVM guest's perspective, but channeled through the Xen hypervisor for actual execution. The "set_timer_op" part suggests this function is involved in either creating, configuring, or potentially cancelling a virtual timer within the guest. There's a decent chance this is key for handling timekeeping within a nested virtualization context, which always gets… *interesting*. I wonder if this relates to some of the observed performance discrepancies I've seen in the past. Time to roll up my sleeves and see what the code has to offer.
|
| 55/2 |
2026/08/09 00:15 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:arch/x86/kvm/xen.c Name:kvm_xen_hcall_set_timer_op]
Results: map[SourceCode:1666: static bool kvm_xen_hcall_set_timer_op(struct kvm_vcpu *vcpu, uint64_t timeout,
1667: u64 *r)
1668: {
1669: if (!kvm_xen_timer_enabled(vcpu))
1670: return false;
1671:
1672: if (timeout)
1673: kvm_xen_start_timer(vcpu, timeout, true);
1674: else
1675: kvm_xen_stop_timer(vcpu);
1676:
1677: *r = 0;
1678: return true;
1679: }
SourceFile:arch/x86/kvm/xen.c]
|
| 56/2 |
2026/08/09 00:15 |
llm |
expert |
0m
Model:
Tokens:
input: 53899
output: 66
thoughts: 1465
Thoughts: **Vulnerability Assessment: Xen Timer Callback in Hardirq Context**
Okay, so I've been digging into this potential bug involving the Xen timer callback. My initial thought is that a guest can use the `set_timer_op` hypercall to start a timer, and when it expires, the `xen_timer_callback` runs on the host in a hardirq context. This is the crux of the issue; a malicious guest can *definitely* trigger this, making it a "VM Guest Trigger."
Let's break down the impact. Initially, it looked like this was just a "sleeping function called from invalid context" warning. On a `PREEMPT_RT` system, this can lead to a deadlock (Denial of Service) if there's contention for a lock, or a panic if `panic_on_warn` is set. It doesn't seem like it's a direct route to memory corruption or privilege escalation, which is good from an exploitation perspective.
* **Exploitable:** No, it's not a direct path to privilege escalation or memory corruption.
* **Denial of Service:** Yes, absolutely. Deadlocks and kernel panics are on the table.
* **Unprivileged Access:** Yes, this is a concern because access to `/dev/kvm` is common, which provides access.
* **User Namespace:** This is also potentially exploitable because access to `/dev/kvm` is common in user namespaces.
* **VM Guest Trigger:** Confirmed. The guest can actively cause this via Xen hypercalls.
* **VM Host Trigger:** No, it's not triggered from the host against a confidential guest.
* **Network/Remote/Peripheral/Filesystem:** Not directly related to these attack vectors.
Thinking it through, if a task is holding a `raw_spinlock_t` and is interrupted by the timer, the `xen_timer_callback` calls `schedule()` which schedules the task out while holding the lock. Another task then tries to acquire the same `raw_spinlock_t`, and it will spin forever because preemption is disabled. *That's* a hard deadlock, a DoS, which is concerning.
Okay, let's keep probing. Is there *any* way to twist this into memory corruption? From what I can see, it is only a scheduling anomaly. The interrupted task's state is preserved. This means we are only dealing with a deadlock, and no privilege escalation or information leak.
I wanted to double-check that `kvm_xen_set_evtchn_fast` can only be called from specific contexts. `kvm_xen_set_evtchn_fast` is called from `kvm_xen_set_evtchn`, which is called from `kvm_xen_hvm_evtchn_send` (guest hypercall) and `kvm_xen_hvm_set_attr` (host ioctl). This is fine because the calls here are from task context and the sleeping lock is valid. The issue is *specifically* in the hardirq context of `xen_timer_callback`.
So the refined assessment is:
* **Exploitable:** False
* **DenialOfService:** True
* **Unprivileged:** True (if `/dev/kvm` is accessible)
* **UserNamespace:** True
* **VMGuestTrigger:** True
* **VMHostTrigger:** False
* **NetworkTrigger:** False
* **RemoteTrigger:** False
* **PeripheralTrigger:** False
* **FilesystemTrigger:** False
Now, a very crucial question. Does the guest need any special setup to leverage Xen hypercalls? The host user-space VMM has to enable Xen support for the VM by setting `KVM_XEN_HVM_CONFIG`. If the VMM doesn't, the guest is blocked. However, an unprivileged user on the host *can* create a VM, enable Xen support, and then trigger the bug. So, unprivileged access is a definite risk. A guest running under a VMM that enables Xen support (like QEMU with Xen emulation enabled) can trigger it.
Checking `panic_on_warn` is not required for a DoS. The deadlock by itself is sufficient to cause a hard deadlock. Also, scheduling from hardirq context also leaves the APIC ISR bit set. This masks interrupts of lower priority until the original task is scheduled back. This leads to system stalls and watchdogs.
Okay, are there any *other* angles? If a task is scheduled out from hardirq context, its `preempt_count` has `HARDIRQ_OFFSET` set. When it gets scheduled back in, it *still* has that offset. And `hardir
|
| 57/2 |
2026/08/09 00:15 |
tool |
set-results |
0m
Args: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:true VMHostTrigger:false]
Results: map[DenialOfService:true Exploitable:false FilesystemTrigger:false NetworkTrigger:false PeripheralTrigger:false RemoteTrigger:false Unprivileged:true UserNamespace:true VMGuestTrigger:true VMHostTrigger:false]
|
| 58/2 |
2026/08/09 00:15 |
llm |
expert |
0m
Model:
Tokens:
input: 56519
output: 683
thoughts: 415
Thoughts: **My Analysis of the PREEMPT_RT Kernel Bug**
Okay, the dust has settled, and the results are in. Time to solidify my understanding into the final response.
This boils down to a classic PREEMPT_RT issue: a "sleeping function called from invalid context" warning. The root cause is pretty clear. The `kvm_xen_set_evtchn_fast()` function, specifically within the KVM/Xen interaction, is taking a `gpc->lock`. This lock is an `rwlock_t`, which, on a PREEMPT_RT kernel, is a *sleeping lock*.
Here's the problem: `kvm_xen_set_evtchn_fast()` is *also* called from `xen_timer_callback()`, and that callback is an hrtimer callback executing within a hardirq context. The critical mistake is trying to acquire a sleeping lock from within hardirq context. This is fundamentally wrong and the warning is a direct consequence.
Now, if there's contention on that lock, and this is the crux of the problem, the hardirq context *will* end up calling `schedule()`. And that's where the fun really begins. Scheduling from hardirq context opens the door to several Denial of Service (DoS) scenarios:
1. If the interrupted task was holding a `raw_spinlock_t`, scheduling away leaves that lock held. Any other task trying to acquire it is stuck spinning with preemption disabled, leading to a hard deadlock.
2. Scheduling from hardirq context leaves the APIC In-Service Register (ISR) bit set, masking interrupts of the same or lower priority on that CPU. This can stall the system or, worse, trigger watchdog panics.
3. If `CONFIG_PANIC_ON_WARN` is enabled, the warning itself becomes a kernel panic. So, a simple misconfiguration or exploitation of the bug will result in a fatal error.
The implications are interesting. An unprivileged user with access to `/dev/kvm` on the host could trigger this by creating a VM and manipulating the Xen timer via KVM ioctls. Furthermore, a malicious guest running under a VMM that has enabled KVM Xen emulation can do the same.
On the positive side, it’s strictly a DoS vulnerability. There's no memory corruption or information disclosure involved, so no privilege escalation is possible. It is a bug, but it’s contained to DoS. Now, time to formulate the fix.
|
| 59/1 |
2026/08/09 00:15 |
action |
format-explanation |
0m
Results: map[Explanation:**Analysis of the Bug**
The bug is a "sleeping function called from invalid context" warning that
specifically affects `PREEMPT_RT` kernels.
**Root Cause:**
The issue occurs in the KVM Xen emulation code. The function
`kvm_xen_set_evtchn_fast()` attempts to acquire `gpc->lock`, which is defined as
an `rwlock_t`. On standard kernels, `rwlock_t` is a spinlock and disables
interrupts, making it safe to use in atomic contexts. However, on `PREEMPT_RT`
kernels, `rwlock_t` is mapped to a sleeping lock (`rt_rw_lock`).
The bug is triggered because `kvm_xen_set_evtchn_fast()` is called by
`xen_timer_callback()`, which is an `hrtimer` callback that executes in hardirq
(atomic) context. Attempting to acquire a sleeping lock while in hardirq context
is invalid and triggers the kernel warning.
**Security Impact:**
- **Denial of Service (DoS):** If `gpc->lock` is contended when the timer
interrupt fires, the `PREEMPT_RT` locking primitives will call `schedule()` to
put the task to sleep. Calling `schedule()` from hardirq context is highly
destructive:
1. **Deadlocks:** If the timer interrupt preempted a task that was holding a
`raw_spinlock_t`, scheduling away leaves that lock held. Any other task
attempting to acquire the same `raw_spinlock_t` will spin indefinitely with
preemption disabled, resulting in a hard CPU deadlock.
2. **Interrupt Masking:** Scheduling away leaves the CPU's APIC In-Service
Register (ISR) bit set for the timer interrupt. This permanently masks all
interrupts of the same or lower priority on that CPU until the original task
is scheduled back and finishes the interrupt handler, leading to severe system
stalls or hardware watchdog panics.
3. **Kernel Panic:** If the kernel is booted with `panic_on_warn=1` (common in
syzkaller and some production environments), the warning itself will
immediately halt the system.
- **Exploitability:** The bug does not cause memory corruption (like
use-after-free or out-of-bounds accesses) or leak kernel memory to userspace.
The state of the interrupted task is safely preserved on the stack. Therefore,
it is not exploitable for privilege escalation or information disclosure.
**Trigger Vectors:**
- **Unprivileged Processes:** An unprivileged user on the host with access to
`/dev/kvm` (which is commonly granted via the `kvm` group or local udev ACLs)
can trigger this by creating a VM, setting up the Xen timer via the
`KVM_XEN_VCPU_SET_ATTR` ioctl, and waiting for the timer to expire.
- **VM Guest Trigger:** A malicious guest running under a VMM that has enabled
KVM Xen emulation (e.g., QEMU) can trigger this bug on the host by programming
the Xen timer via guest hypercalls (e.g., `set_timer_op`). When the timer
expires, the host kernel executes the hrtimer callback and hits the bug.]
|