* [PATCH v3 01/15] KVM: Reject user accesses to guest memory if current->mm != kvm->mm
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
@ 2026-10-27 22:11 ` Sean Christopherson
2026-10-27 22:11 ` [PATCH v3 02/15] KVM: nVMX: Don't force a full nested VM-Exit when destroying a vCPU Sean Christopherson
` (13 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:11 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Reject user accesses to guest memory, which are supposed to be done only
in the context of KVM_RUN or similar operations, if the current address
space is not the VM's (host userspace) address space. If KVM writes to
guest memory after the owning host process has exited, or if the VM is
being destroyed in the context of a different process, then writing using
the wrong address space will corrupt a different process' memory.
Reject the access but don't WARN() or KVM_BUG_ON() event though attempting
to access guest memory with a mismatched address space is a blatant KVM
bug, because unfortunately KVM is buggy. On KVM VMX, when a vCPU is
destroyed while L2 is active, KVM synthesizes a nested VM-Exit to force the
vCPU out of L2 in order to free the nested VMX assets, and a side effect of
a nested VM-Exit is that it flushes the cached shadow VMCS12 back to guest
memory:
vmx_vcpu_free()
|-> nested_vmx_free_vcpu()
|-> vmx_leave_nested()
|-> nested_vmx_vmexit(vcpu, -1, 0, 0)
|-> nested_flush_cached_shadow_vmcs12()
|-> kvm_write_guest_cached()
|-> __copy_to_user(ghc->hva, ...)
Fix the bug broadly even though the "real" bug is that KVM abuses the
nested VM-Exit flow for non-architectural purposes, as there may be other
such violations lurking. For now, punt on fixing individual bugs and
hardening the common flows, e.g. with WARNs.
Opportunistically provide wrappers in anticipation of adding more checks
and hardening, i.e. growing the logic beyond checking current->mm.
Fixes: 61ada7488ffd ("KVM: nVMX: Cache shadow vmcs12 on VMEntry and flush to memory on VMExit")
Cc: stable@vger.kernel.org
Reported-by: Jim Mattson <jmattson@google.com>
Closes: https://lore.kernel.org/all/20260908132838.2116068-1-jmattson@google.com
Reviewed-by: James Houghton <jthoughton@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/vmx/sgx.c | 2 +-
arch/x86/kvm/vmx/vmx.c | 8 ++++----
include/linux/kvm_host.h | 25 +++++++++++++++++++++++--
virt/kvm/kvm_main.c | 24 ++++++++++++------------
4 files changed, 40 insertions(+), 19 deletions(-)
diff --git a/arch/x86/kvm/vmx/sgx.c b/arch/x86/kvm/vmx/sgx.c
index 771c75a58343..52a6d0f8bb9e 100644
--- a/arch/x86/kvm/vmx/sgx.c
+++ b/arch/x86/kvm/vmx/sgx.c
@@ -64,7 +64,7 @@ static void sgx_handle_emulation_failure(struct kvm_vcpu *vcpu, u64 addr,
static int sgx_read_hva(struct kvm_vcpu *vcpu, unsigned long hva, void *data,
unsigned int size)
{
- if (__copy_from_user(data, (void __user *)hva, size)) {
+ if (kvm_copy_from_user(vcpu->kvm, data, (void __user *)hva, size)) {
sgx_handle_emulation_failure(vcpu, hva, size);
return -EFAULT;
}
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index b341af4c6057..d433cd846ac4 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -4021,16 +4021,16 @@ static int init_rmode_tss(struct kvm *kvm, void __user *ua)
int i;
for (i = 0; i < 3; i++) {
- if (__copy_to_user(ua + PAGE_SIZE * i, zero_page, PAGE_SIZE))
+ if (kvm_copy_to_user(kvm, ua + PAGE_SIZE * i, zero_page, PAGE_SIZE))
return -EFAULT;
}
data = TSS_BASE_SIZE + TSS_REDIRECTION_SIZE;
- if (__copy_to_user(ua + TSS_IOPB_BASE_OFFSET, &data, sizeof(u16)))
+ if (kvm_copy_to_user(kvm, ua + TSS_IOPB_BASE_OFFSET, &data, sizeof(u16)))
return -EFAULT;
data = ~0;
- if (__copy_to_user(ua + RMODE_TSS_SIZE - 1, &data, sizeof(u8)))
+ if (kvm_copy_to_user(kvm, ua + RMODE_TSS_SIZE - 1, &data, sizeof(u8)))
return -EFAULT;
return 0;
@@ -4065,7 +4065,7 @@ static int init_rmode_identity_map(struct kvm *kvm)
for (i = 0; i < (PAGE_SIZE / sizeof(tmp)); i++) {
tmp = (i << 22) + (_PAGE_PRESENT | _PAGE_RW | _PAGE_USER |
_PAGE_ACCESSED | _PAGE_DIRTY | _PAGE_PSE);
- if (__copy_to_user(uaddr + i * sizeof(tmp), &tmp, sizeof(tmp))) {
+ if (kvm_copy_to_user(kvm, uaddr + i * sizeof(tmp), &tmp, sizeof(tmp))) {
r = -EFAULT;
goto out;
}
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index e7fc94e2910a..be27b4e8f12d 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1353,13 +1353,34 @@ int kvm_write_guest_offset_cached(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
int kvm_gfn_to_hva_cache_init(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
gpa_t gpa, unsigned long len);
+static __always_inline __must_check bool kvm_can_do_uaccess(struct kvm *kvm)
+{
+ return current->mm == kvm->mm;
+}
+
+#define BUILD_KVM_COPY_USER_WRAPPER(fn, to_user, from_user) \
+static __always_inline __must_check unsigned long kvm_##fn(struct kvm *kvm, \
+ void to_user *to, \
+ const void from_user *from, \
+ unsigned long n) \
+{ \
+ if (!kvm_can_do_uaccess(kvm)) \
+ return n; \
+ \
+ return __##fn(to, from, n); \
+}
+BUILD_KVM_COPY_USER_WRAPPER(copy_from_user, , __user)
+BUILD_KVM_COPY_USER_WRAPPER(copy_from_user_inatomic, , __user)
+BUILD_KVM_COPY_USER_WRAPPER(copy_to_user, __user, )
+BUILD_KVM_COPY_USER_WRAPPER(copy_to_user_inatomic, __user, )
+
#define __kvm_get_guest(kvm, gfn, offset, v) \
({ \
unsigned long __addr = gfn_to_hva(kvm, gfn); \
typeof(v) __user *__uaddr = (typeof(__uaddr))(__addr + offset); \
int __ret = -EFAULT; \
\
- if (!kvm_is_error_hva(__addr)) \
+ if (!kvm_is_error_hva(__addr) && kvm_can_do_uaccess(kvm)) \
__ret = get_user(v, __uaddr); \
__ret; \
})
@@ -1379,7 +1400,7 @@ int kvm_gfn_to_hva_cache_init(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
typeof(v) __user *__uaddr = (typeof(__uaddr))(__addr + offset); \
int __ret = -EFAULT; \
\
- if (!kvm_is_error_hva(__addr)) \
+ if (!kvm_is_error_hva(__addr) && kvm_can_do_uaccess(kvm)) \
__ret = put_user(v, __uaddr); \
if (!__ret) \
mark_page_dirty(kvm, gfn); \
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index cf4cb586f140..218456372b11 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -3279,8 +3279,8 @@ static int next_segment(unsigned long len, int offset)
}
/* Copy @len bytes from guest memory at '(@gfn * PAGE_SIZE) + @offset' to @data */
-static int __kvm_read_guest_page(struct kvm_memory_slot *slot, gfn_t gfn,
- void *data, int offset, int len)
+static int __kvm_read_guest_page(struct kvm *kvm, struct kvm_memory_slot *slot,
+ gfn_t gfn, void *data, int offset, int len)
{
int r;
unsigned long addr;
@@ -3291,7 +3291,7 @@ static int __kvm_read_guest_page(struct kvm_memory_slot *slot, gfn_t gfn,
addr = gfn_to_hva_memslot_prot(slot, gfn, NULL);
if (kvm_is_error_hva(addr))
return -EFAULT;
- r = __copy_from_user(data, (void __user *)addr + offset, len);
+ r = kvm_copy_from_user(kvm, data, (void __user *)addr + offset, len);
if (r)
return -EFAULT;
return 0;
@@ -3302,7 +3302,7 @@ int kvm_read_guest_page(struct kvm *kvm, gfn_t gfn, void *data, int offset,
{
struct kvm_memory_slot *slot = gfn_to_memslot(kvm, gfn);
- return __kvm_read_guest_page(slot, gfn, data, offset, len);
+ return __kvm_read_guest_page(kvm, slot, gfn, data, offset, len);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_read_guest_page);
@@ -3311,7 +3311,7 @@ int kvm_vcpu_read_guest_page(struct kvm_vcpu *vcpu, gfn_t gfn, void *data,
{
struct kvm_memory_slot *slot = kvm_vcpu_gfn_to_memslot(vcpu, gfn);
- return __kvm_read_guest_page(slot, gfn, data, offset, len);
+ return __kvm_read_guest_page(vcpu->kvm, slot, gfn, data, offset, len);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_vcpu_read_guest_page);
@@ -3355,8 +3355,8 @@ int kvm_vcpu_read_guest(struct kvm_vcpu *vcpu, gpa_t gpa, void *data, unsigned l
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_vcpu_read_guest);
-static int __kvm_read_guest_atomic(struct kvm_memory_slot *slot, gfn_t gfn,
- void *data, int offset, unsigned long len)
+static int __kvm_read_guest_atomic(struct kvm *kvm, struct kvm_memory_slot *slot,
+ gfn_t gfn, void *data, int offset, unsigned long len)
{
int r;
unsigned long addr;
@@ -3368,7 +3368,7 @@ static int __kvm_read_guest_atomic(struct kvm_memory_slot *slot, gfn_t gfn,
if (kvm_is_error_hva(addr))
return -EFAULT;
pagefault_disable();
- r = __copy_from_user_inatomic(data, (void __user *)addr + offset, len);
+ r = kvm_copy_from_user_inatomic(kvm, data, (void __user *)addr + offset, len);
pagefault_enable();
if (r)
return -EFAULT;
@@ -3382,7 +3382,7 @@ int kvm_vcpu_read_guest_atomic(struct kvm_vcpu *vcpu, gpa_t gpa,
struct kvm_memory_slot *slot = kvm_vcpu_gfn_to_memslot(vcpu, gfn);
int offset = offset_in_page(gpa);
- return __kvm_read_guest_atomic(slot, gfn, data, offset, len);
+ return __kvm_read_guest_atomic(vcpu->kvm, slot, gfn, data, offset, len);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_vcpu_read_guest_atomic);
@@ -3400,7 +3400,7 @@ static int __kvm_write_guest_page(struct kvm *kvm,
addr = gfn_to_hva_memslot(memslot, gfn);
if (kvm_is_error_hva(addr))
return -EFAULT;
- r = __copy_to_user((void __user *)addr + offset, data, len);
+ r = kvm_copy_to_user(kvm, (void __user *)addr + offset, data, len);
if (r)
return -EFAULT;
mark_page_dirty_in_slot(kvm, memslot, gfn);
@@ -3538,7 +3538,7 @@ int kvm_write_guest_offset_cached(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
if (unlikely(!ghc->memslot))
return kvm_write_guest(kvm, gpa, data, len);
- r = __copy_to_user((void __user *)ghc->hva + offset, data, len);
+ r = kvm_copy_to_user(kvm, (void __user *)ghc->hva + offset, data, len);
if (r)
return -EFAULT;
mark_page_dirty_in_slot(kvm, ghc->memslot, gpa >> PAGE_SHIFT);
@@ -3576,7 +3576,7 @@ int kvm_read_guest_offset_cached(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
if (unlikely(!ghc->memslot))
return kvm_read_guest(kvm, gpa, data, len);
- r = __copy_from_user(data, (void __user *)ghc->hva + offset, len);
+ r = kvm_copy_from_user(kvm, data, (void __user *)ghc->hva + offset, len);
if (r)
return -EFAULT;
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH v3 02/15] KVM: nVMX: Don't force a full nested VM-Exit when destroying a vCPU
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
2026-10-27 22:11 ` [PATCH v3 01/15] KVM: Reject user accesses to guest memory if current->mm != kvm->mm Sean Christopherson
@ 2026-10-27 22:11 ` Sean Christopherson
2026-10-27 22:54 ` Sean Christopherson
2026-10-27 22:11 ` [PATCH v3 03/15] KVM: PPC: Flush/zap all memslots on kvm_arch_flush_shadow_all() Sean Christopherson
` (12 subsequent siblings)
14 siblings, 1 reply; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:11 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
For all intents and purposes, revert commit b4b65b5642d6 ("KVM: x86: cleanup
freeing of nested state") and go back to manually tearing down nested state
when freeing a vCPU.
When a vCPU is destroyed while L2 is active, KVM synthesizes a nested
VM-Exit, which flushes the cached shadow VMCS12 back to guest memory:
vmx_vcpu_free()
|-> nested_vmx_free_vcpu()
|-> vmx_leave_nested()
|-> nested_vmx_vmexit(vcpu, -1, 0, 0)
|-> nested_flush_cached_shadow_vmcs12()
|-> kvm_write_guest_cached()
|-> __copy_to_user(ghc->hva, ...)
Accessing user memory via a memslot during VM destruction is broken, as
there are no guarantees that current->mm == kvm->mm when the VM is dying,
because the last reference to the VM can be put from a different process
than the original creating processes.
And even if the original process does put the final reference, during
process exit, do_exit() calls exit_mm() before closing file descriptors, so
vCPU destruction runs with current->mm == NULL on a borrowed lazy TLB
active_mm. If the borrowed address space has a writable mapping at the
to-be-written userspace address, KVM will corrupt an unrelated task's
memory since uaccess APIs, including __copy_to_user(), don't sanity check
current->mm (and *can't* sanity perform KVM's current->mm == kvm->mm check
since that is firmly a KVM-only concept).
Furthermore, the shadow VMCS flaw is only the tip of the iceberg, as the
MSR load/store lists mean that emulating a nested VM-Exit can read or write
an arbitrary set of MSRs, e.g. if MSR_KVM_SYSTEM_TIME_NEW is in L1's MSR
load list:
kvm_arch_vcpu_destroy()
kvmclock_reset() - takes pv_time off kvm->gpc_list
vmx_vcpu_free()
nested_vmx_free_vcpu()
vmx_leave_nested()
nested_vmx_vmexit()
__nested_vmx_vmexit()
load_vmcs12_host_state()
nested_vmx_load_msr() - writes the MSRs that L1 listed
kvm_cpu_has_injectable_intr() - reads Xen's vcpu_info
kvm_xen_destroy_vcpu() - deactivates the vcpu_info cache
processing the MSR write puts the "pv_time" cache back on kvm->gpc_list,
and the next list_del() writes into the freed vCPU. And as effectively
pointed out by syzbot[*], attempting to fix each case individually is a
losing game of whack-a-mole.
Deliberately leave "nested_run_pending" as-is, because even though doing so
creates potentitally inconsistent state, KVM should *never* consume said
state while freeing a vCPU. I.e. if leaving nested_run_pending set trips a
sanity check or bug, then that just means there's more fixing to be done.
Open code only the "free vCPU" path, i.e. keep emulating a nested VM-Exit
when forcing a vCPU out of L2 via vmx_leave_nested(), as there are a number
of updates buried in the nested VM-Exit flow that are needed if the vCPU is
going to run in L1, e.g. clearing pending exceptions and MTF traps, setting
the TSC offset+scale information back to L1, and so on and so forth. Note,
this means svm_leave_nested() is all but guaranteed to be buggy, but that's
basically an open secret at this point and at least any bugs "only" break
L1, i.e. don't corrupt host state as is the case for abusing nested VM-Exit
when freeing a vCPU.
Link: https://lore.kernel.org/all/6ac0145f.34119e79.2f92f3.0003.GAE@google.com [*]
Fixes: b4b65b5642d6 ("KVM: x86: cleanup freeing of nested state")
Cc: stable@vger.kernel.org
Co-developed-by: Chris Mason <mason@kernel.org>
Signed-off-by: Chris Mason <mason@kernel.org>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/vmx/nested.c | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 9b0bfa2f854c..49c4529901c5 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -385,8 +385,15 @@ static void free_nested(struct kvm_vcpu *vcpu)
*/
void nested_vmx_free_vcpu(struct kvm_vcpu *vcpu)
{
+ struct vcpu_vmx *vmx = to_vmx(vcpu);
+
vcpu_load(vcpu);
- vmx_leave_nested(vcpu);
+ if (is_guest_mode(vcpu)) {
+ hrtimer_cancel(&vmx->nested.preemption_timer);
+ leave_guest_mode(vcpu);
+ vmx_switch_vmcs(vcpu, &vmx->vmcs01);
+ }
+ free_nested(vcpu);
vcpu_put(vcpu);
}
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v3 02/15] KVM: nVMX: Don't force a full nested VM-Exit when destroying a vCPU
2026-10-27 22:11 ` [PATCH v3 02/15] KVM: nVMX: Don't force a full nested VM-Exit when destroying a vCPU Sean Christopherson
@ 2026-10-27 22:54 ` Sean Christopherson
0 siblings, 0 replies; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:54 UTC (permalink / raw)
To: Madhavan Srinivasan, Paolo Bonzini, Nicholas Piggin,
linuxppc-dev, kvm, linux-kernel, Jim Mattson, James Houghton,
Chris Mason
Cc: Hyunwoo Kim
+Hyunwoo
On Tue, Oct 27, 2026, Sean Christopherson wrote:
> For all intents and purposes, revert commit b4b65b5642d6 ("KVM: x86: cleanup
> freeing of nested state") and go back to manually tearing down nested state
> when freeing a vCPU.
>
> When a vCPU is destroyed while L2 is active, KVM synthesizes a nested
> VM-Exit, which flushes the cached shadow VMCS12 back to guest memory:
>
> vmx_vcpu_free()
> |-> nested_vmx_free_vcpu()
> |-> vmx_leave_nested()
> |-> nested_vmx_vmexit(vcpu, -1, 0, 0)
> |-> nested_flush_cached_shadow_vmcs12()
> |-> kvm_write_guest_cached()
> |-> __copy_to_user(ghc->hva, ...)
>
> Accessing user memory via a memslot during VM destruction is broken, as
> there are no guarantees that current->mm == kvm->mm when the VM is dying,
> because the last reference to the VM can be put from a different process
> than the original creating processes.
>
> And even if the original process does put the final reference, during
> process exit, do_exit() calls exit_mm() before closing file descriptors, so
> vCPU destruction runs with current->mm == NULL on a borrowed lazy TLB
> active_mm. If the borrowed address space has a writable mapping at the
> to-be-written userspace address, KVM will corrupt an unrelated task's
> memory since uaccess APIs, including __copy_to_user(), don't sanity check
> current->mm (and *can't* sanity perform KVM's current->mm == kvm->mm check
> since that is firmly a KVM-only concept).
>
> Furthermore, the shadow VMCS flaw is only the tip of the iceberg, as the
> MSR load/store lists mean that emulating a nested VM-Exit can read or write
> an arbitrary set of MSRs, e.g. if MSR_KVM_SYSTEM_TIME_NEW is in L1's MSR
> load list:
>
> kvm_arch_vcpu_destroy()
> kvmclock_reset() - takes pv_time off kvm->gpc_list
> vmx_vcpu_free()
> nested_vmx_free_vcpu()
> vmx_leave_nested()
> nested_vmx_vmexit()
> __nested_vmx_vmexit()
> load_vmcs12_host_state()
> nested_vmx_load_msr() - writes the MSRs that L1 listed
> kvm_cpu_has_injectable_intr() - reads Xen's vcpu_info
> kvm_xen_destroy_vcpu() - deactivates the vcpu_info cache
>
> processing the MSR write puts the "pv_time" cache back on kvm->gpc_list,
> and the next list_del() writes into the freed vCPU. And as effectively
> pointed out by syzbot[*], attempting to fix each case individually is a
> losing game of whack-a-mole.
>
> Deliberately leave "nested_run_pending" as-is, because even though doing so
> creates potentitally inconsistent state, KVM should *never* consume said
> state while freeing a vCPU. I.e. if leaving nested_run_pending set trips a
> sanity check or bug, then that just means there's more fixing to be done.
>
> Open code only the "free vCPU" path, i.e. keep emulating a nested VM-Exit
> when forcing a vCPU out of L2 via vmx_leave_nested(), as there are a number
> of updates buried in the nested VM-Exit flow that are needed if the vCPU is
> going to run in L1, e.g. clearing pending exceptions and MTF traps, setting
> the TSC offset+scale information back to L1, and so on and so forth. Note,
> this means svm_leave_nested() is all but guaranteed to be buggy, but that's
> basically an open secret at this point and at least any bugs "only" break
> L1, i.e. don't corrupt host state as is the case for abusing nested VM-Exit
> when freeing a vCPU.
>
> Link: https://lore.kernel.org/all/6ac0145f.34119e79.2f92f3.0003.GAE@google.com [*]
> Fixes: b4b65b5642d6 ("KVM: x86: cleanup freeing of nested state")
> Cc: stable@vger.kernel.org
I completely forgot that Hyunwoo also reported this, so this also needs:
Reported-by: Hyunwoo Kim <imv4bel@gmail.com>
Closes: https://lore.kernel.org/all/am3Bs0DjZ5kay9NJ@v4bel
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3 03/15] KVM: PPC: Flush/zap all memslots on kvm_arch_flush_shadow_all()
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
2026-10-27 22:11 ` [PATCH v3 01/15] KVM: Reject user accesses to guest memory if current->mm != kvm->mm Sean Christopherson
2026-10-27 22:11 ` [PATCH v3 02/15] KVM: nVMX: Don't force a full nested VM-Exit when destroying a vCPU Sean Christopherson
@ 2026-10-27 22:11 ` Sean Christopherson
2026-10-27 22:11 ` [PATCH v3 04/15] KVM: x86: Unmap VMAs for KVM-internal memslots when the memslot is freed Sean Christopherson
` (11 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:11 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Flush/zap every memslot in kvm_arch_flush_shadow_all(), i.e. when the
owning userspace process is exiting, as KVM must ensure all associated
memory is unmapped from the guest.
Cc: stable@vger.kernel.org
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/powerpc/include/asm/kvm_host.h | 1 -
arch/powerpc/kvm/powerpc.c | 11 +++++++++++
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/arch/powerpc/include/asm/kvm_host.h b/arch/powerpc/include/asm/kvm_host.h
index 708a32f89776..50a42f80e5f1 100644
--- a/arch/powerpc/include/asm/kvm_host.h
+++ b/arch/powerpc/include/asm/kvm_host.h
@@ -910,7 +910,6 @@ struct kvm_vcpu_arch {
#define __KVM_HAVE_CREATE_DEVICE
static inline void kvm_arch_memslots_updated(struct kvm *kvm, u64 gen) {}
-static inline void kvm_arch_flush_shadow_all(struct kvm *kvm) {}
static inline void kvm_arch_vcpu_blocking(struct kvm_vcpu *vcpu) {}
static inline void kvm_arch_vcpu_unblocking(struct kvm_vcpu *vcpu) {}
diff --git a/arch/powerpc/kvm/powerpc.c b/arch/powerpc/kvm/powerpc.c
index 9194cf492d1c..9e9d0865c2f6 100644
--- a/arch/powerpc/kvm/powerpc.c
+++ b/arch/powerpc/kvm/powerpc.c
@@ -764,6 +764,17 @@ void kvm_arch_commit_memory_region(struct kvm *kvm,
kvmppc_core_commit_memory_region(kvm, old, new, change);
}
+void kvm_arch_flush_shadow_all(struct kvm *kvm)
+{
+ struct kvm_memory_slot *slot;
+ struct kvm_memslots *slots;
+ int bkt;
+
+ slots = kvm_memslots(kvm);
+ kvm_for_each_memslot(slot, bkt, slots)
+ kvmppc_core_flush_memslot(kvm, slot);
+}
+
void kvm_arch_flush_shadow_memslot(struct kvm *kvm,
struct kvm_memory_slot *slot)
{
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH v3 04/15] KVM: x86: Unmap VMAs for KVM-internal memslots when the memslot is freed
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (2 preceding siblings ...)
2026-10-27 22:11 ` [PATCH v3 03/15] KVM: PPC: Flush/zap all memslots on kvm_arch_flush_shadow_all() Sean Christopherson
@ 2026-10-27 22:11 ` Sean Christopherson
2026-10-27 22:11 ` [PATCH v3 05/15] KVM: Disallow setting memslots when the VM is being destroyed Sean Christopherson
` (10 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:11 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Unmap KVM-created VMAs for KVM-internal memslots when the associated
memslot is freed instead of unmapping via x86's internal API. This will
allow deleting/freeing all memslots before kvm_arch_destroy_vm() without
leaking VMAs, and in general is a net positive as it makes it far less
likely that KVM will leak VMAs, e.g. in the unlikely scenario some other
flow deletes KVM-internal memslots without going through
__x86_set_memory_region().
Reviewed-by: James Houghton <jthoughton@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 38 ++++++++++++++++----------------------
1 file changed, 16 insertions(+), 22 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 32563a91fba9..0f3fa4571309 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -10018,7 +10018,7 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
* @size > 0 to install a new slot, while @size == 0 to uninstall a
* slot. The return code can be one of the following:
*
- * HVA: on success (uninstall will return a bogus HVA)
+ * HVA: on success (uninstall will return a NULL HVA)
* -errno: on error
*
* The caller should always use IS_ERR() to check the return value
@@ -10031,10 +10031,10 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
u32 size)
{
- int i, r;
- unsigned long hva, old_npages;
struct kvm_memslots *slots = kvm_memslots(kvm);
struct kvm_memory_slot *slot;
+ unsigned long hva;
+ int i, r;
lockdep_assert_held(&kvm->slots_lock);
@@ -10058,8 +10058,7 @@ void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
if (!slot || !slot->npages)
return NULL;
- old_npages = slot->npages;
- hva = slot->userspace_addr;
+ hva = 0;
}
for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) {
@@ -10075,9 +10074,6 @@ void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
return ERR_PTR_USR(r);
}
- if (!size)
- vm_munmap(hva, old_npages * PAGE_SIZE);
-
return (void __user *)hva;
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(__x86_set_memory_region);
@@ -10106,20 +10102,6 @@ void kvm_arch_pre_destroy_vm(struct kvm *kvm)
void kvm_arch_destroy_vm(struct kvm *kvm)
{
- if (current->mm == kvm->mm) {
- /*
- * Free memory regions allocated on behalf of userspace,
- * unless the memory map has changed due to process exit
- * or fd copying.
- */
- mutex_lock(&kvm->slots_lock);
- __x86_set_memory_region(kvm, APIC_ACCESS_PAGE_PRIVATE_MEMSLOT,
- 0, 0);
- __x86_set_memory_region(kvm, IDENTITY_PAGETABLE_PRIVATE_MEMSLOT,
- 0, 0);
- __x86_set_memory_region(kvm, TSS_PRIVATE_MEMSLOT, 0, 0);
- mutex_unlock(&kvm->slots_lock);
- }
if (kvm->arch.created_mediated_pmu)
perf_release_mediated_pmu();
kvm_destroy_vcpus(kvm);
@@ -10159,6 +10141,18 @@ void kvm_arch_free_memslot(struct kvm *kvm, struct kvm_memory_slot *slot)
}
kvm_page_track_free_memslot(slot);
+
+ /*
+ * Unmap VMAs allocated on behalf of userspace for internal memslots,
+ * unless the memory map has changed due to process exit or fd copying.
+ * Do the unmap only for the non-SMM slot, as the same VMA is used for
+ * both non-SMM and SMM memslots. Leak the mapping on failure, e.g.
+ * if the task is killed. Worst case scenario, the page(s) will be
+ * reclaimed when the process exits.
+ */
+ if (current->mm == kvm->mm && slot->id >= KVM_USER_MEM_SLOTS &&
+ !slot->as_id && !WARN_ON_ONCE(!slot->npages))
+ vm_munmap(slot->userspace_addr, slot->npages * PAGE_SIZE);
}
int memslot_rmap_alloc(struct kvm_memory_slot *slot, unsigned long npages)
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH v3 05/15] KVM: Disallow setting memslots when the VM is being destroyed
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (3 preceding siblings ...)
2026-10-27 22:11 ` [PATCH v3 04/15] KVM: x86: Unmap VMAs for KVM-internal memslots when the memslot is freed Sean Christopherson
@ 2026-10-27 22:11 ` Sean Christopherson
2026-10-27 22:11 ` [PATCH v3 06/15] KVM: Destroy memslots immediately after mmu_notifiers are unregistered Sean Christopherson
` (9 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:11 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Now that KVM doesn't delete KVM-internal memslots as an unnecessary side
effect during VM destruction, WARN and reject any attempt to set memslots
after the VM's refcount has hit 0. There's obviously no need to CREATE,
MOVE, or do a FLAGS_ONLY update when a VM is being destroyed, and there
should be no reason for arch code to manually DELETE a memslot: once KVM
KVM unregisters its mmu_notifier and does the final kvm_flush_shadow_all(),
there absolutely must not be any outstanding references to memslots. I.e.
if arch code "needs" to manually DELETE a memslot, then it's already buggy.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
virt/kvm/kvm_main.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 218456372b11..e7c89861632a 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -2041,6 +2041,9 @@ static int kvm_set_memory_region(struct kvm *kvm,
lockdep_assert_held(&kvm->slots_lock);
+ if (WARN_ON_ONCE(!refcount_read(&kvm->users_count)))
+ return -EIO;
+
r = check_memory_region_flags(kvm, mem);
if (r)
return r;
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH v3 06/15] KVM: Destroy memslots immediately after mmu_notifiers are unregistered
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (4 preceding siblings ...)
2026-10-27 22:11 ` [PATCH v3 05/15] KVM: Disallow setting memslots when the VM is being destroyed Sean Christopherson
@ 2026-10-27 22:11 ` Sean Christopherson
2026-10-27 22:12 ` [PATCH v3 07/15] KVM: WARN if KVM attempts to do guest-related uaccess with "wrong" process Sean Christopherson
` (8 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:11 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Instally dummy, empty memslots immediately after unregistering KVM's
mmu_notifier during VM destruction to harden against accessing memory from
the wrong address space when tearing down a VM. Because kvm_destroy_vm()
often runs when the associated VM's file is being released, current->mm is
often no longer kvm->mm, i.e. using the memslots to access userspace memory
is inherently broken/dangerous.
While KVM's APIs to read/write guest memory explicitly reject accesses if
current->mm != kvm->mm, taking away the memslots adds another layer of
defense and helps guard against rogue accesses that don't go through KVM's
standard API, or that do GUP+kmap().
Note, simply hoisting memslot destruction above kvm_arch_destroy_vm()
without installing dummy slots is not a viable alternative. Doing so would
require auditing all the paths of kvm_arch_destroy_vm(), which is nearly
infeasible, and missing even one case would result in a NULL pointer
dereference and/or use-after-free, neither of which is a substantially
better outcome than the status quo.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
virt/kvm/kvm_main.c | 45 +++++++++++++++++++++++++++++++--------------
1 file changed, 31 insertions(+), 14 deletions(-)
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index e7c89861632a..1058b864d427 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -963,23 +963,42 @@ static void kvm_free_memslot(struct kvm *kvm, struct kvm_memory_slot *slot)
kfree(slot);
}
-static void kvm_free_memslots(struct kvm *kvm, struct kvm_memslots *slots)
+static const struct kvm_memslots kvm_empty_memslots = {
+ .generation = -1ull,
+ .hva_tree = RB_ROOT_CACHED,
+ .gfn_tree = RB_ROOT,
+ .id_hash[0 ... (ARRAY_SIZE(kvm_empty_memslots.id_hash) - 1)] = HLIST_HEAD_INIT,
+ .node_idx = 0,
+};
+
+static void kvm_destroy_memslots(struct kvm *kvm)
{
struct hlist_node *idnode;
struct kvm_memory_slot *memslot;
- int bkt;
+ int bkt, i;
+
+ /*
+ * Install empty memslots to guard against memslot lookups while the VM
+ * is being destroyed. Consuming memslots at this stage is a KVM bug,
+ * but "gracefully do nothing" is a much better outcome than "crash the
+ * host" or "corrupt random memory" when there inevitably is a bug.
+ */
+ mutex_lock(&kvm->slots_lock);
+ for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++)
+ rcu_assign_pointer(kvm->memslots[i], &kvm_empty_memslots);
+
+ synchronize_srcu_expedited(&kvm->srcu);
+ mutex_unlock(&kvm->slots_lock);
/*
* The same memslot objects live in both active and inactive sets,
- * arbitrarily free using index '1' so the second invocation of this
- * function isn't operating over a structure with dangling pointers
- * (even though this function isn't actually touching them).
+ * arbitrarily free using index '1'.
*/
- if (!slots->node_idx)
- return;
-
- hash_for_each_safe(slots->id_hash, bkt, idnode, memslot, id_node[1])
- kvm_free_memslot(kvm, memslot);
+ for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) {
+ hash_for_each_safe(kvm->__memslots[i][1].id_hash, bkt, idnode,
+ memslot, id_node[1])
+ kvm_free_memslot(kvm, memslot);
+ }
}
static umode_t kvm_stats_debugfs_mode(const struct kvm_stats_desc *desc)
@@ -1310,12 +1329,10 @@ static void kvm_destroy_vm(struct kvm *kvm)
kvm->mn_active_invalidate_count = 0;
else
WARN_ON(kvm->mmu_invalidate_in_progress);
+ kvm_destroy_memslots(kvm);
+
kvm_arch_destroy_vm(kvm);
kvm_destroy_devices(kvm);
- for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) {
- kvm_free_memslots(kvm, &kvm->__memslots[i][0]);
- kvm_free_memslots(kvm, &kvm->__memslots[i][1]);
- }
cleanup_srcu_struct(&kvm->irq_srcu);
srcu_barrier(&kvm->srcu);
cleanup_srcu_struct(&kvm->srcu);
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH v3 07/15] KVM: WARN if KVM attempts to do guest-related uaccess with "wrong" process
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (5 preceding siblings ...)
2026-10-27 22:11 ` [PATCH v3 06/15] KVM: Destroy memslots immediately after mmu_notifiers are unregistered Sean Christopherson
@ 2026-10-27 22:12 ` Sean Christopherson
2026-10-27 22:12 ` [PATCH v3 08/15] KVM: WARN and reject guest-based uaccess if VM is dying Sean Christopherson
` (7 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:12 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Now that memslots are torn down before kvm_arch_destroy_vm(), i.e. now that
the massive, unaudited path that is known to have at least one rogue access
to guest memory should be short-circuited before attempting to access user
memory, WARN if KVM attempts to do a user access to guest memory with the
wrong address space.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
include/linux/kvm_host.h | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index be27b4e8f12d..f3f59bdf7e18 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1355,7 +1355,7 @@ int kvm_gfn_to_hva_cache_init(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
static __always_inline __must_check bool kvm_can_do_uaccess(struct kvm *kvm)
{
- return current->mm == kvm->mm;
+ return !WARN_ON_ONCE(current->mm != kvm->mm);
}
#define BUILD_KVM_COPY_USER_WRAPPER(fn, to_user, from_user) \
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH v3 08/15] KVM: WARN and reject guest-based uaccess if VM is dying
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (6 preceding siblings ...)
2026-10-27 22:12 ` [PATCH v3 07/15] KVM: WARN if KVM attempts to do guest-related uaccess with "wrong" process Sean Christopherson
@ 2026-10-27 22:12 ` Sean Christopherson
2026-10-28 0:26 ` James Houghton
2026-10-27 22:12 ` [PATCH v3 09/15] KVM: Pre-check uaccesses in KVM's APIs to read/write guest memory Sean Christopherson
` (6 subsequent siblings)
14 siblings, 1 reply; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:12 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
WARN and reject user accesses to guest memory if the associated VM is dying
even if the current address space happens to be the correct address space.
Accessing guest memory after the last reference to the VM has been put may
be "fine" from a safety perspective, but it's still a KVM bug.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
include/linux/kvm_host.h | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index f3f59bdf7e18..ba8ea62b7304 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1355,7 +1355,8 @@ int kvm_gfn_to_hva_cache_init(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
static __always_inline __must_check bool kvm_can_do_uaccess(struct kvm *kvm)
{
- return !WARN_ON_ONCE(current->mm != kvm->mm);
+ return !WARN_ON_ONCE(current->mm != kvm->mm ||
+ !refcount_read(&kvm->users_count));
}
#define BUILD_KVM_COPY_USER_WRAPPER(fn, to_user, from_user) \
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v3 08/15] KVM: WARN and reject guest-based uaccess if VM is dying
2026-10-27 22:12 ` [PATCH v3 08/15] KVM: WARN and reject guest-based uaccess if VM is dying Sean Christopherson
@ 2026-10-28 0:26 ` James Houghton
0 siblings, 0 replies; 20+ messages in thread
From: James Houghton @ 2026-10-28 0:26 UTC (permalink / raw)
To: Sean Christopherson
Cc: Madhavan Srinivasan, Paolo Bonzini, Nicholas Piggin,
linuxppc-dev, kvm, linux-kernel, Jim Mattson, Chris Mason
On Tue, Oct 27, 2026 at 3:12 PM Sean Christopherson <seanjc@google.com> wrote:
>
> WARN and reject user accesses to guest memory if the associated VM is dying
> even if the current address space happens to be the correct address space.
> Accessing guest memory after the last reference to the VM has been put may
> be "fine" from a safety perspective, but it's still a KVM bug.
>
> Signed-off-by: Sean Christopherson <seanjc@google.com>
Reviewed-by: James Houghton <jthoughton@google.com>
> ---
> include/linux/kvm_host.h | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
> index f3f59bdf7e18..ba8ea62b7304 100644
> --- a/include/linux/kvm_host.h
> +++ b/include/linux/kvm_host.h
> @@ -1355,7 +1355,8 @@ int kvm_gfn_to_hva_cache_init(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
>
> static __always_inline __must_check bool kvm_can_do_uaccess(struct kvm *kvm)
> {
> - return !WARN_ON_ONCE(current->mm != kvm->mm);
> + return !WARN_ON_ONCE(current->mm != kvm->mm ||
> + !refcount_read(&kvm->users_count));
> }
>
> #define BUILD_KVM_COPY_USER_WRAPPER(fn, to_user, from_user) \
> --
> 2.56.0.307.g5fe7bd680c-goog
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3 09/15] KVM: Pre-check uaccesses in KVM's APIs to read/write guest memory
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (7 preceding siblings ...)
2026-10-27 22:12 ` [PATCH v3 08/15] KVM: WARN and reject guest-based uaccess if VM is dying Sean Christopherson
@ 2026-10-27 22:12 ` Sean Christopherson
2026-10-28 0:22 ` James Houghton
2026-10-27 22:12 ` [PATCH v3 10/15] KVM: VMX: Explicility free vmcs01, not the current VMCS, in vCPU free paths Sean Christopherson
` (5 subsequent siblings)
14 siblings, 1 reply; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:12 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Add a check on whether or not doing a user access is ok in KVM's APIs for
accessing guest memory, *before* doing memslot lookup/validation, so that
buggy KVM accesses to guest memory more likely to fail noisily. Because
KVM installs dummy/empty memslots early in VM destruction, bugs in the VM
teardown path will fail on the memslot checks without getting to the WARNs
buried in kvm_copy_{to,from}_user() and friends.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
virt/kvm/kvm_main.c | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 1058b864d427..d8b914b76f90 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -3298,6 +3298,11 @@ static int next_segment(unsigned long len, int offset)
return len;
}
+static bool kvm_is_guest_access_ok(struct kvm *kvm, int offset, int len, int size)
+{
+ return kvm_can_do_uaccess(kvm) && !WARN_ON_ONCE(offset + len > size);
+}
+
/* Copy @len bytes from guest memory at '(@gfn * PAGE_SIZE) + @offset' to @data */
static int __kvm_read_guest_page(struct kvm *kvm, struct kvm_memory_slot *slot,
gfn_t gfn, void *data, int offset, int len)
@@ -3305,7 +3310,7 @@ static int __kvm_read_guest_page(struct kvm *kvm, struct kvm_memory_slot *slot,
int r;
unsigned long addr;
- if (WARN_ON_ONCE(offset + len > PAGE_SIZE))
+ if (!kvm_is_guest_access_ok(kvm, offset, len, PAGE_SIZE))
return -EFAULT;
addr = gfn_to_hva_memslot_prot(slot, gfn, NULL);
@@ -3381,7 +3386,7 @@ static int __kvm_read_guest_atomic(struct kvm *kvm, struct kvm_memory_slot *slot
int r;
unsigned long addr;
- if (WARN_ON_ONCE(offset + len > PAGE_SIZE))
+ if (!kvm_is_guest_access_ok(kvm, offset, len, PAGE_SIZE))
return -EFAULT;
addr = gfn_to_hva_memslot_prot(slot, gfn, NULL);
@@ -3414,7 +3419,7 @@ static int __kvm_write_guest_page(struct kvm *kvm,
int r;
unsigned long addr;
- if (WARN_ON_ONCE(offset + len > PAGE_SIZE))
+ if (!kvm_is_guest_access_ok(kvm, offset, len, PAGE_SIZE))
return -EFAULT;
addr = gfn_to_hva_memslot(memslot, gfn);
@@ -3544,7 +3549,7 @@ int kvm_write_guest_offset_cached(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
int r;
gpa_t gpa = ghc->gpa + offset;
- if (WARN_ON_ONCE(len + offset > ghc->len))
+ if (!kvm_is_guest_access_ok(kvm, offset, len, ghc->len))
return -EINVAL;
if (slots->generation != ghc->generation) {
@@ -3582,7 +3587,7 @@ int kvm_read_guest_offset_cached(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
int r;
gpa_t gpa = ghc->gpa + offset;
- if (WARN_ON_ONCE(len + offset > ghc->len))
+ if (!kvm_is_guest_access_ok(kvm, offset, len, ghc->len))
return -EINVAL;
if (slots->generation != ghc->generation) {
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v3 09/15] KVM: Pre-check uaccesses in KVM's APIs to read/write guest memory
2026-10-27 22:12 ` [PATCH v3 09/15] KVM: Pre-check uaccesses in KVM's APIs to read/write guest memory Sean Christopherson
@ 2026-10-28 0:22 ` James Houghton
0 siblings, 0 replies; 20+ messages in thread
From: James Houghton @ 2026-10-28 0:22 UTC (permalink / raw)
To: Sean Christopherson
Cc: Madhavan Srinivasan, Paolo Bonzini, Nicholas Piggin,
linuxppc-dev, kvm, linux-kernel, Jim Mattson, Chris Mason
On Tue, Oct 27, 2026 at 3:12 PM Sean Christopherson <seanjc@google.com> wrote:
>
> Add a check on whether or not doing a user access is ok in KVM's APIs for
> accessing guest memory, *before* doing memslot lookup/validation, so that
> buggy KVM accesses to guest memory more likely to fail noisily. Because
"to guest memory [are] more likely"
> KVM installs dummy/empty memslots early in VM destruction, bugs in the VM
> teardown path will fail on the memslot checks without getting to the WARNs
> buried in kvm_copy_{to,from}_user() and friends.
>
> Signed-off-by: Sean Christopherson <seanjc@google.com>
One somewhat minor comment below. Feel free to add:
Reviewed-by: James Houghton <jthoughton@google.com>
> ---
> virt/kvm/kvm_main.c | 15 ++++++++++-----
> 1 file changed, 10 insertions(+), 5 deletions(-)
>
> diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
> index 1058b864d427..d8b914b76f90 100644
> --- a/virt/kvm/kvm_main.c
> +++ b/virt/kvm/kvm_main.c
> @@ -3298,6 +3298,11 @@ static int next_segment(unsigned long len, int offset)
> return len;
> }
>
> +static bool kvm_is_guest_access_ok(struct kvm *kvm, int offset, int len, int size)
Hrm... I think you at least want `len` and `size` to be `unsigned
long`, and you probably want `offset` to be `unsigned int` or
`unsigned long`. The *_cached() functions are the interesting ones in
this case; I've left a note below at the first callsite.
> +{
> + return kvm_can_do_uaccess(kvm) && !WARN_ON_ONCE(offset + len > size);
> +}
> +
> /* Copy @len bytes from guest memory at '(@gfn * PAGE_SIZE) + @offset' to @data */
> static int __kvm_read_guest_page(struct kvm *kvm, struct kvm_memory_slot *slot,
> gfn_t gfn, void *data, int offset, int len)
> @@ -3305,7 +3310,7 @@ static int __kvm_read_guest_page(struct kvm *kvm, struct kvm_memory_slot *slot,
> int r;
> unsigned long addr;
>
> - if (WARN_ON_ONCE(offset + len > PAGE_SIZE))
> + if (!kvm_is_guest_access_ok(kvm, offset, len, PAGE_SIZE))
> return -EFAULT;
>
> addr = gfn_to_hva_memslot_prot(slot, gfn, NULL);
> @@ -3381,7 +3386,7 @@ static int __kvm_read_guest_atomic(struct kvm *kvm, struct kvm_memory_slot *slot
> int r;
> unsigned long addr;
>
> - if (WARN_ON_ONCE(offset + len > PAGE_SIZE))
> + if (!kvm_is_guest_access_ok(kvm, offset, len, PAGE_SIZE))
> return -EFAULT;
>
> addr = gfn_to_hva_memslot_prot(slot, gfn, NULL);
> @@ -3414,7 +3419,7 @@ static int __kvm_write_guest_page(struct kvm *kvm,
> int r;
> unsigned long addr;
>
> - if (WARN_ON_ONCE(offset + len > PAGE_SIZE))
> + if (!kvm_is_guest_access_ok(kvm, offset, len, PAGE_SIZE))
> return -EFAULT;
>
> addr = gfn_to_hva_memslot(memslot, gfn);
> @@ -3544,7 +3549,7 @@ int kvm_write_guest_offset_cached(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
> int r;
> gpa_t gpa = ghc->gpa + offset;
>
> - if (WARN_ON_ONCE(len + offset > ghc->len))
> + if (!kvm_is_guest_access_ok(kvm, offset, len, ghc->len))
Currently, this truncates `len` and `ghc->len` from `unsigned long` to
`int`, and it will change `offset` from `unsigned int` to `int`.
> return -EINVAL;
>
> if (slots->generation != ghc->generation) {
> @@ -3582,7 +3587,7 @@ int kvm_read_guest_offset_cached(struct kvm *kvm, struct gfn_to_hva_cache *ghc,
> int r;
> gpa_t gpa = ghc->gpa + offset;
>
> - if (WARN_ON_ONCE(len + offset > ghc->len))
> + if (!kvm_is_guest_access_ok(kvm, offset, len, ghc->len))
> return -EINVAL;
>
> if (slots->generation != ghc->generation) {
> --
> 2.56.0.307.g5fe7bd680c-goog
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3 10/15] KVM: VMX: Explicility free vmcs01, not the current VMCS, in vCPU free paths
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (8 preceding siblings ...)
2026-10-27 22:12 ` [PATCH v3 09/15] KVM: Pre-check uaccesses in KVM's APIs to read/write guest memory Sean Christopherson
@ 2026-10-27 22:12 ` Sean Christopherson
2026-10-28 0:33 ` James Houghton
2026-10-27 22:12 ` [PATCH v3 11/15] KVM: nVMX: Don't explicitly VMCLEAR+free() vmcs01's shadow VMCS when freeing vCPU Sean Christopherson
` (4 subsequent siblings)
14 siblings, 1 reply; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:12 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Explicitly free vmcs01 instead of the currently loaded VMCS when freeing a
vCPU, as relying on loaded_vmcs to point at vmcb01 is unnecessarily risky
and confusing. E.g. during vCPU creation, the error path relies on setting
loaded_vmcs and obfuscates that it should be impossible for vmcb02 to be
allocated, let alone active. Similarly, if KVM were buggy and left vmcb02
as the current vCPU when freeing a vCPU, operating on loaded_vmcs would hit
a use-after-free *and* leak vmcs01.
Because KVM isn't buggy, no functional change intended.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/vmx/vmx.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index d433cd846ac4..2ef1f634e7ec 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -7515,7 +7515,7 @@ void vmx_vcpu_free(struct kvm_vcpu *vcpu)
vmx_destroy_pml_buffer(vcpu);
free_vpid(vmx->vpid);
nested_vmx_free_vcpu(vcpu);
- free_loaded_vmcs(vmx->loaded_vmcs);
+ free_loaded_vmcs(&vmx->vmcs01);
free_page((unsigned long)vmx->ve_info);
if (vmx_can_use_ipiv(vcpu))
@@ -7614,7 +7614,7 @@ int vmx_vcpu_create(struct kvm_vcpu *vcpu)
return 0;
free_vmcs:
- free_loaded_vmcs(vmx->loaded_vmcs);
+ free_loaded_vmcs(&vmx->vmcs01);
free_pml:
vmx_destroy_pml_buffer(vcpu);
free_vpid:
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* Re: [PATCH v3 10/15] KVM: VMX: Explicility free vmcs01, not the current VMCS, in vCPU free paths
2026-10-27 22:12 ` [PATCH v3 10/15] KVM: VMX: Explicility free vmcs01, not the current VMCS, in vCPU free paths Sean Christopherson
@ 2026-10-28 0:33 ` James Houghton
0 siblings, 0 replies; 20+ messages in thread
From: James Houghton @ 2026-10-28 0:33 UTC (permalink / raw)
To: Sean Christopherson
Cc: Madhavan Srinivasan, Paolo Bonzini, Nicholas Piggin,
linuxppc-dev, kvm, linux-kernel, Jim Mattson, Chris Mason
On Tue, Oct 27, 2026 at 3:12 PM Sean Christopherson <seanjc@google.com> wrote:
>
> Explicitly free vmcs01 instead of the currently loaded VMCS when freeing a
> vCPU, as relying on loaded_vmcs to point at vmcb01 is unnecessarily risky
> and confusing. E.g. during vCPU creation, the error path relies on setting
> loaded_vmcs and obfuscates that it should be impossible for vmcb02 to be
> allocated, let alone active. Similarly, if KVM were buggy and left vmcb02
> as the current vCPU when freeing a vCPU, operating on loaded_vmcs would hit
> a use-after-free *and* leak vmcs01.
>
> Because KVM isn't buggy, no functional change intended.
>
> Signed-off-by: Sean Christopherson <seanjc@google.com>
Reviewed-by: James Houghton <jthoughton@google.com>
> ---
> arch/x86/kvm/vmx/vmx.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
>
> diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
> index d433cd846ac4..2ef1f634e7ec 100644
> --- a/arch/x86/kvm/vmx/vmx.c
> +++ b/arch/x86/kvm/vmx/vmx.c
> @@ -7515,7 +7515,7 @@ void vmx_vcpu_free(struct kvm_vcpu *vcpu)
> vmx_destroy_pml_buffer(vcpu);
> free_vpid(vmx->vpid);
> nested_vmx_free_vcpu(vcpu);
> - free_loaded_vmcs(vmx->loaded_vmcs);
> + free_loaded_vmcs(&vmx->vmcs01);
> free_page((unsigned long)vmx->ve_info);
>
> if (vmx_can_use_ipiv(vcpu))
> @@ -7614,7 +7614,7 @@ int vmx_vcpu_create(struct kvm_vcpu *vcpu)
> return 0;
>
> free_vmcs:
> - free_loaded_vmcs(vmx->loaded_vmcs);
> + free_loaded_vmcs(&vmx->vmcs01);
> free_pml:
> vmx_destroy_pml_buffer(vcpu);
> free_vpid:
> --
> 2.56.0.307.g5fe7bd680c-goog
>
^ permalink raw reply [flat|nested] 20+ messages in thread
* [PATCH v3 11/15] KVM: nVMX: Don't explicitly VMCLEAR+free() vmcs01's shadow VMCS when freeing vCPU
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (9 preceding siblings ...)
2026-10-27 22:12 ` [PATCH v3 10/15] KVM: VMX: Explicility free vmcs01, not the current VMCS, in vCPU free paths Sean Christopherson
@ 2026-10-27 22:12 ` Sean Christopherson
2026-10-27 22:12 ` [PATCH v3 12/15] KVM: nVMX: Cancel the nested VMX Preemption Timer's hrtimer in free_nested() Sean Christopherson
` (3 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:12 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Explicitly VMCLEAR and free() vmcs01's shadow VMCS only when doing VMXOFF
or forcing the vCPU out of L2, not when freeing the vCPU. Instead, free
the shadow VMCS as part of free_loaded_vmcs(), i.e. when freeing vmcs01 via
vmx_vcpu_free(). This will allow leaving vmcs02 as the loaded/current VMCS
when freeing a vCPU, i.e. will allow dropping the vmx_switch_vmcs() call as
well as the vcpu_{load,put}() calls, all of which do far too much work when
freeing a vCPU. E.g. "just" switching to vmcs01 means potentially doing an
extra VCLEAR shootdown, potentially multiple VMPTRLDs (to first load vmcs02
and then vmcs01) and VMWRITEs, and so on and so forth.
Note, shadow VMCSes are rather odd. They're only ever used when nested
virtualization is live, but a shadow VMCS is provided to L1, not L2, and
more importantly is made "active" (in VMX parlance) when vmcs01 is run (the
shadow VMCS is referenced by a pointer in vmcs01). Because KVM dynamically
allocates and frees nested state, shadow VMCSes are loosely coupled to the
VMXON/VMXOFF lifecycle, but in practice are more concretely tied to vmcs01,
e.g. a shadow VMCS needs to be VMCLEAR'd if and only if its associated
vmcs01 was launched.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/vmx/nested.c | 54 ++++++++++++++++++++++++---------------
arch/x86/kvm/vmx/vmx.c | 5 +++-
2 files changed, 37 insertions(+), 22 deletions(-)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 49c4529901c5..1b30ad7731ee 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -330,17 +330,9 @@ static void nested_put_vmcs12_pages(struct kvm_vcpu *vcpu)
vmx->nested.pi_desc = NULL;
}
-/*
- * Free whatever needs to be freed from vmx->nested when L1 goes down, or
- * just stops using VMX.
- */
static void free_nested(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
- struct vmcs *shadow_vmcs;
-
- if (WARN_ON_ONCE(vmx->loaded_vmcs != &vmx->vmcs01))
- vmx_switch_vmcs(vcpu, &vmx->vmcs01);
if (!vmx->nested.vmxon && !vmx->nested.smm.vmxon)
return;
@@ -353,6 +345,36 @@ static void free_nested(struct kvm_vcpu *vcpu)
free_vpid(vmx->nested.vpid02);
vmx->nested.posted_intr_nv = -1;
vmx->nested.current_vmptr = INVALID_GPA;
+
+ kfree(vmx->nested.cached_vmcs12);
+ vmx->nested.cached_vmcs12 = NULL;
+ kfree(vmx->nested.cached_shadow_vmcs12);
+ vmx->nested.cached_shadow_vmcs12 = NULL;
+
+ nested_put_vmcs12_pages(vcpu);
+
+ kvm_mmu_free_roots(vcpu->kvm, &vcpu->arch.guest_mmu, KVM_MMU_ROOTS_ALL);
+
+ nested_release_evmcs(vcpu);
+
+ free_loaded_vmcs(&vmx->nested.vmcs02);
+}
+
+/*
+ * Free whatever needs to be freed from vmx->nested when L1 goes down, or
+ * just stops using VMX.
+ */
+static void nested_vmx_do_vmxoff(struct kvm_vcpu *vcpu)
+{
+ struct vcpu_vmx *vmx = to_vmx(vcpu);
+ struct vmcs *shadow_vmcs;
+
+ if (WARN_ON_ONCE(vmx->loaded_vmcs != &vmx->vmcs01))
+ vmx_switch_vmcs(vcpu, &vmx->vmcs01);
+
+ if (!vmx->nested.vmxon && !vmx->nested.smm.vmxon)
+ return;
+
if (enable_shadow_vmcs) {
vmx_disable_shadow_vmcs(vmx);
@@ -365,18 +387,8 @@ static void free_nested(struct kvm_vcpu *vcpu)
vmx->vmcs01.shadow_vmcs = NULL;
free_vmcs(shadow_vmcs);
}
- kfree(vmx->nested.cached_vmcs12);
- vmx->nested.cached_vmcs12 = NULL;
- kfree(vmx->nested.cached_shadow_vmcs12);
- vmx->nested.cached_shadow_vmcs12 = NULL;
- nested_put_vmcs12_pages(vcpu);
-
- kvm_mmu_free_roots(vcpu->kvm, &vcpu->arch.guest_mmu, KVM_MMU_ROOTS_ALL);
-
- nested_release_evmcs(vcpu);
-
- free_loaded_vmcs(&vmx->nested.vmcs02);
+ free_nested(vcpu);
}
/*
@@ -5606,7 +5618,7 @@ static int handle_vmxoff(struct kvm_vcpu *vcpu)
if (!nested_vmx_check_permission(vcpu))
return 1;
- free_nested(vcpu);
+ nested_vmx_do_vmxoff(vcpu);
if (kvm_apic_has_pending_init_or_sipi(vcpu))
kvm_make_request(KVM_REQ_EVENT, vcpu);
@@ -6898,7 +6910,7 @@ void vmx_leave_nested(struct kvm_vcpu *vcpu)
vcpu->arch.nested_run_pending = 0;
nested_vmx_vmexit(vcpu, -1, 0, 0);
}
- free_nested(vcpu);
+ nested_vmx_do_vmxoff(vcpu);
}
int nested_vmx_check_restored_vmcs12(struct kvm_vcpu *vcpu)
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 2ef1f634e7ec..292001bbcbff 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -3089,7 +3089,10 @@ void free_loaded_vmcs(struct loaded_vmcs *loaded_vmcs)
loaded_vmcs->vmcs = NULL;
if (loaded_vmcs->msr_bitmap)
free_page((unsigned long)loaded_vmcs->msr_bitmap);
- WARN_ON(loaded_vmcs->shadow_vmcs != NULL);
+ if (loaded_vmcs->shadow_vmcs) {
+ free_vmcs(loaded_vmcs->shadow_vmcs);
+ loaded_vmcs->shadow_vmcs = NULL;
+ }
}
int alloc_loaded_vmcs(struct loaded_vmcs *loaded_vmcs)
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH v3 12/15] KVM: nVMX: Cancel the nested VMX Preemption Timer's hrtimer in free_nested()
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (10 preceding siblings ...)
2026-10-27 22:12 ` [PATCH v3 11/15] KVM: nVMX: Don't explicitly VMCLEAR+free() vmcs01's shadow VMCS when freeing vCPU Sean Christopherson
@ 2026-10-27 22:12 ` Sean Christopherson
2026-10-27 22:12 ` [PATCH v3 13/15] KVM: VMX: Explicitly remove dying vCPUs from the PI wakeup list Sean Christopherson
` (2 subsequent siblings)
14 siblings, 0 replies; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:12 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Cancel the hrtimer used to emulate a VMX Preemption Timer for L2 when
freeing nested state instead of precisely canceling the timer if and only
if the vCPU is/was in L2. hrtimer_cancel() bails early if the timer isn't
running, and KVM initializes the timer when allocating nested state, i.e.
canceling the timer even if it's not fully in-use is glorified nop.
Canceling the timer even if L1 is active will allow KVM to leave the vCPU
in L2 without having to retain an "is_guest_mode()" check just to cancel
timer.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/vmx/nested.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 1b30ad7731ee..553915015a75 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -339,6 +339,8 @@ static void free_nested(struct kvm_vcpu *vcpu)
kvm_clear_request(KVM_REQ_GET_NESTED_STATE_PAGES, vcpu);
+ hrtimer_cancel(&vmx->nested.preemption_timer);
+
vmx->nested.vmxon = false;
vmx->nested.smm.vmxon = false;
vmx->nested.vmxon_ptr = INVALID_GPA;
@@ -401,7 +403,6 @@ void nested_vmx_free_vcpu(struct kvm_vcpu *vcpu)
vcpu_load(vcpu);
if (is_guest_mode(vcpu)) {
- hrtimer_cancel(&vmx->nested.preemption_timer);
leave_guest_mode(vcpu);
vmx_switch_vmcs(vcpu, &vmx->vmcs01);
}
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH v3 13/15] KVM: VMX: Explicitly remove dying vCPUs from the PI wakeup list
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (11 preceding siblings ...)
2026-10-27 22:12 ` [PATCH v3 12/15] KVM: nVMX: Cancel the nested VMX Preemption Timer's hrtimer in free_nested() Sean Christopherson
@ 2026-10-27 22:12 ` Sean Christopherson
2026-10-27 22:12 ` [PATCH v3 14/15] KVM: nVMX: Don't switch to vmcs01 when freeing a vCPU Sean Christopherson
2026-10-27 22:12 ` [PATCH v3 15/15] KVM: nVMX: Don't load+put the vCPU when freeing nested state for a dying vCPU Sean Christopherson
14 siblings, 0 replies; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:12 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Explicitly remove dying vCPUs that were blocking when the VM was released
from the Posted Interrupts wakeup list instead of very, very subtly relying
on the vcpu_load() in nested_vmx_free_vcpu() to take the vCPU off said list.
In addition to helping avoid future regressions, e.g. if an enterprising
developer were to optimize nested_vmx_free_vcpu() by bailing early if
"nested" is completely disabled, this will allow dropping the load+put pair
from nested_vmx_free_vcpu().
Provide a dedicated API to remove a vCPU from the wakeup list, i.e. don't
_completely_ abuse vt_vcpu_pi_load(), but do reuse the core logic for
updating the PID control word. Using a CMPXCHG8B _should_ be unnecessary
as IRQ routing has already been torn down, i.e. no IRTEs should be pointing
at updating the PID, and there are obviously no other references to the VM
or vCPU, and so concurrently modifications of the PID should be impossible.
For the same reasons, updating the destination should also be unnecessary,
as nothing should ever send an IRQ. However, relying on those assumptions
begs for WARNs to ensure they hold, along with fallback code to avoid the
worst case scenario, at which point trying to take advantage of the fact
that the PID is unreachable would be *more* complex than what is now
__vt_vcpu_pi_load(), not to mention KVM would have two paths to maintain.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/vmx/posted_intr.c | 68 +++++++++++++++++++++-------------
arch/x86/kvm/vmx/posted_intr.h | 1 +
arch/x86/kvm/vmx/vmx.c | 2 +
3 files changed, 45 insertions(+), 26 deletions(-)
diff --git a/arch/x86/kvm/vmx/posted_intr.c b/arch/x86/kvm/vmx/posted_intr.c
index 2644add54b76..7414fb551d2e 100644
--- a/arch/x86/kvm/vmx/posted_intr.c
+++ b/arch/x86/kvm/vmx/posted_intr.c
@@ -56,35 +56,13 @@ static int pi_try_set_control(struct pi_desc *pi_desc, u64 *pold, u64 new)
return 0;
}
-void vt_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
+static void __vt_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
{
struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu);
- struct vcpu_vt *vt = to_vt(vcpu);
struct pi_desc old, new;
- unsigned long flags;
unsigned int dest;
- /*
- * To simplify hot-plug and dynamic toggling of APICv, keep PI.NDST and
- * PI.SN up-to-date even if there is no assigned device or if APICv is
- * deactivated due to a dynamic inhibit bit, e.g. for Hyper-V's SyncIC.
- */
- if (!enable_apicv || !lapic_in_kernel(vcpu))
- return;
-
- /*
- * If the vCPU wasn't on the wakeup list and wasn't migrated, then the
- * full update can be skipped as neither the vector nor the destination
- * needs to be changed. Clear SN even if there is no assigned device,
- * again for simplicity.
- */
- if (pi_desc->nv != POSTED_INTR_WAKEUP_VECTOR && vcpu->cpu == cpu) {
- if (pi_test_and_clear_sn(pi_desc))
- goto after_clear_sn;
- return;
- }
-
- local_irq_save(flags);
+ guard(irqsave)();
/*
* If the vCPU was waiting for wakeup, remove the vCPU from the wakeup
@@ -103,7 +81,7 @@ void vt_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
*/
raw_spin_lock(spinlock);
spin_acquire(&spinlock->dep_map, PI_LOCK_SCHED_OUT, 0, _RET_IP_);
- list_del(&vt->pi_wakeup_list);
+ list_del(&to_vt(vcpu)->pi_wakeup_list);
spin_release(&spinlock->dep_map, _RET_IP_);
raw_spin_unlock(spinlock);
}
@@ -129,8 +107,33 @@ void vt_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
*/
new.nv = POSTED_INTR_VECTOR;
} while (pi_try_set_control(pi_desc, &old.control, new.control));
+}
- local_irq_restore(flags);
+void vt_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
+{
+ struct pi_desc *pi_desc = vcpu_to_pi_desc(vcpu);
+
+ /*
+ * To simplify hot-plug and dynamic toggling of APICv, keep PI.NDST and
+ * PI.SN up-to-date even if there is no assigned device or if APICv is
+ * deactivated due to a dynamic inhibit bit, e.g. for Hyper-V's SyncIC.
+ */
+ if (!enable_apicv || !lapic_in_kernel(vcpu))
+ return;
+
+ /*
+ * If the vCPU wasn't on the wakeup list and wasn't migrated, then the
+ * full update can be skipped as neither the vector nor the destination
+ * needs to be changed. Clear SN even if there is no assigned device,
+ * again for simplicity.
+ */
+ if (pi_desc->nv != POSTED_INTR_WAKEUP_VECTOR && vcpu->cpu == cpu) {
+ if (pi_test_and_clear_sn(pi_desc))
+ goto after_clear_sn;
+ return;
+ }
+
+ __vt_vcpu_pi_load(vcpu, cpu);
after_clear_sn:
@@ -146,6 +149,19 @@ void vt_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu)
pi_set_on(pi_desc);
}
+void vt_vcpu_pi_remove_from_wakeup_list(struct kvm_vcpu *vcpu)
+{
+ WARN_ON_ONCE(refcount_read(&vcpu->kvm->users_count));
+
+ if (!enable_apicv || !lapic_in_kernel(vcpu) ||
+ vcpu_to_pi_desc(vcpu)->nv != POSTED_INTR_WAKEUP_VECTOR)
+ return;
+
+ __vt_vcpu_pi_load(vcpu, get_cpu());
+
+ put_cpu();
+}
+
static bool vt_can_use_vtd_pi(struct kvm *kvm)
{
/*
diff --git a/arch/x86/kvm/vmx/posted_intr.h b/arch/x86/kvm/vmx/posted_intr.h
index c5860ea828ae..1d9ccbc2bb6f 100644
--- a/arch/x86/kvm/vmx/posted_intr.h
+++ b/arch/x86/kvm/vmx/posted_intr.h
@@ -10,6 +10,7 @@
void vt_vcpu_pi_load(struct kvm_vcpu *vcpu, int cpu);
void vt_vcpu_pi_put(struct kvm_vcpu *vcpu);
+void vt_vcpu_pi_remove_from_wakeup_list(struct kvm_vcpu *vcpu);
void pi_wakeup_handler(void);
void __init pi_init_cpu(int cpu);
void pi_apicv_pre_state_restore(struct kvm_vcpu *vcpu);
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 292001bbcbff..1f9e43c5b4c4 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -7514,6 +7514,8 @@ void vmx_vcpu_free(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
+ vt_vcpu_pi_remove_from_wakeup_list(vcpu);
+
if (enable_pml)
vmx_destroy_pml_buffer(vcpu);
free_vpid(vmx->vpid);
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH v3 14/15] KVM: nVMX: Don't switch to vmcs01 when freeing a vCPU
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (12 preceding siblings ...)
2026-10-27 22:12 ` [PATCH v3 13/15] KVM: VMX: Explicitly remove dying vCPUs from the PI wakeup list Sean Christopherson
@ 2026-10-27 22:12 ` Sean Christopherson
2026-10-27 22:12 ` [PATCH v3 15/15] KVM: nVMX: Don't load+put the vCPU when freeing nested state for a dying vCPU Sean Christopherson
14 siblings, 0 replies; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:12 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Now that neither free_nested() nor vmx_vcpu_free() relies on vmcs01 being
the active VMCS, don't bother switching to L1 and thus vmcs01 when freeing
nested state.
Temporarily keep the vcpu_{load,put}() pair even though the only reason the
vCPU needed to be loaded was to be able to do vmx_switch_vmcs(), as there
was at least one *known* side effect of doing vcpu_load() that KVM was
subtly relying on (taking the vCPU off the PI wakeup list). I.e. defer
dropping the superfluous load+put to a separate patch so that there's a
dedicated bisection point if there are more lurking dependencies.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/vmx/nested.c | 10 ----------
1 file changed, 10 deletions(-)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 553915015a75..4a8f57eb3277 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -393,19 +393,9 @@ static void nested_vmx_do_vmxoff(struct kvm_vcpu *vcpu)
free_nested(vcpu);
}
-/*
- * Ensure that the current vmcs of the logical processor is the
- * vmcs01 of the vcpu before calling free_nested().
- */
void nested_vmx_free_vcpu(struct kvm_vcpu *vcpu)
{
- struct vcpu_vmx *vmx = to_vmx(vcpu);
-
vcpu_load(vcpu);
- if (is_guest_mode(vcpu)) {
- leave_guest_mode(vcpu);
- vmx_switch_vmcs(vcpu, &vmx->vmcs01);
- }
free_nested(vcpu);
vcpu_put(vcpu);
}
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread* [PATCH v3 15/15] KVM: nVMX: Don't load+put the vCPU when freeing nested state for a dying vCPU
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (13 preceding siblings ...)
2026-10-27 22:12 ` [PATCH v3 14/15] KVM: nVMX: Don't switch to vmcs01 when freeing a vCPU Sean Christopherson
@ 2026-10-27 22:12 ` Sean Christopherson
14 siblings, 0 replies; 20+ messages in thread
From: Sean Christopherson @ 2026-10-27 22:12 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Drop the vcpu_{load,put}() pair when freeing nested state for a dying vCPU,
as nothing in what was free_nested() requires the vCPU to be loaded, common
x86 doesn't require the vCPU to be loaded when it's dying (because SVM
never loads the vCPU), KVM now explicitly handles taking the vCPU off the
PI wakeup list (was subtly being handled by vt_vcpu_pi_load()), and the
rest of vmx_vcpu_load() has no effects outside of loading/modifying the
VMCS or vCPU-specific metadata.
As for put(), because the vCPU obviously can't be loaded at the start of
nested_vmx_free_vcpu(), "guest_state_loaded" can't be true and thus
vmx_prepare_switch_to_host() must be a nop. Similarly, the vCPU can't be
blocking when it's dying, and so vt_vcpu_pi_put() is also a nop (and if
it's not, that would be a fatal bug as it means the vCPU would be left on
the PI wakeup list after being freed).
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/vmx/nested.c | 11 ++---------
1 file changed, 2 insertions(+), 9 deletions(-)
diff --git a/arch/x86/kvm/vmx/nested.c b/arch/x86/kvm/vmx/nested.c
index 4a8f57eb3277..51183520f64a 100644
--- a/arch/x86/kvm/vmx/nested.c
+++ b/arch/x86/kvm/vmx/nested.c
@@ -330,7 +330,7 @@ static void nested_put_vmcs12_pages(struct kvm_vcpu *vcpu)
vmx->nested.pi_desc = NULL;
}
-static void free_nested(struct kvm_vcpu *vcpu)
+void nested_vmx_free_vcpu(struct kvm_vcpu *vcpu)
{
struct vcpu_vmx *vmx = to_vmx(vcpu);
@@ -390,14 +390,7 @@ static void nested_vmx_do_vmxoff(struct kvm_vcpu *vcpu)
free_vmcs(shadow_vmcs);
}
- free_nested(vcpu);
-}
-
-void nested_vmx_free_vcpu(struct kvm_vcpu *vcpu)
-{
- vcpu_load(vcpu);
- free_nested(vcpu);
- vcpu_put(vcpu);
+ nested_vmx_free_vcpu(vcpu);
}
#define EPTP_PA_MASK GENMASK_ULL(51, 12)
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 20+ messages in thread