From 81d99abe2bd81379abfb595e403bbbee6fbfc624 Mon Sep 17 00:00:00 2001 From: EJ Campbell Date: Tue, 29 Sep 2026 11:12:32 -0700 Subject: [PATCH 1/2] x86/kvm: Don't consume async #PF flags on a kernel #PF in NMI context An async #PF "page not present" event arrives through the #PF vector, and its reason sits in the per-CPU apf_reason.flags until __kvm_handle_async_pf() reads and clears it. If an NMI arrives after the event is injected and before the #PF handler reads the flags, and the NMI handler itself takes a #PF, that fault reads and clears the flags belonging to the #PF it interrupted. It then finds interrupts disabled in the NMI context it interrupted, and panics: Kernel panic - not syncing: Host injected async #PF in interrupt disabled region Call Trace: panic+0x52/0x60 __kvm_handle_async_pf+0xb0/0xb0 exc_page_fault+0x71/0xb0 asm_exc_page_fault+0x27/0x30 RIP: 0010:__get_user_nocheck_8+0x6/0x20 perf_callchain_user+0x147/0x1a0 get_perf_callchain+0x12d/0x1c0 ... perf_event_nmi_handler+0x26/0x50 exc_nmi+0xe8/0x110 RIP: 0010:asm_exc_page_fault+0x0/0x30 The last RIP line shows the NMI landing on the first instruction of the #PF entry. perf with user call chains takes such a fault: the sampling NMI walks the user stack with __get_user(), which faults when a saved frame pointer is not a valid user address or the walk runs off the top of the stack. The same window exists when KVM, running as an L1 hypervisor with DELIVERY_AS_PF_VMEXIT, reads the flags itself after a #PF VM-exit. The NMI entry code saves and restores CR2 for this nesting, but not the async #PF reason. Andy Lutomirski described the race, with perf as the example, when async #PF delivery was reworked in 2020 [1]. Saving and clearing the flags across the NMI, next to CR2, was discussed there but not merged. Since that rework, Linux has stopped setting KVM_ASYNC_PF_SEND_ALWAYS, so the host delivers "page not present" only for user mode faults and a kernel-mode fault in NMI context (including kernel-mode #MC and #DB) cannot be an async page fault. In that case, return without touching the flags so that the interrupted handler finds them intact. Tested on v7.3-rc5 in a 4-vCPU, 2 GiB guest restored from a snapshot whose memory is served on demand through userfaultfd, so that reading it back takes async #PFs, while every thread samples hardware cycles with user call chains and points its frame pointer at an unmapped page. Without this patch the guest panicked as above in 3 of 3 runs. With it, the guest survived 3 of 3 runs of 60 seconds, and the host counted 148,441 to 153,334 kvm_async_pf_not_present events in the first 20 seconds of each. On 6.18.50 the results were the same: the guest panicked without the patch and survived with it. Link: https://lore.kernel.org/all/ed71d0967113a35f670a9625a058b8e6e0b2f104.1583547991.git.luto@kernel.org/ [1] Link: https://lore.kernel.org/all/20211126123145.2772-1-jiangshanlai@gmail.com/ [2] Cc: stable@vger.kernel.org # v5.9+ Signed-off-by: EJ Campbell Link: https://patch.msgid.link/CADkxfh-uQTca_6jB1JPDq1MsKffF_UKGQR339oQSJNcoV75GNQ@mail.gmail.com Signed-off-by: Paolo Bonzini --- arch/x86/kernel/kvm.c | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/arch/x86/kernel/kvm.c b/arch/x86/kernel/kvm.c index 6b0a5861ccb8..d8dd2cadcbf7 100644 --- a/arch/x86/kernel/kvm.c +++ b/arch/x86/kernel/kvm.c @@ -265,11 +265,39 @@ noinstr u32 kvm_read_and_reset_apf_flags(void) } EXPORT_SYMBOL_FOR_KVM(kvm_read_and_reset_apf_flags); +static __always_inline bool kvm_apf_reason_pending(void) +{ + return __this_cpu_read(async_pf_enabled) && + __this_cpu_read(apf_reason.flags); +} + noinstr bool __kvm_handle_async_pf(struct pt_regs *regs, u32 token) { - u32 flags = kvm_read_and_reset_apf_flags(); irqentry_state_t state; + u32 flags; + if (!kvm_apf_reason_pending()) + return false; + + /* + * The guest does not enable KVM_ASYNC_PF_SEND_ALWAYS, so the host + * delivers async #PF only to user mode (or as a #PF VM-exit, which KVM + * reads itself). A #PF from kernel mode can still arrive between that + * delivery and the read of the reason flags, inside an NMI that + * interrupted it, for instance perf reading user memory for a call + * chain. Such a fault is not async, so leave the async PF flags + * untouched for the outer handler. Outside NMI context, on the other + * hand, async PF flags on a kernel-mode fault mean the host is broken. + */ + if (!user_mode(regs)) { + if (likely(in_nmi())) + return false; + state = irqentry_enter(regs); + instrumentation_begin(); + panic("Host injected async #PF in kernel mode\n"); + } + + flags = kvm_read_and_reset_apf_flags(); if (!flags) return false; @@ -285,8 +313,6 @@ noinstr bool __kvm_handle_async_pf(struct pt_regs *regs, u32 token) panic("Host injected async #PF in interrupt disabled region\n"); if (flags & KVM_PV_REASON_PAGE_NOT_PRESENT) { - if (unlikely(!(user_mode(regs)))) - panic("Host injected async #PF in kernel mode\n"); /* Page is swapped out by the host. */ kvm_async_pf_task_wait_schedule(token); } else { From c8f4cfe1cf93bce924e27f2abead7a4c4a487505 Mon Sep 17 00:00:00 2001 From: Amirmohammad Eftekhar Date: Thu, 1 Oct 2026 14:43:54 -0700 Subject: [PATCH 2/2] KVM: nVMX: Reset MMU back to the root MMU if loading L2 state fails for !VM-Entry Reset the vCPU's MMU pointer back to the root MMU if loading L2 state fails in the "not from VM-Entry" case, i.e. when emulating RSM or handling KVM_SET_NESTED_STATE. Failure to unwind the MMU pointer results in KVM trying to run L1 with a nested MMU, and is especially problematic if the vCPU manages to free its nested state before the VM crashes. Constructing the root for a nested EPT MMU goes through vmcs12, which is dynamically allocated/freed when nVMX is enabled/disabled. And so if the nested state is freed with the MMU still pointing at the nested MMU, KVM will hit a NULL pointer deref when attempting to get L1=>L2 EPTP. BUG: kernel NULL pointer dereference, address: 0000000000000078 #PF: supervisor read access in kernel mode #PF: error_code(0x0000) - not-present page PGD 114a2d067 P4D 111743067 PUD 11141f067 PMD 0 Oops: Oops: 0000 [#1] SMP CPU: 20 UID: 1000 PID: 1272 Comm: nested_tdp_faul Not tainted 7.3.0-rc4-8c89f11d1a99b-sink-vm #1914 PREEMPTLAZY Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015 RIP: 0010:nested_ept_get_eptp+0xb/0x10 [kvm_intel] Call Trace: kvm_mmu_load+0xcd/0x870 [kvm] kvm_arch_vcpu_ioctl_run+0x18f5/0x1f80 [kvm] kvm_vcpu_ioctl+0x2c7/0x970 [kvm] __x64_sys_ioctl+0x90/0xd0 do_syscall_64+0x9c/0x4d0 entry_SYSCALL_64_after_hwframe+0x4b/0x53 RIP: 0033:0x47491b Modules linked in: kvm_intel kvm irqbypass CR2: 0000000000000078 ---[ end trace 0000000000000000 ]--- RIP: 0010:nested_ept_get_eptp+0xb/0x10 [kvm_intel] Cc: David Woodhouse Fixes: 72e9cbdb4338 ("KVM: nVMX: fix SMI injection in guest mode") Fixes: 8fcc4b5923af ("kvm: nVMX: Introduce KVM_CAP_NESTED_STATE") Cc: stable@vger.kernel.org Signed-off-by: Amirmohammad Eftekhar [sean: write changelog] Signed-off-by: Sean Christopherson Link: https://patch.msgid.link/20261001214356.3825100-2-seanjc@google.com Signed-off-by: Paolo Bonzini --- arch/x86/kvm/vmx/nested.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c index b25216862740..5165275128fc 100644 --- a/arch/x86/kvm/vmx/nested.c +++ b/arch/x86/kvm/vmx/nested.c @@ -3770,8 +3770,10 @@ enum nvmx_vmentry_status nested_vmx_enter_non_root_mode(struct kvm_vcpu *vcpu, vmentry_fail_vmexit: vmx_switch_vmcs(vcpu, &vmx->vmcs01); - if (!from_vmentry) + if (!from_vmentry) { + nested_ept_uninit_mmu_context(vcpu); return NVMX_VMENTRY_VMEXIT; + } nested_put_vmcs12_pages(vcpu);