* [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM
@ 2026-10-27 22:11 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
` (15 more replies)
0 siblings, 16 replies; 23+ 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
Fix a class of bugs (limited to nested VMX, as far as we know) where KVM can
corrupt an unrelated process' memory if KVM (attempts to) write to guest memory
during VM destruction. Because current->mm usually isn't kvm->mm when a VM is
dying, e.g. because the associated kvm->mm process has already exited, writing
to what KVM thinks is guest memory will corrupt the current address space if
the associated userspace address happens to be writable in the victim.
Patch 1 is a blanket fix for the uaccess paths. AFAIK, x86's nVMX is the only
path in KVM that screws up, but auditing kvm_arch_destroy_vm() proved to be
infeasible for a human (I didn't throw AI at it, yet...), and I can't think of
any downsides to going straight to a broader fix.
Patch 2 fixes the underlyihng nVMX bug where KVM absuses the architectural
nested VM-Exit flow when tearing down a vCPU, which has a truly absurd number
of problems.
Patch 3 fixes what I assume is a blatant PPC bug. KVM PPC completely ignores
kvm_arch_flush_shadow_all(), i.e. AFAICT, doesn't tear down its page tables
when the owning process exits. I don't have much confidence in the "fix", in
part because it seems impossible that such a blatant bug could have gone
unnoticed, but also because I went with a very naive approach of invoking
kvm_arch_flush_shadow_memslot() for each memslot. The changelog is pretty
sparse, because I didn't know how to describe the issue byeond "this is
completely broken".
Patches 4-6 implement more agressive hardening to nuke the memslots before
calling kvm_arch_destroy_vm(), e.g. to guard against writing to guest memory
during kvm_arch_destroy_vm() without going through uaccess. Setting dummy
memslots feels a little hacky, but kvm_arch_flush_shadow_all() should have
purged everything that effectively caches memslots, so it seems like the right
approach?
Patches 7-9 add more hardening to detect KVM bugs (though the uaccess+memslot
changes earlier in the series should render any bugs benign).
The remaining patches double down on open coding nVMX's vCPU teardown by
tearing down the vCPU "as-is", instead of forcing a switch to L1 and then
doing teardown. The forced switch is quite heinous, as it requires KVM to
*load* the vCPU when it's dying due to architectural constraints for active
VMCSes. Not only is that gross, it was also papering over a bug (hopefully
just the one) where KVM was relying on the vCPU load for nested VMX to take
vCPUs off the posted interrupts wakeup list.
v3:
- Give up, and open code nVMX teardown. [Sashiko, syzbot, Chris]
- Drop the nVMX hacks that were trying to fudge around the mass of nVMX
teardown issues.
- Free the VMA only for the non-SMM internal memslot. [Sashiko]
- Use @len, not hardcoded PAGE_SIZE, in kvm_is_guest_access_ok(). [Sashiko]
- Rip out the awful vCPU load code from nested_vmx_free_vcpu().
- Collect James' reviews.
v2: https://lore.kernel.org/all/20261001202234.3794060-1-seanjc@google.com
v1: https://lore.kernel.org/all/20260908132838.2116068-1-jmattson@google.com
Sean Christopherson (15):
KVM: Reject user accesses to guest memory if current->mm != kvm->mm
KVM: nVMX: Don't force a full nested VM-Exit when destroying a vCPU
KVM: PPC: Flush/zap all memslots on kvm_arch_flush_shadow_all()
KVM: x86: Unmap VMAs for KVM-internal memslots when the memslot is
freed
KVM: Disallow setting memslots when the VM is being destroyed
KVM: Destroy memslots immediately after mmu_notifiers are unregistered
KVM: WARN if KVM attempts to do guest-related uaccess with "wrong"
process
KVM: WARN and reject guest-based uaccess if VM is dying
KVM: Pre-check uaccesses in KVM's APIs to read/write guest memory
KVM: VMX: Explicility free vmcs01, not the current VMCS, in vCPU free
paths
KVM: nVMX: Don't explicitly VMCLEAR+free() vmcs01's shadow VMCS when
freeing vCPU
KVM: nVMX: Cancel the nested VMX Preemption Timer's hrtimer in
free_nested()
KVM: VMX: Explicitly remove dying vCPUs from the PI wakeup list
KVM: nVMX: Don't switch to vmcs01 when freeing a vCPU
KVM: nVMX: Don't load+put the vCPU when freeing nested state for a
dying vCPU
arch/powerpc/include/asm/kvm_host.h | 1 -
arch/powerpc/kvm/powerpc.c | 11 ++++
arch/x86/kvm/vmx/nested.c | 69 ++++++++++++-----------
arch/x86/kvm/vmx/posted_intr.c | 68 +++++++++++++---------
arch/x86/kvm/vmx/posted_intr.h | 1 +
arch/x86/kvm/vmx/sgx.c | 2 +-
arch/x86/kvm/vmx/vmx.c | 19 ++++---
arch/x86/kvm/x86.c | 38 ++++++-------
include/linux/kvm_host.h | 26 ++++++++-
virt/kvm/kvm_main.c | 87 +++++++++++++++++++----------
10 files changed, 199 insertions(+), 123 deletions(-)
base-commit: 7e60162b16f9da0d2fa83da5c1723b6853a0ac1d
--
2.56.0.307.g5fe7bd680c-goog
^ permalink raw reply [flat|nested] 23+ messages in thread
* [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
` (14 subsequent siblings)
15 siblings, 0 replies; 23+ 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] 23+ 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
` (13 subsequent siblings)
15 siblings, 1 reply; 23+ 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] 23+ 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
` (12 subsequent siblings)
15 siblings, 0 replies; 23+ 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] 23+ 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
` (11 subsequent siblings)
15 siblings, 0 replies; 23+ 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] 23+ 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
` (10 subsequent siblings)
15 siblings, 0 replies; 23+ 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] 23+ 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
` (9 subsequent siblings)
15 siblings, 0 replies; 23+ 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] 23+ 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
` (8 subsequent siblings)
15 siblings, 0 replies; 23+ 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] 23+ 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
` (7 subsequent siblings)
15 siblings, 1 reply; 23+ 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] 23+ 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
` (6 subsequent siblings)
15 siblings, 1 reply; 23+ 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] 23+ 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
` (5 subsequent siblings)
15 siblings, 1 reply; 23+ 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] 23+ 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
` (4 subsequent siblings)
15 siblings, 0 replies; 23+ 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] 23+ 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
` (3 subsequent siblings)
15 siblings, 0 replies; 23+ 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] 23+ 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
` (2 subsequent siblings)
15 siblings, 0 replies; 23+ 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] 23+ 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
2026-10-28 9:18 ` [syzbot ci] Re: KVM: Fix+harden against bad uaccess using dying VM syzbot ci
15 siblings, 0 replies; 23+ 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] 23+ 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
2026-10-28 9:18 ` [syzbot ci] Re: KVM: Fix+harden against bad uaccess using dying VM syzbot ci
15 siblings, 0 replies; 23+ 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] 23+ 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; 23+ 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] 23+ 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; 23+ 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] 23+ 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; 23+ 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] 23+ 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; 23+ 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] 23+ messages in thread
* [syzbot ci] Re: KVM: Fix+harden against bad uaccess using dying VM
2026-10-27 22:11 [PATCH v3 00/15] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (14 preceding siblings ...)
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
@ 2026-10-28 9:18 ` syzbot ci
15 siblings, 0 replies; 23+ messages in thread
From: syzbot ci @ 2026-10-28 9:18 UTC (permalink / raw)
To: jmattson, jthoughton, kvm, linux-kernel, linuxppc-dev, maddy,
mason, npiggin, pbonzini, seanjc
Cc: syzbot, syzkaller-bugs
syzbot ci has tested the following series
[v3] KVM: Fix+harden against bad uaccess using dying VM
https://lore.kernel.org/all/20261027221209.1560865-1-seanjc@google.com
* [PATCH v3 01/15] KVM: Reject user accesses to guest memory if current->mm != kvm->mm
* [PATCH v3 02/15] KVM: nVMX: Don't force a full nested VM-Exit when destroying a vCPU
* [PATCH v3 03/15] KVM: PPC: Flush/zap all memslots on kvm_arch_flush_shadow_all()
* [PATCH v3 04/15] KVM: x86: Unmap VMAs for KVM-internal memslots when the memslot is freed
* [PATCH v3 05/15] KVM: Disallow setting memslots when the VM is being destroyed
* [PATCH v3 06/15] KVM: Destroy memslots immediately after mmu_notifiers are unregistered
* [PATCH v3 07/15] KVM: WARN if KVM attempts to do guest-related uaccess with "wrong" process
* [PATCH v3 08/15] KVM: WARN and reject guest-based uaccess if VM is dying
* [PATCH v3 09/15] KVM: Pre-check uaccesses in KVM's APIs to read/write guest memory
* [PATCH v3 10/15] KVM: VMX: Explicility free vmcs01, not the current VMCS, in vCPU free paths
* [PATCH v3 11/15] KVM: nVMX: Don't explicitly VMCLEAR+free() vmcs01's shadow VMCS when freeing vCPU
* [PATCH v3 12/15] KVM: nVMX: Cancel the nested VMX Preemption Timer's hrtimer in free_nested()
* [PATCH v3 13/15] KVM: VMX: Explicitly remove dying vCPUs from the PI wakeup list
* [PATCH v3 14/15] KVM: nVMX: Don't switch to vmcs01 when freeing a vCPU
* [PATCH v3 15/15] KVM: nVMX: Don't load+put the vCPU when freeing nested state for a dying vCPU
and found the following issue:
WARNING in vt_vcpu_pi_remove_from_wakeup_list
Full report is available here:
https://ci.syzbot.org/series/c648fc23-f4c2-4346-93cd-b717ef38b27c
***
WARNING in vt_vcpu_pi_remove_from_wakeup_list
tree: kvm-next
URL: https://kernel.googlesource.com/pub/scm/virt/kvm/kvm/
base: 7e60162b16f9da0d2fa83da5c1723b6853a0ac1d
arch: amd64
compiler: Debian clang version 22.1.8 (1~deb13u4), Debian LLD 22.1.8
config: https://ci.syzbot.org/builds/e4ed6c4f-9b58-4549-9ac6-525add47e472/config
syz repro: https://ci.syzbot.org/findings/320f356a-c5e9-41ff-b8f5-3f0866e300b7/syz_repro
------------[ cut here ]------------
refcount_read(&vcpu->kvm->users_count)
WARNING: arch/x86/kvm/vmx/posted_intr.c:154 at vt_vcpu_pi_remove_from_wakeup_list+0x142/0x1f0 arch/x86/kvm/vmx/posted_intr.c:154, CPU#1: syz.2.19/5870
Modules linked in:
CPU: 1 UID: 0 PID: 5870 Comm: syz.2.19 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:vt_vcpu_pi_remove_from_wakeup_list+0x142/0x1f0 arch/x86/kvm/vmx/posted_intr.c:154
Code: 48 85 db 74 2d e8 ee 1b 64 00 eb 0c e8 e7 1b 64 00 eb 05 e8 e0 1b 64 00 5b 41 5e 41 5f 5d e9 c5 b5 87 0a cc e8 cf 1b 64 00 90 <0f> 0b 90 e9 2b ff ff ff e8 c1 1b 64 00 e8 ac 60 cf ff eb da 44 89
RSP: 0018:ffffc900037cf8c8 EFLAGS: 00010293
RAX: ffffffff816404a1 RBX: ffff88816ada8000 RCX: ffff888110eb0000
RDX: 0000000000000000 RSI: 0000000000000003 RDI: 0000000000000000
RBP: 0000000000000003 R08: ffff88810fb01f83 R09: 1ffff11021f603f0
R10: dffffc0000000000 R11: ffffed1021f603f1 R12: dffffc0000000000
R13: 00000000fffffff8 R14: ffff88810fb01f80 R15: dffffc0000000000
FS: 00007f9e487ab6c0(0000) GS:ffff8882a7cbf000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f07a7ed4870 CR3: 000000010eea9000 CR4: 0000000000352ef0
Call Trace:
<TASK>
vmx_vcpu_free+0x28/0x400 arch/x86/kvm/vmx/vmx.c:7517
kvm_arch_vcpu_destroy+0x154/0x380 arch/x86/kvm/x86.c:9556
kvm_vm_ioctl_create_vcpu+0x76f/0x9f0 virt/kvm/kvm_main.c:4384
kvm_vm_ioctl+0x442/0xcb0 virt/kvm/kvm_main.c:5294
vfs_ioctl fs/ioctl.c:51 [inline]
__do_sys_ioctl fs/ioctl.c:597 [inline]
__se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x166/0x520 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f9e4799f1eb
Code: 00 48 89 44 24 18 31 c0 48 8d 44 24 60 c7 04 24 10 00 00 00 48 89 44 24 08 48 8d 44 24 20 48 89 44 24 10 b8 10 00 00 00 0f 05 <89> c2 3d 00 f0 ff ff 77 1c 48 8b 44 24 18 64 48 2b 04 25 28 00 00
RSP: 002b:00007f9e487aaed0 EFLAGS: 00000246 ORIG_RAX: 0000000000000010
RAX: ffffffffffffffda RBX: 0000000000000003 RCX: 00007f9e4799f1eb
RDX: 0000000000000003 RSI: 000000000000ae41 RDI: 0000000000000085
RBP: 00007f9e47a36b20 R08: 0000000000000000 R09: 0000000000000000
R10: 0000200000c00000 R11: 0000000000000246 R12: 00000000000004ce
R13: 0000200000c00000 R14: 0000200000001040 R15: 00007ffeb7342be8
</TASK>
***
If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
Tested-by: syzbot@syzkaller.appspotmail.com
---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.
To test a fix for this bug, please reply with `#syz test`
(on a separate line) and attach the patch to the email.
Notes:
- The patch will be applied on top of the tested series (as an
incremental fix).
- To test a new version of the whole series, please send it directly
to syzbot@lists.linux.dev.
- Arguments like custom git repos and branches are not supported.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [syzbot ci] Re: KVM: Fix+harden against bad uaccess using dying VM
2026-10-02 20:30 ` [syzbot ci] " syzbot ci
@ 2026-10-02 20:39 ` Sean Christopherson
0 siblings, 0 replies; 23+ messages in thread
From: Sean Christopherson @ 2026-10-02 20:39 UTC (permalink / raw)
To: syzbot ci
Cc: jmattson, kvm, linux-kernel, linuxppc-dev, maddy, npiggin,
pbonzini, syzbot, syzkaller-bugs
On Fri, Oct 02, 2026, syzbot ci wrote:
> ------------[ cut here ]------------
> !__kvm_can_do_uaccess(kvm)
> WARNING: ./include/linux/kvm_host.h:1360 at kvm_can_do_uaccess include/linux/kvm_host.h:1360 [inline], CPU#1: syz.1.18/5858
> WARNING: ./include/linux/kvm_host.h:1360 at kvm_is_guest_access_ok virt/kvm/kvm_main.c:3216 [inline], CPU#1: syz.1.18/5858
> WARNING: ./include/linux/kvm_host.h:1360 at __kvm_read_guest_page+0x38e/0x440 virt/kvm/kvm_main.c:3226, CPU#1: syz.1.18/5858
> Modules linked in:
> CPU: 1 UID: 0 PID: 5858 Comm: syz.1.18 Not tainted syzkaller #0 PREEMPT(full)
> Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
> RIP: 0010:kvm_can_do_uaccess include/linux/kvm_host.h:1360 [inline]
> RIP: 0010:kvm_is_guest_access_ok virt/kvm/kvm_main.c:3216 [inline]
> RIP: 0010:__kvm_read_guest_page+0x38e/0x440 virt/kvm/kvm_main.c:3226
> Code: f2 ff ff ff 0f 44 d8 31 ff e8 5e a4 89 00 89 d8 48 83 c4 30 5b 41 5c 41 5d 41 5e 41 5f 5d e9 09 ac a8 0a cc e8 83 9e 89 00 90 <0f> 0b 90 bb f2 ff ff ff eb da e8 73 9e 89 00 90 0f 0b 90 bb f2 ff
> RSP: 0018:ffffc90003157778 EFLAGS: 00010293
> RAX: ffffffff813e2d22 RBX: 0000000000000000 RCX: ffff888174382580
> RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
> RBP: 1ffff1102217d82a R08: ffff888110bee183 R09: 1ffff1102217dc30
> R10: dffffc0000000000 R11: ffffed102217dc31 R12: ffff888110bee180
> R13: ffff888174382b40 R14: 1ffff1102217dc30 R15: 0000000000000000
> FS: 000055557c0db500(0000) GS:ffff8882a8cda000(0000) knlGS:0000000000000000
> CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
> CR2: 00007f458dfeb840 CR3: 000000016c9d0000 CR4: 0000000000352ef0
> Call Trace:
> <TASK>
> kvm_vcpu_read_guest+0x64/0x140 virt/kvm/kvm_main.c:3284
> nested_vmx_load_msr+0x133/0x4d0 arch/x86/kvm/vmx/nested.c:1107
Oh man. vmx_leave_nested() is so broken. If loading MSRs on nested VM-Exit is
broken (and it obviously is), then storing MSRs on nested VM-Exit is also broken,
i.e. there's at least a second case where nVMX can write to random process memory
on vCPU teardown (shadow vmcs12 being the other one).
It probably makes sense to go straight to open coding punting the vCPU out of L2
in vmx_leave_nested() instead of hack-a-fixing a bunch of flows. I.e. replace
patch 8 and 9 with a proper fix.
^ permalink raw reply [flat|nested] 23+ messages in thread
* [syzbot ci] Re: KVM: Fix+harden against bad uaccess using dying VM
2026-10-01 20:22 [PATCH v2 00/10] " Sean Christopherson
@ 2026-10-02 20:30 ` syzbot ci
2026-10-02 20:39 ` Sean Christopherson
0 siblings, 1 reply; 23+ messages in thread
From: syzbot ci @ 2026-10-02 20:30 UTC (permalink / raw)
To: jmattson, kvm, linux-kernel, linuxppc-dev, maddy, npiggin,
pbonzini, seanjc
Cc: syzbot, syzkaller-bugs
syzbot ci has tested the following series
[v2] KVM: Fix+harden against bad uaccess using dying VM
https://lore.kernel.org/all/20261001202234.3794060-1-seanjc@google.com
* [PATCH v2 01/10] KVM: Reject user accesses to guest memory if current->mm != kvm->mm
* [PATCH v2 02/10] KVM: PPC: Flush/zap all memslots on kvm_arch_flush_shadow_all()
* [PATCH v2 03/10] KVM: x86: Unmap VMAs for KVM-internal memslots when the memslot is freed
* [PATCH v2 04/10] KVM: Disallow setting memslots when the VM is being destroyed
* [PATCH v2 05/10] KVM: Destroy memslots immediately after mmu_notifiers are unregistered
* [PATCH v2 06/10] KVM: WARN if KVM attempts to do guest-related uaccess with "wrong" process
* [PATCH v2 07/10] KVM: WARN and reject guest-based uaccess if VM is dying
* [PATCH v2 08/10] KVM: nVMX: Don't flush shadow VMCS12 to guest memory during vCPU teardown
* [PATCH v2 09/10] KVM: nVMX: Don't try to load eVMCS12 page when the VM is dying
* [PATCH v2 10/10] KVM: Pre-check uaccesses in KVM's APIs to read/write guest memory
and found the following issue:
WARNING in __kvm_read_guest_page
Full report is available here:
https://ci.syzbot.org/series/5f8da53a-c584-4176-abcb-7a29eb02e4c4
***
WARNING in __kvm_read_guest_page
tree: kvm-next
URL: https://kernel.googlesource.com/pub/scm/virt/kvm/kvm/
base: d4b7fb647204f0c81dfeae2d1a708e4d858e0c94
arch: amd64
compiler: Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8
config: https://ci.syzbot.org/builds/4d4c10d0-f56c-448f-8782-ad4dab8f46d0/config
syz repro: https://ci.syzbot.org/findings/dc5f6c42-c3ab-41b0-9384-1a15798e4820/syz_repro
------------[ cut here ]------------
!__kvm_can_do_uaccess(kvm)
WARNING: ./include/linux/kvm_host.h:1360 at kvm_can_do_uaccess include/linux/kvm_host.h:1360 [inline], CPU#1: syz.1.18/5858
WARNING: ./include/linux/kvm_host.h:1360 at kvm_is_guest_access_ok virt/kvm/kvm_main.c:3216 [inline], CPU#1: syz.1.18/5858
WARNING: ./include/linux/kvm_host.h:1360 at __kvm_read_guest_page+0x38e/0x440 virt/kvm/kvm_main.c:3226, CPU#1: syz.1.18/5858
Modules linked in:
CPU: 1 UID: 0 PID: 5858 Comm: syz.1.18 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:kvm_can_do_uaccess include/linux/kvm_host.h:1360 [inline]
RIP: 0010:kvm_is_guest_access_ok virt/kvm/kvm_main.c:3216 [inline]
RIP: 0010:__kvm_read_guest_page+0x38e/0x440 virt/kvm/kvm_main.c:3226
Code: f2 ff ff ff 0f 44 d8 31 ff e8 5e a4 89 00 89 d8 48 83 c4 30 5b 41 5c 41 5d 41 5e 41 5f 5d e9 09 ac a8 0a cc e8 83 9e 89 00 90 <0f> 0b 90 bb f2 ff ff ff eb da e8 73 9e 89 00 90 0f 0b 90 bb f2 ff
RSP: 0018:ffffc90003157778 EFLAGS: 00010293
RAX: ffffffff813e2d22 RBX: 0000000000000000 RCX: ffff888174382580
RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000
RBP: 1ffff1102217d82a R08: ffff888110bee183 R09: 1ffff1102217dc30
R10: dffffc0000000000 R11: ffffed102217dc31 R12: ffff888110bee180
R13: ffff888174382b40 R14: 1ffff1102217dc30 R15: 0000000000000000
FS: 000055557c0db500(0000) GS:ffff8882a8cda000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007f458dfeb840 CR3: 000000016c9d0000 CR4: 0000000000352ef0
Call Trace:
<TASK>
kvm_vcpu_read_guest+0x64/0x140 virt/kvm/kvm_main.c:3284
nested_vmx_load_msr+0x133/0x4d0 arch/x86/kvm/vmx/nested.c:1107
load_vmcs12_host_state+0x10ca/0x1e20 arch/x86/kvm/vmx/nested.c:4930
__nested_vmx_vmexit+0x1800/0x2d10 arch/x86/kvm/vmx/nested.c:5206
nested_vmx_vmexit arch/x86/kvm/vmx/nested.h:46 [inline]
vmx_leave_nested arch/x86/kvm/vmx/nested.c:6891 [inline]
nested_vmx_free_vcpu+0x8e/0xd0 arch/x86/kvm/vmx/nested.c:389
vmx_vcpu_free+0x109/0x3e0 arch/x86/kvm/vmx/vmx.c:7653
kvm_arch_vcpu_destroy+0x154/0x380 arch/x86/kvm/x86.c:9463
kvm_vcpu_destroy virt/kvm/kvm_main.c:469 [inline]
kvm_destroy_vcpus+0x123/0x380 virt/kvm/kvm_main.c:489
kvm_arch_destroy_vm+0x55/0x120 arch/x86/kvm/x86.c:10014
kvm_destroy_vm virt/kvm/kvm_main.c:1326 [inline]
kvm_put_kvm+0x93a/0xc80 virt/kvm/kvm_main.c:1359
kvm_vm_release+0x43/0x50 virt/kvm/kvm_main.c:1382
__fput+0x418/0xa50 fs/file_table.c:512
task_work_run+0x1d9/0x270 kernel/task_work.c:233
resume_user_mode_work include/linux/resume_user_mode.h:50 [inline]
__exit_to_user_mode_loop kernel/entry/common.c:70 [inline]
exit_to_user_mode_loop+0x204/0x770 kernel/entry/common.c:101
__exit_to_user_mode_prepare include/linux/irq-entry-common.h:207 [inline]
syscall_exit_to_user_mode_prepare include/linux/irq-entry-common.h:230 [inline]
syscall_exit_to_user_mode include/linux/entry-common.h:336 [inline]
do_syscall_64+0x328/0x520 arch/x86/entry/syscall_64.c:89
entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f7fec79e159
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007fff0d2850c8 EFLAGS: 00000246 ORIG_RAX: 00000000000001b4
RAX: 0000000000000000 RBX: 00007f7feca27da0 RCX: 00007f7fec79e159
RDX: 0000000000000000 RSI: 000000000000001e RDI: 0000000000000003
RBP: 00007f7feca27da0 R08: 00007f7feca26038 R09: 0000000000000000
R10: 000000000003fdb8 R11: 0000000000000246 R12: 000000000000ec04
R13: 00007f7feca25fac R14: 000000000000e938 R15: 00007fff0d2851d0
</TASK>
***
If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
Tested-by: syzbot@syzkaller.appspotmail.com
---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.
To test a fix for this bug, please reply with `#syz test`
(on a separate line) and attach the patch to the email.
Notes:
- The patch will be applied on top of the tested series (as an
incremental fix).
- To test a new version of the whole series, please send it directly
to syzbot@lists.linux.dev.
- Arguments like custom git repos and branches are not supported.
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2026-10-28 9:18 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
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: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
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 ` [PATCH v3 05/15] KVM: Disallow setting memslots when the VM is being destroyed Sean Christopherson
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 ` [PATCH v3 07/15] KVM: WARN if KVM attempts to do guest-related uaccess with "wrong" process Sean Christopherson
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
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
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
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 ` [PATCH v3 12/15] KVM: nVMX: Cancel the nested VMX Preemption Timer's hrtimer in free_nested() Sean Christopherson
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 ` [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
2026-10-28 9:18 ` [syzbot ci] Re: KVM: Fix+harden against bad uaccess using dying VM syzbot ci
-- strict thread matches above, loose matches on Subject: below --
2026-10-01 20:22 [PATCH v2 00/10] " Sean Christopherson
2026-10-02 20:30 ` [syzbot ci] " syzbot ci
2026-10-02 20:39 ` Sean Christopherson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®