* [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled
@ 2026-07-23 9:44 Paolo Bonzini
2026-07-24 22:36 ` Sean Christopherson
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Paolo Bonzini @ 2026-07-23 9:44 UTC (permalink / raw)
To: linux-kernel, kvm; +Cc: Vitaly Kuznetsov, Alexander Lougovski
Red Hat is seeing multiple reports of Windows memory corruptions
(and consequent BSODs) with hv-tlbflush=on, on AMD processors only.
The crashes, while extremely rare, happen even with a stock configuration,
but with Driver Verifier enabled they can be detected after approximately
200 VM hours. In particular, Alexander Lougovski measured the following:
- on AMD Turin, 15 crashes in 3300 VM hours
- on AMD Milan, 2 crashes in 500 VM hours (there are fewer hours
here due to the host being smaller)
- on Intel Sapphire Rapids, 0 crashes in 8000 VM hours
- on AMD Turin with full TLB flush (not exactly this patch but
similar), no crashes in ~2 weeks of run time which should also
be ~7000 VM hours
For Turin, the microcode version was 0x0b002162, which (assuming
this is the same issue) should not be affected by the problem listed in
https://knowledge.broadcom.com/external/article/419026/bsod-on-virtual-machines-running-on-amd.html;
on the other hand that problem should not apply to earlier processors.
AMD has not provided any information or analysis yet, and when we asked
we didn't know yet that it reproduced on Milan as well.
As to the workload, Alexander threw more or less everything at the same
time at the VM:
- a full Windows Defender scan every 30 minutes
- a disk I/O job
- a loop doing repeated mmap of system files (mostly to hope that
it triggers some consistency check in the Windows memory manager)
- SQL Express 2022 + StressDB (1.6M rows), with the host doing queries
(75% write/25% read) via sqlcmd
Driver Verifier is able to detect BSODs more or less at the same time as
the pages are freed. They mostly happen in the Windows Defender filter
driver, but occasionally also in the networking stack (e.g., afd.sys)
or elsewhere in the filesystem stack (e.g., fltmgr.sys).
The flush is issued from kvm_hv_vcpu_flush_tlb(), which receives the
cross-CPU requests from the Hyper-V TLB flush hypercalls via a kfifo
and is invoked by the KVM_REQ_HV_TLB_FLUSH request. The mechanism is
the same for both Intel and AMD, and the handler for both vendors is
a simple INVVPID(ADDR)/INVLPGA instruction.
Because the request is handled on the destination CPU, there is a question
of what happens if the VM is migrated across physical CPUs. In that case,
the INVLPGA instruction would use a stale svm->vmcb->control.asid; but
if anything that might do an *unnecessary* flush (on an asid that's being
used for another VM) and then pre_svm_run() would force a full TLB rebuild.
So, for lack of better ideas, this patch forces a full ASID bump in
svm_flush_tlb_gva(). To avoid paying the price on Intel and also to
avoid unnecessary loops on AMD, the flush_tlb_gva op now returns whether
it did a full flush or not; kvm_hv_vcpu_flush_tlb() takes note and exits
its loops immediately. While there is an obvious performance impact,
about half of the benefit from Hyper-V tlbflush is preserved (10% vs. 20%
on the SQL Server workload).
kvm_mmu_invalidate_addr() is the only other caller of the flush_tlb_gva op.
The change would have a performance impact on every intercepted INVLPG and,
for nested SVM, on every L1 INVLPGA. For INVLPGA specifically, this covers
the same suspected issue but for nested hypervisors, so it is correct to
apply the workaround; for INVLPG on shadow paging, instead, the impact
would be stronger and, due to lack of data, for now the use of INVLPGA is
left in place in svm_flush_tlb_gva().
Analyzed-by: Vitaly Kuznetsov <vkuznets@redhat.com>
Analyzed-by: Alexander Lougovski <alougovsk@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/include/asm/kvm_host.h | 2 +-
arch/x86/kvm/hyperv.c | 7 ++++---
arch/x86/kvm/mmu/mmu.c | 2 +-
arch/x86/kvm/svm/svm.c | 27 ++++++++++++++++++++-------
arch/x86/kvm/vmx/main.c | 4 ++--
arch/x86/kvm/vmx/vmx.c | 2 +-
arch/x86/kvm/vmx/x86_ops.h | 2 +-
7 files changed, 30 insertions(+), 16 deletions(-)
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index b517257a6315..eca04d4b974e 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -1751,7 +1751,7 @@ struct kvm_x86_ops {
* Can potentially get non-canonical addresses through INVLPGs, which
* the implementation may choose to ignore if appropriate.
*/
- void (*flush_tlb_gva)(struct kvm_vcpu *vcpu, gva_t addr);
+ void (*flush_tlb_gva)(struct kvm_vcpu *vcpu, gva_t addr, bool *full);
/*
* Flush any TLB entries created by the guest. Like tlb_flush_gva(),
diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 1ee0d23f8949..5ec1bf28a195 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -1974,6 +1974,7 @@ int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu)
u64 entries[KVM_HV_TLB_FLUSH_FIFO_SIZE];
int i, j, count;
gva_t gva;
+ bool full = false;
if (!tdp_enabled || !hv_vcpu)
return -EINVAL;
@@ -1982,7 +1983,7 @@ int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu)
count = kfifo_out(&tlb_flush_fifo->entries, entries, KVM_HV_TLB_FLUSH_FIFO_SIZE);
- for (i = 0; i < count; i++) {
+ for (i = 0; i < count && !full; i++) {
if (entries[i] == KVM_HV_TLB_FLUSHALL_ENTRY)
goto out_flush_all;
@@ -1991,11 +1992,11 @@ int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu)
* pages to flush.
*/
gva = entries[i] & PAGE_MASK;
- for (j = 0; j < (entries[i] & ~PAGE_MASK) + 1; j++) {
+ for (j = 0; j < (entries[i] & ~PAGE_MASK) + 1 && !full; j++) {
if (is_noncanonical_invlpg_address(gva + j * PAGE_SIZE, vcpu))
continue;
- kvm_x86_call(flush_tlb_gva)(vcpu, gva + j * PAGE_SIZE);
+ kvm_x86_call(flush_tlb_gva)(vcpu, gva + j * PAGE_SIZE, &full);
}
++vcpu->stat.tlb_flush;
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 6c13da942bfc..bea5499fc9f1 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -6672,7 +6672,7 @@ void kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_pagewalk *w,
if (is_noncanonical_invlpg_address(addr, vcpu))
return;
- kvm_x86_call(flush_tlb_gva)(vcpu, addr);
+ kvm_x86_call(flush_tlb_gva)(vcpu, addr, NULL);
if (tdp_enabled)
return;
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index ef69a51ab27f..0bd63970305b 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -4222,13 +4222,6 @@ static void svm_flush_tlb_all(struct kvm_vcpu *vcpu)
svm_flush_tlb_asid(vcpu);
}
-static void svm_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva)
-{
- struct vcpu_svm *svm = to_svm(vcpu);
-
- invlpga(gva, svm->vmcb->control.asid);
-}
-
static void svm_flush_tlb_guest(struct kvm_vcpu *vcpu)
{
kvm_register_mark_dirty(vcpu, VCPU_REG_ERAPS);
@@ -4236,6 +4229,26 @@ static void svm_flush_tlb_guest(struct kvm_vcpu *vcpu)
svm_flush_tlb_asid(vcpu);
}
+static void svm_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva, bool *full)
+{
+ struct vcpu_svm *svm = to_svm(vcpu);
+
+ /*
+ * INVLPGA has had errata on Genoa and Turin, and even on older
+ * generations there were reports of Windows BSODs if INVLPGA
+ * was used for Hyper-V tlbflush. Use it only for shadow paging
+ * where it seems to be okay.
+ */
+ if (!npt_enabled) {
+ invlpga(gva, svm->vmcb->control.asid);
+ return;
+ }
+
+ svm_flush_tlb_guest(vcpu);
+ if (full)
+ *full = true;
+}
+
static inline void sync_cr8_to_lapic(struct kvm_vcpu *vcpu)
{
struct vcpu_svm *svm = to_svm(vcpu);
diff --git a/arch/x86/kvm/vmx/main.c b/arch/x86/kvm/vmx/main.c
index 83d9921277ea..f204a0fc0a57 100644
--- a/arch/x86/kvm/vmx/main.c
+++ b/arch/x86/kvm/vmx/main.c
@@ -535,12 +535,12 @@ static void vt_flush_tlb_current(struct kvm_vcpu *vcpu)
vmx_flush_tlb_current(vcpu);
}
-static void vt_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr)
+static void vt_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr, bool *full)
{
if (is_td_vcpu(vcpu))
return;
- vmx_flush_tlb_gva(vcpu, addr);
+ vmx_flush_tlb_gva(vcpu, addr, full);
}
static void vt_flush_tlb_guest(struct kvm_vcpu *vcpu)
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 3681d565f177..e27084d30a5e 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -3374,7 +3374,7 @@ void vmx_flush_tlb_current(struct kvm_vcpu *vcpu)
vpid_sync_context(vmx_get_current_vpid(vcpu));
}
-void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr)
+void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr, bool *full)
{
/*
* vpid_sync_vcpu_addr() is a nop if vpid==0, see the comment in
diff --git a/arch/x86/kvm/vmx/x86_ops.h b/arch/x86/kvm/vmx/x86_ops.h
index 409858074246..17595d52985c 100644
--- a/arch/x86/kvm/vmx/x86_ops.h
+++ b/arch/x86/kvm/vmx/x86_ops.h
@@ -82,7 +82,7 @@ void vmx_set_rflags(struct kvm_vcpu *vcpu, unsigned long rflags);
bool vmx_get_if_flag(struct kvm_vcpu *vcpu);
void vmx_flush_tlb_all(struct kvm_vcpu *vcpu);
void vmx_flush_tlb_current(struct kvm_vcpu *vcpu);
-void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr);
+void vmx_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t addr, bool *full);
void vmx_flush_tlb_guest(struct kvm_vcpu *vcpu);
void vmx_set_interrupt_shadow(struct kvm_vcpu *vcpu, int mask);
u32 vmx_get_interrupt_shadow(struct kvm_vcpu *vcpu);
--
2.55.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled
2026-07-23 9:44 [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled Paolo Bonzini
@ 2026-07-24 22:36 ` Sean Christopherson
2026-07-25 13:37 ` Paolo Bonzini
2026-07-24 23:42 ` Yosry Ahmed
2026-07-27 19:23 ` Tycho Andersen
2 siblings, 1 reply; 9+ messages in thread
From: Sean Christopherson @ 2026-07-24 22:36 UTC (permalink / raw)
To: Paolo Bonzini
Cc: linux-kernel, kvm, Vitaly Kuznetsov, Alexander Lougovski,
Yosry Ahmed
+Yosry, who has been digging deep on SVM TLB crud.
On Thu, Jul 23, 2026, Paolo Bonzini wrote:
> The flush is issued from kvm_hv_vcpu_flush_tlb(), which receives the
> cross-CPU requests from the Hyper-V TLB flush hypercalls via a kfifo
> and is invoked by the KVM_REQ_HV_TLB_FLUSH request. The mechanism is
> the same for both Intel and AMD, and the handler for both vendors is
> a simple INVVPID(ADDR)/INVLPGA instruction.
>
> Because the request is handled on the destination CPU, there is a question
> of what happens if the VM is migrated across physical CPUs. In that case,
> the INVLPGA instruction would use a stale svm->vmcb->control.asid; but
> if anything that might do an *unnecessary* flush (on an asid that's being
> used for another VM) and then pre_svm_run() would force a full TLB rebuild.
>
> So, for lack of better ideas, this patch forces a full ASID bump in
> svm_flush_tlb_gva(). To avoid paying the price on Intel and also to
> avoid unnecessary loops on AMD, the flush_tlb_gva op now returns whether
> it did a full flush or not; kvm_hv_vcpu_flush_tlb() takes note and exits
> its loops immediately. While there is an obvious performance impact,
> about half of the benefit from Hyper-V tlbflush is preserved (10% vs. 20%
> on the SQL Server workload).
>
> kvm_mmu_invalidate_addr() is the only other caller of the flush_tlb_gva op.
> The change would have a performance impact on every intercepted INVLPG and,
> for nested SVM, on every L1 INVLPGA. For INVLPGA specifically, this covers
> the same suspected issue but for nested hypervisors, so it is correct to
> apply the workaround; for INVLPG on shadow paging, instead, the impact
> would be stronger and, due to lack of data, for now the use of INVLPGA is
> left in place in svm_flush_tlb_gva().
>
> Analyzed-by: Vitaly Kuznetsov <vkuznets@redhat.com>
> Analyzed-by: Alexander Lougovski <alougovsk@redhat.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> ---
> arch/x86/include/asm/kvm_host.h | 2 +-
> arch/x86/kvm/hyperv.c | 7 ++++---
> arch/x86/kvm/mmu/mmu.c | 2 +-
> arch/x86/kvm/svm/svm.c | 27 ++++++++++++++++++++-------
> arch/x86/kvm/vmx/main.c | 4 ++--
> arch/x86/kvm/vmx/vmx.c | 2 +-
> arch/x86/kvm/vmx/x86_ops.h | 2 +-
> 7 files changed, 30 insertions(+), 16 deletions(-)
>
> diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
> index b517257a6315..eca04d4b974e 100644
> --- a/arch/x86/include/asm/kvm_host.h
> +++ b/arch/x86/include/asm/kvm_host.h
> @@ -1751,7 +1751,7 @@ struct kvm_x86_ops {
> * Can potentially get non-canonical addresses through INVLPGs, which
> * the implementation may choose to ignore if appropriate.
> */
> - void (*flush_tlb_gva)(struct kvm_vcpu *vcpu, gva_t addr);
> + void (*flush_tlb_gva)(struct kvm_vcpu *vcpu, gva_t addr, bool *full);
LOL, why on earth are you using an out-param? If we do this at runtime, just
return a bool, at least that way we don't have to churn every call-site.
But I would much rather handle this by nuking .flush_tlb_gva at setup, e.g.
diff --git a/arch/x86/include/asm/kvm-x86-ops.h b/arch/x86/include/asm/kvm-x86-ops.h
index 3776cf5382a2..4235331b71a0 100644
--- a/arch/x86/include/asm/kvm-x86-ops.h
+++ b/arch/x86/include/asm/kvm-x86-ops.h
@@ -61,7 +61,7 @@ KVM_X86_OP(flush_tlb_current)
KVM_X86_OP_OPTIONAL(flush_remote_tlbs)
KVM_X86_OP_OPTIONAL(flush_remote_tlbs_range)
#endif
-KVM_X86_OP(flush_tlb_gva)
+KVM_X86_OP_OPTIONAL(flush_tlb_gva)
KVM_X86_OP(flush_tlb_guest)
KVM_X86_OP(vcpu_pre_run)
KVM_X86_OP(vcpu_run)
diff --git a/arch/x86/kvm/hyperv.c b/arch/x86/kvm/hyperv.c
index 4438ecac9a89..7b1c6391f878 100644
--- a/arch/x86/kvm/hyperv.c
+++ b/arch/x86/kvm/hyperv.c
@@ -1978,7 +1978,8 @@ int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu)
count = kfifo_out(&tlb_flush_fifo->entries, entries, KVM_HV_TLB_FLUSH_FIFO_SIZE);
for (i = 0; i < count; i++) {
- if (entries[i] == KVM_HV_TLB_FLUSHALL_ENTRY)
+ if (entries[i] == KVM_HV_TLB_FLUSHALL_ENTRY ||
+ !kvm_x86_ops.flush_tlb_gva)
goto out_flush_all;
/*
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index f0144ae8d891..bf16119640dc 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -6555,7 +6555,10 @@ void kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
if (is_noncanonical_invlpg_address(addr, vcpu))
return;
- kvm_x86_call(flush_tlb_gva)(vcpu, addr);
+ if (kvm_x86_ops.flush_tlb_gva)
+ kvm_x86_call(flush_tlb_gva)(vcpu, addr);
+ else
+ kvm_make_request(KVM_REQ_TLB_FLUSH_GUEST, vcpu);
}
if (!mmu->sync_spte)
diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c
index c46a34aeb3df..9e04f016d939 100644
--- a/arch/x86/kvm/svm/svm.c
+++ b/arch/x86/kvm/svm/svm.c
@@ -5688,6 +5688,15 @@ static __init int svm_hardware_setup(void)
if (!enable_pmu)
pr_info("PMU virtualization is disabled\n");
+ /*
+ * INVLPGA has had errata on Genoa and Turin, and even on older
+ * generations there were reports of Windows BSODs if INVLPGA
+ * was used for Hyper-V tlbflush. Use it only for shadow paging
+ * where it seems to be okay.
+ */
+ if (npt_enabled)
+ svm_x86_ops.flush_tlb_gva = NULL;
+
svm_set_cpu_caps();
kvm_caps.inapplicable_quirks &= ~KVM_X86_QUIRK_CD_NW_CLEARED;
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled
2026-07-23 9:44 [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled Paolo Bonzini
2026-07-24 22:36 ` Sean Christopherson
@ 2026-07-24 23:42 ` Yosry Ahmed
2026-07-24 23:50 ` Yosry Ahmed
2026-07-25 13:32 ` Paolo Bonzini
2026-07-27 19:23 ` Tycho Andersen
2 siblings, 2 replies; 9+ messages in thread
From: Yosry Ahmed @ 2026-07-24 23:42 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: linux-kernel, kvm, Vitaly Kuznetsov, Alexander Lougovski
On Thu, Jul 23, 2026 at 2:44 AM Paolo Bonzini <pbonzini@redhat.com> wrote:
>
> Red Hat is seeing multiple reports of Windows memory corruptions
> (and consequent BSODs) with hv-tlbflush=on, on AMD processors only.
> The crashes, while extremely rare, happen even with a stock configuration,
> but with Driver Verifier enabled they can be detected after approximately
> 200 VM hours. In particular, Alexander Lougovski measured the following:
>
> - on AMD Turin, 15 crashes in 3300 VM hours
>
> - on AMD Milan, 2 crashes in 500 VM hours (there are fewer hours
> here due to the host being smaller)
>
> - on Intel Sapphire Rapids, 0 crashes in 8000 VM hours
>
> - on AMD Turin with full TLB flush (not exactly this patch but
> similar), no crashes in ~2 weeks of run time which should also
> be ~7000 VM hours
>
> For Turin, the microcode version was 0x0b002162, which (assuming
> this is the same issue) should not be affected by the problem listed in
> https://knowledge.broadcom.com/external/article/419026/bsod-on-virtual-machines-running-on-amd.html;
> on the other hand that problem should not apply to earlier processors.
> AMD has not provided any information or analysis yet, and when we asked
> we didn't know yet that it reproduced on Milan as well.
>
> As to the workload, Alexander threw more or less everything at the same
> time at the VM:
>
> - a full Windows Defender scan every 30 minutes
>
> - a disk I/O job
>
> - a loop doing repeated mmap of system files (mostly to hope that
> it triggers some consistency check in the Windows memory manager)
>
> - SQL Express 2022 + StressDB (1.6M rows), with the host doing queries
> (75% write/25% read) via sqlcmd
>
> Driver Verifier is able to detect BSODs more or less at the same time as
> the pages are freed. They mostly happen in the Windows Defender filter
> driver, but occasionally also in the networking stack (e.g., afd.sys)
> or elsewhere in the filesystem stack (e.g., fltmgr.sys).
>
> The flush is issued from kvm_hv_vcpu_flush_tlb(), which receives the
> cross-CPU requests from the Hyper-V TLB flush hypercalls via a kfifo
> and is invoked by the KVM_REQ_HV_TLB_FLUSH request. The mechanism is
> the same for both Intel and AMD, and the handler for both vendors is
> a simple INVVPID(ADDR)/INVLPGA instruction.
>
> Because the request is handled on the destination CPU, there is a question
> of what happens if the VM is migrated across physical CPUs. In that case,
> the INVLPGA instruction would use a stale svm->vmcb->control.asid; but
> if anything that might do an *unnecessary* flush (on an asid that's being
> used for another VM) and then pre_svm_run() would force a full TLB rebuild.
>
> So, for lack of better ideas, this patch forces a full ASID bump in
> svm_flush_tlb_gva(). To avoid paying the price on Intel and also to
> avoid unnecessary loops on AMD, the flush_tlb_gva op now returns whether
> it did a full flush or not; kvm_hv_vcpu_flush_tlb() takes note and exits
> its loops immediately. While there is an obvious performance impact,
> about half of the benefit from Hyper-V tlbflush is preserved (10% vs. 20%
> on the SQL Server workload).
>
> kvm_mmu_invalidate_addr() is the only other caller of the flush_tlb_gva op.
> The change would have a performance impact on every intercepted INVLPG and,
> for nested SVM, on every L1 INVLPGA. For INVLPGA specifically, this covers
> the same suspected issue but for nested hypervisors, so it is correct to
> apply the workaround; for INVLPG on shadow paging, instead, the impact
> would be stronger and, due to lack of data, for now the use of INVLPGA is
> left in place in svm_flush_tlb_gva().
>
> Analyzed-by: Vitaly Kuznetsov <vkuznets@redhat.com>
> Analyzed-by: Alexander Lougovski <alougovsk@redhat.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
[..]
> +static void svm_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva, bool *full)
> +{
> + struct vcpu_svm *svm = to_svm(vcpu);
> +
> + /*
> + * INVLPGA has had errata on Genoa and Turin, and even on older
> + * generations there were reports of Windows BSODs if INVLPGA
> + * was used for Hyper-V tlbflush. Use it only for shadow paging
> + * where it seems to be okay.
Is this an actual errata documented by AMD, or is this just an
empirical observation?
I ask because the APM says:
---
The input address is always interpreted as a guest virtual address, so
INVLPGA is typically meaningful only when used with shadow page
tables; it does not provide a means to invalidate a nested translation
by guest physical address
---
While this is terrible wording, it seems like KVM should *not* be
using INVLPGA when TDP is enabled. Looks like
kvm_mmu_invalidate_addr() might be doing the right thing, but it seems
like kvm_hv_vcpu_flush_tlb() shouldn't be calling flush_tlb_gva() with
TDP enabled to begin with, at least on AMD?
I don't have enough context about what kvm_hv_vcpu_flush_tlb() is
doing to know if flush_tlb_gva() makes sense on Intel. But at least on
AMD, looks like it should always just do a full ASID flush (since it
falls back to a full flush with TDP disabled anyway)?
So maybe something like:
int kvm_hv_vcpu_flush_tlb(struct kvm_vcpu *vcpu)
{
...
if (AMD CPU)
goto out_flush_all;
...
}
I also love Sean's idea, I think it's good to harden against this by
nullifying flush_tlb_gva, and maybe add a helper that does the
fallback:
static void kvm_vcpu_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva)
{
if (kvm_x86_ops.flush_tlb_gva)
kvm_x86_call(flush_tlb_gva)(vcpu, addr);
else
kvm_make_request(KVM_REQ_TLB_FLUSH_GUEST, vcpu);
}
Hmm actually we check KVM_REQ_TLB_FLUSH_GUEST before
KVM_REQ_HV_TLB_FLUSH, so maybe just call kvm_vcpu_flush_tlb_guest()
directly for the fallback:
static void kvm_vcpu_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva)
{
if (kvm_x86_ops.flush_tlb_gva)
kvm_x86_call(flush_tlb_gva)(vcpu, addr);
else
kvm_vcpu_flush_tlb_guest(vcpu);
}
And if we go this route, I think we can key off the presence of
flush_tlb_gva in kvm_hv_vcpu_flush_tlb() instead of checking for an
AMD CPU. We can probably break it down into a stable-friendly fix that
just jumps to out_flush_all on AMD CPUs, then the hardening on top.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled
2026-07-24 23:42 ` Yosry Ahmed
@ 2026-07-24 23:50 ` Yosry Ahmed
2026-07-25 13:32 ` Paolo Bonzini
1 sibling, 0 replies; 9+ messages in thread
From: Yosry Ahmed @ 2026-07-24 23:50 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: linux-kernel, kvm, Vitaly Kuznetsov, Alexander Lougovski
> I also love Sean's idea, I think it's good to harden against this by
> nullifying flush_tlb_gva, and maybe add a helper that does the
> fallback:
>
> static void kvm_vcpu_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva)
> {
> if (kvm_x86_ops.flush_tlb_gva)
> kvm_x86_call(flush_tlb_gva)(vcpu, addr);
> else
> kvm_make_request(KVM_REQ_TLB_FLUSH_GUEST, vcpu);
> }
>
> Hmm actually we check KVM_REQ_TLB_FLUSH_GUEST before
> KVM_REQ_HV_TLB_FLUSH, so maybe just call kvm_vcpu_flush_tlb_guest()
> directly for the fallback:
>
> static void kvm_vcpu_flush_tlb_gva(struct kvm_vcpu *vcpu, gva_t gva)
> {
> if (kvm_x86_ops.flush_tlb_gva)
> kvm_x86_call(flush_tlb_gva)(vcpu, addr);
> else
> kvm_vcpu_flush_tlb_guest(vcpu);
> }
>
> And if we go this route, I think we can key off the presence of
> flush_tlb_gva in kvm_hv_vcpu_flush_tlb() instead of checking for an
> AMD CPU. We can probably break it down into a stable-friendly fix that
> just jumps to out_flush_all on AMD CPUs, then the hardening on top.
Although it could be less crud if we just made svm_flush_tlb_gva() do
the fallback. kvm_hv_vcpu_flush_tlb() would need to key-off AMD CPU
though instead of the presence of flush_tlb_gva. Pick your poison, I
guess.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled
2026-07-24 23:42 ` Yosry Ahmed
2026-07-24 23:50 ` Yosry Ahmed
@ 2026-07-25 13:32 ` Paolo Bonzini
2026-07-25 21:33 ` Yosry Ahmed
1 sibling, 1 reply; 9+ messages in thread
From: Paolo Bonzini @ 2026-07-25 13:32 UTC (permalink / raw)
To: Yosry Ahmed
Cc: Kernel Mailing List, Linux, kvm, Vitaly Kuznetsov,
Alexander Lougovski
Il sab 25 lug 2026, 01:44 Yosry Ahmed <yosry@kernel.org> ha scritto:
> > + /*
> > + * INVLPGA has had errata on Genoa and Turin, and even on older
> > + * generations there were reports of Windows BSODs if INVLPGA
> > + * was used for Hyper-V tlbflush. Use it only for shadow paging
> > + * where it seems to be okay.
>
> Is this an actual errata documented by AMD, or is this just an
> empirical observation?
There is the VMware knowledge base in the commit message that requires
new microcode:
> > For Turin, the microcode version was 0x0b002162, which (assuming
> > this is the same issue) should not be affected by the problem listed in
> > https://knowledge.broadcom.com/external/article/419026/bsod-on-virtual-machines-running-on-amd.html;
> > on the other hand that problem should not apply to earlier processors.
> > AMD has not provided any information or analysis yet, and when we asked
> > we didn't know yet that it reproduced on Milan as well.
and I interpreted that as an erratum. But we reproduced it also on
Milan and with supposedly fixed microcode.
> I ask because the APM says:
> ---
> The input address is always interpreted as a guest virtual address, so
> INVLPGA is typically meaningful only when used with shadow page
> tables; it does not provide a means to invalidate a nested translation
> by guest physical address
> ---
>
> While this is terrible wording, it seems like KVM should *not* be
> using INVLPGA when TDP is enabled
While it is certainly an odd case, here the guest has requested to do
an invalidation by GVA on its behalf, so INVLPGA should have worked.
"Not typically meaningful" is a friendly hint to read the manual
twice, but reality seems to be more like "doesn't actually flush the
right entries" when NPT is in use. Especially since Intel has INVVPID
for the exact same operation and it works just fine.
Paolo
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled
2026-07-24 22:36 ` Sean Christopherson
@ 2026-07-25 13:37 ` Paolo Bonzini
0 siblings, 0 replies; 9+ messages in thread
From: Paolo Bonzini @ 2026-07-25 13:37 UTC (permalink / raw)
To: Sean Christopherson
Cc: Kernel Mailing List, Linux, kvm, Vitaly Kuznetsov,
Alexander Lougovski, Yosry Ahmed
Il sab 25 lug 2026, 00:36 Sean Christopherson <seanjc@google.com> ha scritto:
> > diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
> > index b517257a6315..eca04d4b974e 100644
> > --- a/arch/x86/include/asm/kvm_host.h
> > +++ b/arch/x86/include/asm/kvm_host.h
> > @@ -1751,7 +1751,7 @@ struct kvm_x86_ops {
> > * Can potentially get non-canonical addresses through INVLPGs, which
> > * the implementation may choose to ignore if appropriate.
> > */
> > - void (*flush_tlb_gva)(struct kvm_vcpu *vcpu, gva_t addr);
> > + void (*flush_tlb_gva)(struct kvm_vcpu *vcpu, gva_t addr, bool *full);
>
> LOL, why on earth are you using an out-param? If we do this at runtime, just
> return a bool, at least that way we don't have to churn every call-site.
To be precise the churn is only one call site out of two, and in fact
it's the one which is mangled even more by your proposal below. :)
More seriously, I used an out parameter because returning 0/1 or
false/true is impenetrable for the Hyper-V call site, while
0/-EOPNOTSUPP (i.e. do not flush at all on NPT) would force changes in
kvm_mmu_invalidate_addr().
So, instead of making things good for one call site at the expense of
the other, the optional out param is more self documenting in svm.c
and is either good or bearable for the callers: Hyper-V doesn't care
either way (it doesn't e.g. need the return value within an "if" or
"while"), and kvm_mmu_invalidate_addr() just gets an extra NULL
argument.
Overall it's a matter of taste, I understand if you're not convinced
but I can say it wasn't out of a whim. In your defense maybe I should
have put this argument in the commit message somewhere? AI also
complained, but I shelved it because "Sean surely has better taste
than the AI"... :)
> But I would much rather handle this by nuking .flush_tlb_gva at setup, e.g.
>
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index f0144ae8d891..bf16119640dc 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
> @@ -6555,7 +6555,10 @@ void kvm_mmu_invalidate_addr(struct kvm_vcpu *vcpu, struct kvm_mmu *mmu,
> if (is_noncanonical_invlpg_address(addr, vcpu))
> return;
>
> - kvm_x86_call(flush_tlb_gva)(vcpu, addr);
> + if (kvm_x86_ops.flush_tlb_gva)
> + kvm_x86_call(flush_tlb_gva)(vcpu, addr);
> + else
> + kvm_make_request(KVM_REQ_TLB_FLUSH_GUEST, vcpu);
> }
>
> if (!mmu->sync_spte)
This is a variation on the -EOPNOTSUPP convention, for which I didn't
like having this "else" in the caller that doesn't already have a
full-flush fallback. If you insist, I guess I would be fine with
adding a kvm_flush_tlb_gva() wrapper, use it in mmu.c, and check for
NULL in hyperv.c... but in the end is it really better than the out
param version?
In fact I am not even sure it is better than returning -EOPNOTSUPP.
From the svm.c point of view it certainly is very clean, but for
everyone else it's super easy to forget about checking the callback;
and you don't even have __must_check to save you.
Paolo
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled
2026-07-25 13:32 ` Paolo Bonzini
@ 2026-07-25 21:33 ` Yosry Ahmed
0 siblings, 0 replies; 9+ messages in thread
From: Yosry Ahmed @ 2026-07-25 21:33 UTC (permalink / raw)
To: Paolo Bonzini
Cc: Kernel Mailing List, Linux, kvm, Vitaly Kuznetsov,
Alexander Lougovski
On Sat, Jul 25, 2026 at 6:32 AM Paolo Bonzini <pbonzini@redhat.com> wrote:
>
> Il sab 25 lug 2026, 01:44 Yosry Ahmed <yosry@kernel.org> ha scritto:
> > > + /*
> > > + * INVLPGA has had errata on Genoa and Turin, and even on older
> > > + * generations there were reports of Windows BSODs if INVLPGA
> > > + * was used for Hyper-V tlbflush. Use it only for shadow paging
> > > + * where it seems to be okay.
> >
> > Is this an actual errata documented by AMD, or is this just an
> > empirical observation?
>
> There is the VMware knowledge base in the commit message that requires
> new microcode:
>
> > > For Turin, the microcode version was 0x0b002162, which (assuming
> > > this is the same issue) should not be affected by the problem listed in
> > > https://knowledge.broadcom.com/external/article/419026/bsod-on-virtual-machines-running-on-amd.html;
> > > on the other hand that problem should not apply to earlier processors.
> > > AMD has not provided any information or analysis yet, and when we asked
> > > we didn't know yet that it reproduced on Milan as well.
>
> and I interpreted that as an erratum. But we reproduced it also on
> Milan and with supposedly fixed microcode.
>
> > I ask because the APM says:
> > ---
> > The input address is always interpreted as a guest virtual address, so
> > INVLPGA is typically meaningful only when used with shadow page
> > tables; it does not provide a means to invalidate a nested translation
> > by guest physical address
> > ---
> >
> > While this is terrible wording, it seems like KVM should *not* be
> > using INVLPGA when TDP is enabled
>
> While it is certainly an odd case, here the guest has requested to do
> an invalidation by GVA on its behalf, so INVLPGA should have worked.
>
> "Not typically meaningful" is a friendly hint to read the manual
> twice, but reality seems to be more like "doesn't actually flush the
> right entries" when NPT is in use. Especially since Intel has INVVPID
> for the exact same operation and it works just fine.
Yeah I agree that it makes sense that INVLPGA should work if we are
just flushing a GVA on behalf of the guest (e.g. guest making a
hypercall instead of INVLPG).
I think we probably need clarification from AMD about what the
intention is. Is INVLPGA expected to be broken when NPT is enabled (in
which case the wording in the APM needs fixing), or is INVLPGA
expected to work but is actually broken. IIUC the erratum you referred
to should already be fixed with the new microcode, so maybe there's
another problem with INVLPGA?
The third possibility is that INVLPGA works as intended but the
Hyper-V code in KVM is broken in some other way. I am not sure if we
can ask, or if people at Microsoft can answer, but it would definitely
help to know how Hyper-V handles these cases. Does it actually use
INVLPGA to flush GVAs on behalf of the guest with NPT enabeld? If yes,
there's a good chance it's KVM that's broken here?
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled
2026-07-23 9:44 [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled Paolo Bonzini
2026-07-24 22:36 ` Sean Christopherson
2026-07-24 23:42 ` Yosry Ahmed
@ 2026-07-27 19:23 ` Tycho Andersen
2026-07-27 19:58 ` Yosry Ahmed
2 siblings, 1 reply; 9+ messages in thread
From: Tycho Andersen @ 2026-07-27 19:23 UTC (permalink / raw)
To: Paolo Bonzini
Cc: linux-kernel, kvm, Vitaly Kuznetsov, Alexander Lougovski,
Tom Lendacky, Borislav Petkov
Hi all,
On Thu, Jul 23, 2026 at 11:44:19AM +0200, Paolo Bonzini wrote:
> As to the workload, Alexander threw more or less everything at the same
> time at the VM:
>
> - a full Windows Defender scan every 30 minutes
>
> - a disk I/O job
>
> - a loop doing repeated mmap of system files (mostly to hope that
> it triggers some consistency check in the Windows memory manager)
>
> - SQL Express 2022 + StressDB (1.6M rows), with the host doing queries
> (75% write/25% read) via sqlcmd
Can you share the code for the mmap + disk io + DB content generator?
We're looking at this and would like to reproduce.
Thanks,
Tycho
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled
2026-07-27 19:23 ` Tycho Andersen
@ 2026-07-27 19:58 ` Yosry Ahmed
0 siblings, 0 replies; 9+ messages in thread
From: Yosry Ahmed @ 2026-07-27 19:58 UTC (permalink / raw)
To: Tycho Andersen, Paolo Bonzini
Cc: linux-kernel, kvm, Vitaly Kuznetsov, Alexander Lougovski,
Tom Lendacky, Borislav Petkov, Sean Christopherson
On Mon, Jul 27, 2026 at 12:23 PM Tycho Andersen <tycho@kernel.org> wrote:
>
> Hi all,
>
> On Thu, Jul 23, 2026 at 11:44:19AM +0200, Paolo Bonzini wrote:
> > As to the workload, Alexander threw more or less everything at the same
> > time at the VM:
> >
> > - a full Windows Defender scan every 30 minutes
> >
> > - a disk I/O job
> >
> > - a loop doing repeated mmap of system files (mostly to hope that
> > it triggers some consistency check in the Windows memory manager)
> >
> > - SQL Express 2022 + StressDB (1.6M rows), with the host doing queries
> > (75% write/25% read) via sqlcmd
>
> Can you share the code for the mmap + disk io + DB content generator?
>
> We're looking at this and would like to reproduce.
Not sure if this is relevant, but I see that hyperv_tlb_flush is flaky
with npt=0 on Turin at the tip of kvm-x86/next (commit 567329869b9c7).
It's not likely because the problem fixed by this patch happens
specifically with npt=1 and not npt=0 AFAICT, but it is a weird
coincidence :)
Maybe something is wrong in the Hyper-V code that manifests
differently with npt=0/1?
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-07-27 19:59 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23 9:44 [PATCH] KVM: SVM: make svm_flush_tlb_gva do a full asid flush if NPT enabled Paolo Bonzini
2026-07-24 22:36 ` Sean Christopherson
2026-07-25 13:37 ` Paolo Bonzini
2026-07-24 23:42 ` Yosry Ahmed
2026-07-24 23:50 ` Yosry Ahmed
2026-07-25 13:32 ` Paolo Bonzini
2026-07-25 21:33 ` Yosry Ahmed
2026-07-27 19:23 ` Tycho Andersen
2026-07-27 19:58 ` Yosry Ahmed
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.