Message ID | 20241031202011.1580522-1-seanjc@google.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | KVM: nVMX: Treat vpid01 as current if L2 is active, but with VPID disabled | expand |
On Thu, Oct 31, 2024 at 01:20:11PM -0700, Sean Christopherson wrote: >When getting the current VPID, e.g. to emulate a guest TLB flush, return >vpid01 if L2 is running but with VPID disabled, i.e. if VPID is disabled >in vmcs12. Architecturally, if VPID is disabled, then the guest and host >effectively share VPID=0. KVM emulates this behavior by using vpid01 when >running an L2 with VPID disabled (see prepare_vmcs02_early_rare()), and so >KVM must also treat vpid01 as the current VPID while L2 is active. > >Unconditionally treating vpid02 as the current VPID when L2 is active >causes KVM to flush TLB entries for vpid02 instead of vpid01, which >results in TLB entries from L1 being incorrectly preserved across nested >VM-Enter to L2 (L2=>L1 isn't problematic, because the TLB flush after >nested VM-Exit flushes vpid01). > >The bug manifests as failures in the vmx_apicv_test KVM-Unit-Test, as KVM >incorrectly retains TLB entries for the APIC-access page across a nested >VM-Enter. > >Opportunisticaly add comments at various touchpoints to explain the >architectural requirements, and also why KVM uses vpid01 instead of vpid02. > >All credit goes to Chao, who root caused the issue and identified the fix. > >Link: https://lore.kernel.org/all/ZwzczkIlYGX+QXJz@intel.com >Fixes: 2b4a5a5d5688 ("KVM: nVMX: Flush current VPID (L1 vs. L2) for KVM_REQ_TLB_FLUSH_GUEST") >Cc: stable@vger.kernel.org >Cc: Like Xu <like.xu.linux@gmail.com> >Debugged-by: Chao Gao <chao.gao@intel.com> >Signed-off-by: Sean Christopherson <seanjc@google.com> Reviewed-by: Chao Gao <chao.gao@intel.com> I also ran the vmx_apicv_test KVM-Unit-Test. All failures are gone with this patch applied. So, Tested-by: Chao Gao <chao.gao@intel.com>
On Thu, 31 Oct 2024 13:20:11 -0700, Sean Christopherson wrote: > When getting the current VPID, e.g. to emulate a guest TLB flush, return > vpid01 if L2 is running but with VPID disabled, i.e. if VPID is disabled > in vmcs12. Architecturally, if VPID is disabled, then the guest and host > effectively share VPID=0. KVM emulates this behavior by using vpid01 when > running an L2 with VPID disabled (see prepare_vmcs02_early_rare()), and so > KVM must also treat vpid01 as the current VPID while L2 is active. > > [...] Applied to kvm-x86 fixes, thanks! [1/1] KVM: nVMX: Treat vpid01 as current if L2 is active, but with VPID disabled https://github.com/kvm-x86/linux/commit/2657b82a78f1 -- https://github.com/kvm-x86/linux/tree/next
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c index 746cb41c5b98..aa78b6f38dfe 100644 --- a/arch/x86/kvm/vmx/nested.c +++ b/arch/x86/kvm/vmx/nested.c @@ -1197,11 +1197,14 @@ static void nested_vmx_transition_tlb_flush(struct kvm_vcpu *vcpu, kvm_hv_nested_transtion_tlb_flush(vcpu, enable_ept); /* - * If vmcs12 doesn't use VPID, L1 expects linear and combined mappings - * for *all* contexts to be flushed on VM-Enter/VM-Exit, i.e. it's a - * full TLB flush from the guest's perspective. This is required even - * if VPID is disabled in the host as KVM may need to synchronize the - * MMU in response to the guest TLB flush. + * If VPID is disabled, then guest TLB accesses use VPID=0, i.e. the + * same VPID as the host, and so architecturally, linear and combined + * mappings for VPID=0 must be flushed at VM-Enter and VM-Exit. KVM + * emulates L2 sharing L1's VPID=0 by using vpid01 while running L2, + * and so KVM must also emulate TLB flush of VPID=0, i.e. vpid01. This + * is required if VPID is disabled in KVM, as a TLB flush (there are no + * VPIDs) still occurs from L1's perspective, and KVM may need to + * synchronize the MMU in response to the guest TLB flush. * * Note, using TLB_FLUSH_GUEST is correct even if nested EPT is in use. * EPT is a special snowflake, as guest-physical mappings aren't @@ -2315,6 +2318,17 @@ static void prepare_vmcs02_early_rare(struct vcpu_vmx *vmx, vmcs_write64(VMCS_LINK_POINTER, INVALID_GPA); + /* + * If VPID is disabled, then guest TLB accesses use VPID=0, i.e. the + * same VPID as the host. Emulate this behavior by using vpid01 for L2 + * if VPID is disabled in vmcs12. Note, if VPID is disabled, VM-Enter + * and VM-Exit are architecturally required to flush VPID=0, but *only* + * VPID=0. I.e. using vpid02 would be ok (so long as KVM emulates the + * required flushes), but doing so would cause KVM to over-flush. E.g. + * if L1 runs L2 X with VPID12=1, then runs L2 Y with VPID12 disabled, + * and then runs L2 X again, then KVM can and should retain TLB entries + * for VPID12=1. + */ if (enable_vpid) { if (nested_cpu_has_vpid(vmcs12) && vmx->nested.vpid02) vmcs_write16(VIRTUAL_PROCESSOR_ID, vmx->nested.vpid02); @@ -5957,6 +5971,12 @@ static int handle_invvpid(struct kvm_vcpu *vcpu) return nested_vmx_fail(vcpu, VMXERR_INVALID_OPERAND_TO_INVEPT_INVVPID); + /* + * Always flush the effective vpid02, i.e. never flush the current VPID + * and never explicitly flush vpid01. INVVPID targets a VPID, not a + * VMCS, and so whether or not the current vmcs12 has VPID enabled is + * irrelevant (and there may not be a loaded vmcs12). + */ vpid02 = nested_get_vpid02(vcpu); switch (type) { case VMX_VPID_EXTENT_INDIVIDUAL_ADDR: diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c index 6ed801ffe33f..3d4a8d5b0b80 100644 --- a/arch/x86/kvm/vmx/vmx.c +++ b/arch/x86/kvm/vmx/vmx.c @@ -3213,7 +3213,7 @@ void vmx_flush_tlb_all(struct kvm_vcpu *vcpu) static inline int vmx_get_current_vpid(struct kvm_vcpu *vcpu) { - if (is_guest_mode(vcpu)) + if (is_guest_mode(vcpu) && nested_cpu_has_vpid(get_vmcs12(vcpu))) return nested_get_vpid02(vcpu); return to_vmx(vcpu)->vpid; }
When getting the current VPID, e.g. to emulate a guest TLB flush, return vpid01 if L2 is running but with VPID disabled, i.e. if VPID is disabled in vmcs12. Architecturally, if VPID is disabled, then the guest and host effectively share VPID=0. KVM emulates this behavior by using vpid01 when running an L2 with VPID disabled (see prepare_vmcs02_early_rare()), and so KVM must also treat vpid01 as the current VPID while L2 is active. Unconditionally treating vpid02 as the current VPID when L2 is active causes KVM to flush TLB entries for vpid02 instead of vpid01, which results in TLB entries from L1 being incorrectly preserved across nested VM-Enter to L2 (L2=>L1 isn't problematic, because the TLB flush after nested VM-Exit flushes vpid01). The bug manifests as failures in the vmx_apicv_test KVM-Unit-Test, as KVM incorrectly retains TLB entries for the APIC-access page across a nested VM-Enter. Opportunisticaly add comments at various touchpoints to explain the architectural requirements, and also why KVM uses vpid01 instead of vpid02. All credit goes to Chao, who root caused the issue and identified the fix. Link: https://lore.kernel.org/all/ZwzczkIlYGX+QXJz@intel.com Fixes: 2b4a5a5d5688 ("KVM: nVMX: Flush current VPID (L1 vs. L2) for KVM_REQ_TLB_FLUSH_GUEST") Cc: stable@vger.kernel.org Cc: Like Xu <like.xu.linux@gmail.com> Debugged-by: Chao Gao <chao.gao@intel.com> Signed-off-by: Sean Christopherson <seanjc@google.com> --- arch/x86/kvm/vmx/nested.c | 30 +++++++++++++++++++++++++----- arch/x86/kvm/vmx/vmx.c | 2 +- 2 files changed, 26 insertions(+), 6 deletions(-) base-commit: e466901b947d529f7b091a3b00b19d2bdee206ee