[PATCH] KVM: nVMX: Re-arm the vmcs12 pages request if mapping the pages fails
Your Name <[email protected]>
| Newsgroups | org.kernel.vger.stable |
|---|---|
| Message-ID | <[email protected]> |
From: Jinwoo Lee <[email protected]> Re-arm KVM_REQ_GET_NESTED_STATE_PAGES when nested_get_vmcs12_pages() fails, so that KVM retries the mapping on the next KVM_RUN instead of resuming L2 with a stale vmcs02. On failure KVM exits to userspace with KVM_EXIT_INTERNAL_ERROR but leaves the vCPU in guest mode with vmcs02 loaded. The request has already been consumed by kvm_check_request() in vcpu_enter_guest(), and nothing re-arms it, so a subsequent KVM_RUN goes straight to VM-Enter. vmcs02's APIC_ACCESS_ADDR, VIRTUAL_APIC_PAGE_ADDR and POSTED_INTR_DESC_ADDR still hold the host physical addresses that were mapped for the previous nested VM-Enter. Those pages have already been unmapped and unpinned by nested_put_vmcs12_pages(), which runs after vmx_switch_vmcs() to vmcs01 and therefore cannot clear the vmcs02 fields, and prepare_vmcs02_early() re-arms the controls that consume them without rewriting the address fields. Hardware then accesses pages that KVM no longer holds a reference to. The SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES branch makes this worse by returning before the CPU_BASED_TPR_SHADOW and posted interrupt fallbacks, which would otherwise write INVALID_GPA to VIRTUAL_APIC_PAGE_ADDR and clear PIN_BASED_POSTED_INTR. Commit 671ddc700fd0 ("KVM: nVMX: Don't leak L1 MMIO regions to L2") replaced the "clear the control" fallback with an error return. The intent is right, but it left vmcs02 in a usable state. Re-arming the request closes that; if the mapping keeps failing KVM keeps exiting to userspace, which is noisy but safe. Note this relies on KVM_REQ_GET_NESTED_STATE_PAGES being cleared on nested VM-Exit, which "KVM: nVMX: Ensure KVM_REQ_GET_NESTED_STATE_PAGES is cleared on VM-Exit" makes unconditional. Fixes: 671ddc700fd0 ("KVM: nVMX: Don't leak L1 MMIO regions to L2") Cc: [email protected] Signed-off-by: Jinwoo Lee <[email protected]> --- Notes for reviewers, not intended for the commit log. Affected versions: v5.4-rc5 (671ddc700fd0) through v7.2-rc6. Verified that nested_get_vmcs12_pages() and the KVM_REQ_GET_NESTED_STATE_PAGES consumer in vcpu_enter_guest() are unchanged in kvm-x86/next as of 2026-08-13. Disclosure: this was found with AI-assisted code review, so per Documentation/process/security-bugs.rst it is being reported publicly rather than to [email protected]. What I verified empirically, on the RSM path with the load_pdptrs() abort: - KVM consumes KVM_REQ_GET_NESTED_STATE_PAGES, the mapping fails, KVM_RUN returns 0 with run->exit_reason left at KVM_EXIT_UNKNOWN, and the vCPU is still in guest mode. - The request is not re-armed, and the next KVM_RUN VM-Enters L2, which then executes with vmcs02 still naming the previously mapped pages. Confirmed deterministically (4/4) with a selftest, plus a control run showing that the same sequence without the poison maps successfully. - With the patch applied the code compiles clean, but I have not been able to boot a patched kernel, so the fix itself is not runtime tested. The selftest fails on an unpatched kernel as expected. What I did not verify: - The APIC-access branch end to end. That is the interesting one, because it is reachable by L1 alone (point vmcs12->apic_access_addr at an unbacked GPA) and it returns before the CPU_BASED_TPR_SHADOW and posted interrupt fallbacks. This host does not expose SECONDARY_EXEC_VIRTUALIZE_APIC_ACCESSES or PIN_BASED_POSTED_INTR to L1, so I could only reach the load_pdptrs() abort, which needs userspace to poison the PDPTEs through a KVM_GUESTDBG_SINGLESTEP window and is therefore not guest-triggerable on its own. - Whether a stale page is actually reused by the host. The selftest keeps every page allocated as its own guest RAM for the whole run. On whether userspace resumes: QEMU's kvm_cpu_exec() treats KVM_INTERNAL_ERROR_EMULATION as recoverable and returns EXCP_INTERRUPT when kvm_arch_stop_on_emulation_error() is false, which for x86 is the case when the guest is in protected mode at CPL 3 (target/i386/kvm/kvm.c). It does not re-push nested state on that path. This is from reading qemu.git at 055952c0aa91; I have not run it. A selftest is available. I have not included it here per the reproducer guidance in security-bugs.rst; happy to post it if you want it. arch/x86/kvm/vmx/nested.c | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c index ddf6df7bee93..9e9bd6c541ba 100644 --- a/arch/x86/kvm/vmx/nested.c +++ b/arch/x86/kvm/vmx/nested.c @@ -3453,7 +3453,7 @@ static bool nested_get_vmcs12_pages(struct kvm_vcpu *vcpu) * state which can lead to a load of wrong PDPTRs. */ if (CC(!load_pdptrs(vcpu, vcpu->arch.cr3))) - return false; + goto fail; } @@ -3469,7 +3469,7 @@ static bool nested_get_vmcs12_pages(struct kvm_vcpu *vcpu) vcpu->run->internal.suberror = KVM_INTERNAL_ERROR_EMULATION; vcpu->run->internal.ndata = 0; - return false; + goto fail; } } @@ -3525,6 +3525,20 @@ static bool nested_get_vmcs12_pages(struct kvm_vcpu *vcpu) exec_controls_clearbit(vmx, CPU_BASED_USE_MSR_BITMAPS); return true; + +fail: + /* + * Re-arm the request so that KVM retries the mapping instead of running + * L2 with a stale vmcs02. Bailing here leaves the vCPU in guest mode + * with vmcs02 loaded and its APIC-access, virtual-APIC and posted + * interrupt descriptor addresses still pointing at the host pages that + * were mapped for the *previous* nested VM-Enter, which have since been + * unmapped and unpinned by nested_put_vmcs12_pages(). KVM returns to + * userspace without leaving guest mode, so if userspace resumes the + * vCPU, VM-Enter succeeds and hardware accesses those stale HPAs. + */ + kvm_make_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu); + return false; } static bool vmx_get_nested_state_pages(struct kvm_vcpu *vcpu) -- 2.43.0