syzbot
WARNING in kvm_vcpu_ioctl
Status:
closed as dup on 2026/10/29 17:26
Subsystems:
kvm
Labels:
prio:low
[Documentation on labels]
Reported-by: syzbot+da2a2f350196c7a0a389@syzkaller.appspotmail.com
First crash: 2d03h, last: 2d03h
β¨ AI-generated root cause summary:
When a VM is terminated or marked dead (e.g., via kvm_vm_dead() due to an error in enter_smm() or migration), the KVM_REQ_VM_DEAD request is raised on its vCPUs. If userspace subsequently issues the KVM_PRE_FAULT_MEMORY ioctl, kvm_tdp_page_prefault() bails out upon observing KVM_REQ_VM_DEAD and returns -EIO. However, kvm_vcpu_pre_fault_memory() in virt/kvm/kvm_main.c considers -EIO an unexpected internal condition and triggers WARN_ON_ONCE(r == 0 || r == -EIO). In a production kernel without panic_on_warn, execution breaks out of the loop and cleanly returns -EIO to userspace without crashing or corrupting memory.
▶
▼
β¨ Full AI root cause analysis
Root Cause Analysis
1. Functions, Structs, and Files Involved
virt/kvm/kvm_main.c: kvm_vcpu_pre_fault_memory() handles the generic KVM_PRE_FAULT_MEMORY vCPU ioctl.
arch/x86/kvm/mmu/mmu.c:
kvm_arch_vcpu_pre_fault_memory(): x86 architecture backend for prefaulting GPA ranges.
kvm_tdp_page_prefault(): Performs TDP prefaulting for a given GPA and checks vCPU requests.
include/linux/kvm_host.h & arch/x86/kvm/smm.c (and other caller sites): kvm_vm_dead() marks a VM as dead and raises KVM_REQ_VM_DEAD on all vCPUs.
2. Mechanism and Trigger
- When a fatal condition occurs in a VM that does not bug the host kernel data structuresβfor example, failure to enter SMM via
enter_smm() in arch/x86/kvm/smm.c:370 (goto error -> kvm_vm_dead(vcpu->kvm)), or after context migration in sev_vm_move_enc_context_from()βKVM marks the VM dead by calling kvm_vm_dead(kvm).
kvm_vm_dead() sets kvm->vm_dead = true and raises KVM_REQ_VM_DEAD on each vCPU via kvm_make_all_cpus_request(kvm, KVM_REQ_VM_DEAD).
- If userspace subsequently invokes
ioctl(vcpu_fd, KVM_PRE_FAULT_MEMORY, ...) on the vCPU:
kvm_vcpu_ioctl() calls kvm_vcpu_pre_fault_memory().
kvm_vcpu_pre_fault_memory() invokes the arch hook kvm_arch_vcpu_pre_fault_memory(), which calls kvm_tdp_page_prefault().
- In
kvm_tdp_page_prefault() (arch/x86/kvm/mmu/mmu.c:5119):
if (kvm_test_request(KVM_REQ_VM_DEAD, vcpu))
return -EIO;
kvm_tdp_page_prefault() checks for KVM_REQ_VM_DEAD to avoid getting stuck in a prefault retry loop and explicitly returns -EIO.
- This
-EIO is propagated back to kvm_vcpu_pre_fault_memory() in virt/kvm/kvm_main.c:4464-4465:
r = kvm_arch_vcpu_pre_fault_memory(vcpu, range);
if (WARN_ON_ONCE(r == 0 || r == -EIO))
break;
- Because
r == -EIO (-5, matching register R12 = 0xfffffffffffffffb), WARN_ON_ONCE(r == 0 || r == -EIO) triggers.
The defect is an API contract mismatch between the generic KVM pre-fault memory loop in virt/kvm/kvm_main.c (which assumes -EIO is a bug that should never occur and asserts on it) and the architecture-specific TDP page fault handler (which legitimately returns -EIO when KVM_REQ_VM_DEAD is set).
3. Runtime Consequences in a Production Kernel
- Memory Corruption: False. No invalid memory access, out-of-bounds write/read, or use-after-free occurs.
- Info Leak: "none". No kernel memory or pointer values are disclosed to userspace.
- Denial of Service: False.
WARN_ON_ONCE does not halt execution in a production kernel without panic_on_warn. When the warning fires, execution immediately executes break;, drops the SRCU lock (srcu_read_unlock(&vcpu->kvm->srcu, idx)), releases the vCPU (vcpu_put(vcpu)), and safely returns -EIO to userspace. All kernel state remains consistent and no deadlock or leak ensues.
Sample crash report:
------------[ cut here ]------------
r == 0 || r == -EIO
WARNING: arch/x86/kvm/../../../virt/kvm/kvm_main.c:4465 at kvm_vcpu_pre_fault_memory virt/kvm/kvm_main.c:4465 [inline], CPU#1: syz.0.17/6063
WARNING: arch/x86/kvm/../../../virt/kvm/kvm_main.c:4465 at kvm_vcpu_ioctl+0x156a/0x17b0 virt/kvm/kvm_main.c:4744, CPU#1: syz.0.17/6063
Modules linked in:
CPU: 1 UID: 0 PID: 6063 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
RIP: 0010:kvm_vcpu_pre_fault_memory virt/kvm/kvm_main.c:4465 [inline]
RIP: 0010:kvm_vcpu_ioctl+0x156a/0x17b0 virt/kvm/kvm_main.c:4744
Code: 9c 35 8b 00 be 01 00 00 00 4c 89 f7 e8 4f b1 b5 03 e9 c3 f8 ff ff e8 85 35 8b 00 90 0f 0b 90 e9 2c f2 ff ff e8 77 35 8b 00 90 <0f> 0b 90 e9 38 fd ff ff e8 69 35 8b 00 65 48 8b 3d 99 02 36 14 31
RSP: 0018:ffffc90003957c98 EFLAGS: 00010293
RAX: 0000000000000000 RBX: 1ffff9200072af98 RCX: 0000000000000000
RDX: ffff8880355fcb00 RSI: ffffffff81831d69 RDI: 0000000000000007
RBP: ffff8880459f8048 R08: fffffffffffffffb R09: fffffffffffffffb
R10: ffff8880459f803f R11: 0000000000000001 R12: fffffffffffffffb
R13: ffffed1006abf960 R14: ffff8880459f8000 R15: ffffc90003957d70
FS: 00007f7bf8c946c0(0000) GS:ffff8880d458e000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f0b9aa1ee9c CR3: 0000000037636000 CR4: 0000000000352ef0
Call Trace:
<TASK>
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:597 [inline]
__se_sys_ioctl fs/ioctl.c:583 [inline]
__x64_sys_ioctl+0x18e/0x210 fs/ioctl.c:583
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x123/0x790 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f7bf7d9f559
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f7bf8c94028 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 00007f7bf8026090 RCX: 00007f7bf7d9f559
RDX: 0000200000000800 RSI: 00000000c040aed5 RDI: 0000000000000005
RBP: 00007f7bf7e36b20 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f7bf8026128 R14: 00007f7bf8026090 R15: 00007ffcdd24ea18
</TASK>
* Struck through repros no longer work on HEAD.