* [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM
@ 2026-10-30 21:20 Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 01/23] KVM: Reject user accesses to guest memory if current->mm != kvm->mm Sean Christopherson
` (22 more replies)
0 siblings, 23 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:20 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Stupid SMM. Patches 1-3 need to go to 7.4, everything else can and should wait.
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-(I lost track and stopped caring) 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. The big pain in the ass is x86's SMM, or more specifically
x86's internal memslots colliding with SMM, where each internal memslot is
actual *two* internal memslots. KVM currently uses a single VMA for both,
which means KVM can't automatically unmap the VMA when the memslot is freed
without relying on arbitrary ordering. And there are several other bugs in
the related code.
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 after that 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.
v4:
- Fix the SMM+internal memslots issues. [Sashiko]
- Massage the vt_vcpu_pi_remove_from_wakeup_list() WARN so that it isn't
triggered if creating a vCPU fails. [Sashiko]
- Use "unsigned long" instead of "int" for kvm_is_guest_access_ok() params.
[James]
- Collect reviews. [James]
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 (23):
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: Drop dead check on an existing memslot not having any pages
KVM: WARN and error out if memslot update encounters existing slot
with no pages
KVM: x86: Unmap VMAs for KVM-internal memslots when the memslot is
freed
KVM: x86: Delete non-SMM internal memslot if creating SMM memslot
fails
KVM: x86: Process all address spaces when deleting internal memslot
KVM: x86: Re-queue APICv update request if inhibiting APIC access page
fails
KVM: x86: Map a separate VMA for each address space for internal
memslots
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: Add lockdep-only APIs to query and assert that a vCPU is
unreachable
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
KVM: x86: Rename __x86_set_memory_region() =>
x86_set_internal_memslot()
arch/powerpc/include/asm/kvm_host.h | 1 -
arch/powerpc/kvm/powerpc.c | 11 +++
arch/x86/kvm/lapic.c | 42 ++++++-----
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 | 31 ++++----
arch/x86/kvm/x86.c | 110 +++++++++++++++-------------
arch/x86/kvm/x86.h | 4 +-
include/linux/kvm_host.h | 45 +++++++++++-
virt/kvm/kvm_main.c | 94 +++++++++++++++---------
12 files changed, 298 insertions(+), 180 deletions(-)
base-commit: 7e60162b16f9da0d2fa83da5c1723b6853a0ac1d
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 01/23] KVM: Reject user accesses to guest memory if current->mm != kvm->mm
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
@ 2026-10-30 21:20 ` Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 02/23] KVM: nVMX: Don't force a full nested VM-Exit when destroying a vCPU Sean Christopherson
` (21 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:20 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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 02/23] KVM: nVMX: Don't force a full nested VM-Exit when destroying a vCPU
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 01/23] KVM: Reject user accesses to guest memory if current->mm != kvm->mm Sean Christopherson
@ 2026-10-30 21:20 ` Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 03/23] KVM: PPC: Flush/zap all memslots on kvm_arch_flush_shadow_all() Sean Christopherson
` (20 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:20 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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 03/23] KVM: PPC: Flush/zap all memslots on kvm_arch_flush_shadow_all()
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 01/23] KVM: Reject user accesses to guest memory if current->mm != kvm->mm Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 02/23] KVM: nVMX: Don't force a full nested VM-Exit when destroying a vCPU Sean Christopherson
@ 2026-10-30 21:20 ` Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 04/23] KVM: x86: Drop dead check on an existing memslot not having any pages Sean Christopherson
` (19 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:20 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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 04/23] KVM: x86: Drop dead check on an existing memslot not having any pages
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (2 preceding siblings ...)
2026-10-30 21:20 ` [PATCH v4 03/23] KVM: PPC: Flush/zap all memslots on kvm_arch_flush_shadow_all() Sean Christopherson
@ 2026-10-30 21:20 ` Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 05/23] KVM: WARN and error out if memslot update encounters existing slot with no pages Sean Christopherson
` (18 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:20 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 should-be-impossible check on an existing memslot not having any
pages. Ever since commit 0577d1abe704 ("KVM: Terminate memslot walks via
used_slots"), KVM returns NULL if there is no memslot. Before that, KVM
pre-allocated all possible memslots and relied on npages==0 to denote "no
valid memslot". The check on npages was deliberately left behind to aid
debug, but that was over six years ago.
Side topic, there is no need to check for KVM_MEMSLOT_INVALID as it
should be impossible to see an invalid slot while holding slots_lock, and
more importantly, invalid slots preserve their original size, i.e. the
npages check has nothing to do with to-be-deleted/moved memslots.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 32563a91fba9..cb8b60da5b11 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -10043,7 +10043,7 @@ void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
slot = id_to_memslot(slots, id);
if (size) {
- if (slot && slot->npages)
+ if (slot)
return ERR_PTR_USR(-EEXIST);
/*
@@ -10055,7 +10055,7 @@ void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
if (IS_ERR_VALUE(hva))
return (void __user *)hva;
} else {
- if (!slot || !slot->npages)
+ if (!slot)
return NULL;
old_npages = slot->npages;
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 05/23] KVM: WARN and error out if memslot update encounters existing slot with no pages
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (3 preceding siblings ...)
2026-10-30 21:20 ` [PATCH v4 04/23] KVM: x86: Drop dead check on an existing memslot not having any pages Sean Christopherson
@ 2026-10-30 21:20 ` Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 06/23] KVM: x86: Unmap VMAs for KVM-internal memslots when the memslot is freed Sean Christopherson
` (17 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:20 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 return -EIO if an existing memslot with npages=0 is encountered
when updating memslots, as KVM returns NULL if there is no memslot since
commit 0577d1abe704 ("KVM: Terminate memslot walks via used_slots". Before
that, KVM pre-allocated all possible memslots and relied on npages==0 to
denote "no valid memslot". The check on npages was deliberately left
behind to aid debug, but that was over six years ago.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
virt/kvm/kvm_main.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 218456372b11..dcd28b45ef30 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -2086,9 +2086,11 @@ static int kvm_set_memory_region(struct kvm *kvm,
* and/or destroyed by kvm_set_memslot().
*/
old = id_to_memslot(slots, id);
+ if (WARN_ON_ONCE(old && !old->npages))
+ return -EIO;
if (!mem->memory_size) {
- if (!old || !old->npages)
+ if (!old)
return -EINVAL;
if (WARN_ON_ONCE(kvm->nr_memslot_pages < old->npages))
@@ -2100,7 +2102,7 @@ static int kvm_set_memory_region(struct kvm *kvm,
base_gfn = (mem->guest_phys_addr >> PAGE_SHIFT);
npages = (mem->memory_size >> PAGE_SHIFT);
- if (!old || !old->npages) {
+ if (!old) {
change = KVM_MR_CREATE;
/*
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 06/23] KVM: x86: Unmap VMAs for KVM-internal memslots when the memslot is freed
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (4 preceding siblings ...)
2026-10-30 21:20 ` [PATCH v4 05/23] KVM: WARN and error out if memslot update encounters existing slot with no pages Sean Christopherson
@ 2026-10-30 21:20 ` Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 07/23] KVM: x86: Delete non-SMM internal memslot if creating SMM memslot fails Sean Christopherson
` (16 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:20 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().
Free the VMA only when deleting the memslot from the "last" address space,
where "last" is the highest numbered address space. KVM currently uses a
single VMA for both non-SMM and SMM memslots, and deletes the memslots in
ascending numerical order, i.e. deletes the SMM memslot last, if SMM is
enabled. Freeing the VMA on the non-SMM memslot would technically regress
inhibiting the APIC access page, which is the only case where KVM deletes
an internal memslot on a live VM, as accessing the SMM memslot after the
VMA is removed would result in KVM_RUN failing with -EFAULT.
Add a TODO to call out that unmapping only the "last" memslot is a hack,
along with a hint/suggestion for how the hack can be removed.
CC: James Houghton <jthoughton@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 39 +++++++++++++++++----------------------
1 file changed, 17 insertions(+), 22 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index cb8b60da5b11..489631686ba6 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)
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,19 @@ 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.
+ * 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.
+ *
+ * TODO: Allocate VMAs for each address space and drop the check on the
+ * as_id, which depends on freeing memslots from 0=>N-1.
+ */
+ if (current->mm == kvm->mm && slot->id >= KVM_USER_MEM_SLOTS &&
+ slot->as_id == (kvm_arch_nr_memslot_as_ids(kvm) - 1))
+ vm_munmap(slot->userspace_addr, slot->npages * PAGE_SIZE);
}
int memslot_rmap_alloc(struct kvm_memory_slot *slot, unsigned long npages)
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 07/23] KVM: x86: Delete non-SMM internal memslot if creating SMM memslot fails
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (5 preceding siblings ...)
2026-10-30 21:20 ` [PATCH v4 06/23] KVM: x86: Unmap VMAs for KVM-internal memslots when the memslot is freed Sean Christopherson
@ 2026-10-30 21:20 ` Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 08/23] KVM: x86: Process all address spaces when deleting internal memslot Sean Christopherson
` (15 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:20 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
When installing a KVM-internal memslot, delete the non-SMM memslot if
creating the SMM memslot fails. Leaving the non-SMM memslot in-place
makes it impossible for userspace to recover (not that any VMM tries), as
subsequent tries to install the internal memslot will fail with -EEXIST.
Fixes: 9da0e4d5ac96 ("KVM: x86: work on all available address spaces")
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 32 +++++++++++++++++++++++---------
1 file changed, 23 insertions(+), 9 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 489631686ba6..326f8a9a5d18 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -10006,6 +10006,19 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
return ret;
}
+static int x86_set_internal_memslot(struct kvm *kvm, int id, int as_id,
+ gpa_t gpa, unsigned long hva, u32 size)
+{
+ struct kvm_userspace_memory_region2 m;
+
+ m.slot = id | (as_id << 16);
+ m.flags = 0;
+ m.guest_phys_addr = gpa;
+ m.userspace_addr = hva;
+ m.memory_size = size;
+ return kvm_set_internal_memslot(kvm, &m);
+}
+
/**
* __x86_set_memory_region: Setup KVM internal memory slot
*
@@ -10062,16 +10075,17 @@ void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
}
for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) {
- struct kvm_userspace_memory_region2 m;
+ r = x86_set_internal_memslot(kvm, id, i, gpa, hva, size);
+ if (!r)
+ continue;
- m.slot = id | (i << 16);
- m.flags = 0;
- m.guest_phys_addr = gpa;
- m.userspace_addr = hva;
- m.memory_size = size;
- r = kvm_set_internal_memslot(kvm, &m);
- if (r < 0)
- return ERR_PTR_USR(r);
+ if (size) {
+ for (i--; i >= 0; i--)
+ x86_set_internal_memslot(kvm, id, i, gpa, 0, 0);
+
+ vm_munmap(hva, size);
+ }
+ return ERR_PTR_USR(r);
}
return (void __user *)hva;
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 08/23] KVM: x86: Process all address spaces when deleting internal memslot
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (6 preceding siblings ...)
2026-10-30 21:20 ` [PATCH v4 07/23] KVM: x86: Delete non-SMM internal memslot if creating SMM memslot fails Sean Christopherson
@ 2026-10-30 21:20 ` Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 09/23] KVM: x86: Re-queue APICv update request if inhibiting APIC access page fails Sean Christopherson
` (14 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:20 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Process all address spaces when deleting a KVM-internal memslot, i.e. try
to delete the SMM memslot (if SMM is enabled) even if there is no non-SMM
memslot. This will allow retrying deletion when inhibiting the APIC access
page (the only scenario where KVM deletes memslots while the VM is alive).
Fixes: 9da0e4d5ac96 ("KVM: x86: work on all available address spaces")
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 15 ++++++---------
1 file changed, 6 insertions(+), 9 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 326f8a9a5d18..0b94f981a2e1 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -10011,6 +10011,9 @@ static int x86_set_internal_memslot(struct kvm *kvm, int id, int as_id,
{
struct kvm_userspace_memory_region2 m;
+ if (!size && !id_to_memslot(__kvm_memslots(kvm, as_id), id))
+ return 0;
+
m.slot = id | (as_id << 16);
m.flags = 0;
m.guest_phys_addr = gpa;
@@ -10044,8 +10047,6 @@ static int x86_set_internal_memslot(struct kvm *kvm, int id, int as_id,
void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
u32 size)
{
- struct kvm_memslots *slots = kvm_memslots(kvm);
- struct kvm_memory_slot *slot;
unsigned long hva;
int i, r;
@@ -10054,11 +10055,10 @@ void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
if (WARN_ON(id >= KVM_MEM_SLOTS_NUM))
return ERR_PTR_USR(-EINVAL);
- slot = id_to_memslot(slots, id);
+ if (size && id_to_memslot(kvm_memslots(kvm), id))
+ return ERR_PTR_USR(-EEXIST);
+
if (size) {
- if (slot)
- return ERR_PTR_USR(-EEXIST);
-
/*
* MAP_SHARED to prevent internal slot pages from being moved
* by fork()/COW.
@@ -10068,9 +10068,6 @@ void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
if (IS_ERR_VALUE(hva))
return (void __user *)hva;
} else {
- if (!slot)
- return NULL;
-
hva = 0;
}
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 09/23] KVM: x86: Re-queue APICv update request if inhibiting APIC access page fails
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (7 preceding siblings ...)
2026-10-30 21:20 ` [PATCH v4 08/23] KVM: x86: Process all address spaces when deleting internal memslot Sean Christopherson
@ 2026-10-30 21:20 ` Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 10/23] KVM: x86: Map a separate VMA for each address space for internal memslots Sean Christopherson
` (13 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:20 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Re-queue an APICv update request if deleting the APIC access page memslot
fails when trying to (permanently) inhibit the APIC access page. Sadly,
memslot deletion requires a memory allocation and so can fail even if KVM
does everything right.
Fixes: 2008fab34530 ("KVM: x86: Inhibit APIC memslot if x2APIC and AVIC are enabled")
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/lapic.c | 38 ++++++++++++++++++++++----------------
1 file changed, 22 insertions(+), 16 deletions(-)
diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c
index ecf611845fb1..c20e2a25834f 100644
--- a/arch/x86/kvm/lapic.c
+++ b/arch/x86/kvm/lapic.c
@@ -2931,6 +2931,7 @@ EXPORT_SYMBOL_FOR_KVM_INTERNAL(kvm_alloc_apic_access_page);
void kvm_inhibit_apic_access_page(struct kvm_vcpu *vcpu)
{
struct kvm *kvm = vcpu->kvm;
+ void __user *p;
if (!kvm->arch.apic_access_memslot_enabled)
return;
@@ -2939,25 +2940,30 @@ void kvm_inhibit_apic_access_page(struct kvm_vcpu *vcpu)
mutex_lock(&kvm->slots_lock);
- if (kvm->arch.apic_access_memslot_enabled) {
- __x86_set_memory_region(kvm, APIC_ACCESS_PAGE_PRIVATE_MEMSLOT, 0, 0);
- /*
- * Clear "enabled" after the memslot is deleted so that a
- * different vCPU doesn't get a false negative when checking
- * the flag out of slots_lock. No additional memory barrier is
- * needed as modifying memslots requires waiting other vCPUs to
- * drop SRCU (see above), and false positives are ok as the
- * flag is rechecked after acquiring slots_lock.
- */
- kvm->arch.apic_access_memslot_enabled = false;
+ if (!kvm->arch.apic_access_memslot_enabled)
+ goto out;
- /*
- * Mark the memslot as inhibited to prevent reallocating the
- * memslot during vCPU creation, e.g. if a vCPU is hotplugged.
- */
- kvm->arch.apic_access_memslot_inhibited = true;
+ p = __x86_set_memory_region(kvm, APIC_ACCESS_PAGE_PRIVATE_MEMSLOT, 0, 0);
+ if (IS_ERR(p)) {
+ kvm_make_request(KVM_REQ_APICV_UPDATE, vcpu);
+ goto out;
}
+ /*
+ * Clear "enabled" after the memslot is deleted so that a different vCPU
+ * doesn't get a false negative when checking the flag out of slots_lock.
+ * No additional memory barrier is needed as modifying memslots requires
+ * waiting other vCPUs to drop SRCU (see above), and false positives are
+ * ok as the flag is rechecked after acquiring slots_lock.
+ */
+ kvm->arch.apic_access_memslot_enabled = false;
+
+ /*
+ * Mark the memslot as inhibited to prevent reallocating the memslot
+ * during vCPU creation, e.g. if a vCPU is hotplugged.
+ */
+ kvm->arch.apic_access_memslot_inhibited = true;
+out:
mutex_unlock(&kvm->slots_lock);
kvm_vcpu_srcu_read_lock(vcpu);
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 10/23] KVM: x86: Map a separate VMA for each address space for internal memslots
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (8 preceding siblings ...)
2026-10-30 21:20 ` [PATCH v4 09/23] KVM: x86: Re-queue APICv update request if inhibiting APIC access page fails Sean Christopherson
@ 2026-10-30 21:20 ` Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 11/23] KVM: Disallow setting memslots when the VM is being destroyed Sean Christopherson
` (12 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:20 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Use a separate VMA for each address space when installing internal memslots,
i.e. mmap() a separate VMA for the SMM memslot, and delete the TODO along
with the hack to only unmap() the SMM memslot when freeing memslots. Use
an explicit shmem file (as opposed to the implicit shmem file created for
an anonymous MAP_SHARED mapping) so that both memslots' VMAs point at the
same backing page.
Note, using shmem_kernel_file_setup() directly is more or less equivalent
to shmem_zero_setup(), which is used for "MAP_SHARED | MAP_ANONYMOUS"
mappings; the only practical difference is the dentry name.
Return the virtual address of the non-SMM memslot mapping, as that memslot
is guaranteed to exist.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/x86.c | 62 +++++++++++++++++++++++++---------------------
1 file changed, 34 insertions(+), 28 deletions(-)
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 0b94f981a2e1..8cb58040dd81 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -59,6 +59,7 @@
#include <linux/irqbypass.h>
#include <linux/sched/stat.h>
#include <linux/sched/isolation.h>
+#include <linux/shmem_fs.h>
#include <linux/mem_encrypt.h>
#include <linux/suspend.h>
#include <linux/smp.h>
@@ -10007,9 +10008,19 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
}
static int x86_set_internal_memslot(struct kvm *kvm, int id, int as_id,
- gpa_t gpa, unsigned long hva, u32 size)
+ gpa_t gpa, struct file *f, u32 size)
{
struct kvm_userspace_memory_region2 m;
+ unsigned long hva;
+ int r;
+
+ if (f) {
+ hva = vm_mmap(f, 0, size, PROT_READ | PROT_WRITE, MAP_SHARED, 0);
+ if (IS_ERR_VALUE(hva))
+ return (long)hva;
+ } else {
+ hva = 0;
+ }
if (!size && !id_to_memslot(__kvm_memslots(kvm, as_id), id))
return 0;
@@ -10019,7 +10030,11 @@ static int x86_set_internal_memslot(struct kvm *kvm, int id, int as_id,
m.guest_phys_addr = gpa;
m.userspace_addr = hva;
m.memory_size = size;
- return kvm_set_internal_memslot(kvm, &m);
+
+ r = kvm_set_internal_memslot(kvm, &m);
+ if (r && f)
+ vm_munmap(hva, size);
+ return r;
}
/**
@@ -10047,7 +10062,8 @@ static int x86_set_internal_memslot(struct kvm *kvm, int id, int as_id,
void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
u32 size)
{
- unsigned long hva;
+ struct file *f __free(fput) = NULL;
+ struct kvm_memory_slot *slot;
int i, r;
lockdep_assert_held(&kvm->slots_lock);
@@ -10055,37 +10071,31 @@ void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
if (WARN_ON(id >= KVM_MEM_SLOTS_NUM))
return ERR_PTR_USR(-EINVAL);
- if (size && id_to_memslot(kvm_memslots(kvm), id))
- return ERR_PTR_USR(-EEXIST);
-
if (size) {
- /*
- * MAP_SHARED to prevent internal slot pages from being moved
- * by fork()/COW.
- */
- hva = vm_mmap(NULL, 0, size, PROT_READ | PROT_WRITE,
- MAP_SHARED | MAP_ANONYMOUS, 0);
- if (IS_ERR_VALUE(hva))
- return (void __user *)hva;
- } else {
- hva = 0;
+ if (id_to_memslot(kvm_memslots(kvm), id))
+ return ERR_PTR_USR(-EEXIST);
+
+ f = shmem_kernel_file_setup("kvm_memslot", size, EMPTY_VMA_FLAGS);
+ if (IS_ERR(f))
+ return (void __user *)f;
}
for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) {
- r = x86_set_internal_memslot(kvm, id, i, gpa, hva, size);
+ r = x86_set_internal_memslot(kvm, id, i, gpa, f, size);
if (!r)
continue;
- if (size) {
- for (i--; i >= 0; i--)
- x86_set_internal_memslot(kvm, id, i, gpa, 0, 0);
+ for (i--; i >= 0 && size; i--)
+ x86_set_internal_memslot(kvm, id, i, gpa, NULL, 0);
- vm_munmap(hva, size);
- }
return ERR_PTR_USR(r);
}
- return (void __user *)hva;
+ slot = id_to_memslot(kvm_memslots(kvm), id);
+ if (WARN_ON_ONCE(!!size != !!slot))
+ return ERR_PTR_USR(-EIO);
+
+ return (void __user *)(slot ? slot->userspace_addr : 0);
}
EXPORT_SYMBOL_FOR_KVM_INTERNAL(__x86_set_memory_region);
@@ -10158,12 +10168,8 @@ void kvm_arch_free_memslot(struct kvm *kvm, struct kvm_memory_slot *slot)
* unless the memory map has changed due to process exit or fd copying.
* 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.
- *
- * TODO: Allocate VMAs for each address space and drop the check on the
- * as_id, which depends on freeing memslots from 0=>N-1.
*/
- if (current->mm == kvm->mm && slot->id >= KVM_USER_MEM_SLOTS &&
- slot->as_id == (kvm_arch_nr_memslot_as_ids(kvm) - 1))
+ if (current->mm == kvm->mm && slot->id >= KVM_USER_MEM_SLOTS)
vm_munmap(slot->userspace_addr, slot->npages * PAGE_SIZE);
}
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 11/23] KVM: Disallow setting memslots when the VM is being destroyed
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (9 preceding siblings ...)
2026-10-30 21:20 ` [PATCH v4 10/23] KVM: x86: Map a separate VMA for each address space for internal memslots Sean Christopherson
@ 2026-10-30 21:20 ` Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 12/23] KVM: Destroy memslots immediately after mmu_notifiers are unregistered Sean Christopherson
` (11 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:20 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 dcd28b45ef30..fa548b12228f 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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 12/23] KVM: Destroy memslots immediately after mmu_notifiers are unregistered
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (10 preceding siblings ...)
2026-10-30 21:20 ` [PATCH v4 11/23] KVM: Disallow setting memslots when the VM is being destroyed Sean Christopherson
@ 2026-10-30 21:21 ` Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 13/23] KVM: WARN if KVM attempts to do guest-related uaccess with "wrong" process Sean Christopherson
` (10 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:21 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 fa548b12228f..2eda12a52bf4 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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 13/23] KVM: WARN if KVM attempts to do guest-related uaccess with "wrong" process
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (11 preceding siblings ...)
2026-10-30 21:21 ` [PATCH v4 12/23] KVM: Destroy memslots immediately after mmu_notifiers are unregistered Sean Christopherson
@ 2026-10-30 21:21 ` Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 14/23] KVM: WARN and reject guest-based uaccess if VM is dying Sean Christopherson
` (9 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:21 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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 14/23] KVM: WARN and reject guest-based uaccess if VM is dying
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (12 preceding siblings ...)
2026-10-30 21:21 ` [PATCH v4 13/23] KVM: WARN if KVM attempts to do guest-related uaccess with "wrong" process Sean Christopherson
@ 2026-10-30 21:21 ` Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 15/23] KVM: Pre-check uaccesses in KVM's APIs to read/write guest memory Sean Christopherson
` (8 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:21 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.
Reviewed-by: James Houghton <jthoughton@google.com>
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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 15/23] KVM: Pre-check uaccesses in KVM's APIs to read/write guest memory
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (13 preceding siblings ...)
2026-10-30 21:21 ` [PATCH v4 14/23] KVM: WARN and reject guest-based uaccess if VM is dying Sean Christopherson
@ 2026-10-30 21:21 ` Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 16/23] KVM: VMX: Explicility free vmcs01, not the current VMCS, in vCPU free paths Sean Christopherson
` (7 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:21 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 are 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.
Reviewed-by: James Houghton <jthoughton@google.com>
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
virt/kvm/kvm_main.c | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 2eda12a52bf4..c6af11cea296 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -3300,6 +3300,12 @@ static int next_segment(unsigned long len, int offset)
return len;
}
+static bool kvm_is_guest_access_ok(struct kvm *kvm, unsigned long offset,
+ unsigned long len, unsigned long 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)
@@ -3307,7 +3313,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);
@@ -3383,7 +3389,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);
@@ -3416,7 +3422,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);
@@ -3546,7 +3552,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) {
@@ -3584,7 +3590,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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 16/23] KVM: VMX: Explicility free vmcs01, not the current VMCS, in vCPU free paths
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (14 preceding siblings ...)
2026-10-30 21:21 ` [PATCH v4 15/23] KVM: Pre-check uaccesses in KVM's APIs to read/write guest memory Sean Christopherson
@ 2026-10-30 21:21 ` Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 17/23] KVM: nVMX: Don't explicitly VMCLEAR+free() vmcs01's shadow VMCS when freeing vCPU Sean Christopherson
` (6 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:21 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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 17/23] KVM: nVMX: Don't explicitly VMCLEAR+free() vmcs01's shadow VMCS when freeing vCPU
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (15 preceding siblings ...)
2026-10-30 21:21 ` [PATCH v4 16/23] KVM: VMX: Explicility free vmcs01, not the current VMCS, in vCPU free paths Sean Christopherson
@ 2026-10-30 21:21 ` Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 18/23] KVM: nVMX: Cancel the nested VMX Preemption Timer's hrtimer in free_nested() Sean Christopherson
` (5 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:21 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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 18/23] KVM: nVMX: Cancel the nested VMX Preemption Timer's hrtimer in free_nested()
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (16 preceding siblings ...)
2026-10-30 21:21 ` [PATCH v4 17/23] KVM: nVMX: Don't explicitly VMCLEAR+free() vmcs01's shadow VMCS when freeing vCPU Sean Christopherson
@ 2026-10-30 21:21 ` Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 19/23] KVM: Add lockdep-only APIs to query and assert that a vCPU is unreachable Sean Christopherson
` (4 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:21 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 a 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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 19/23] KVM: Add lockdep-only APIs to query and assert that a vCPU is unreachable
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (17 preceding siblings ...)
2026-10-30 21:21 ` [PATCH v4 18/23] KVM: nVMX: Cancel the nested VMX Preemption Timer's hrtimer in free_nested() Sean Christopherson
@ 2026-10-30 21:21 ` Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 20/23] KVM: VMX: Explicitly remove dying vCPUs from the PI wakeup list Sean Christopherson
` (3 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:21 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 an APIs to assert that a vCPU is unreachable, along with a helper to
deduplicate the "is unreachable" logic with the existing "assert the vCPU
is locked or unreachable". The new assert API will be used by VMX to
ensure a not-so-innocuous vCPU teardown helper is really only used when
the vCPU is unreachable and being destroyed.
Disallow using the query API without lockdep to prevent it from being used
to make control-flow decisions in KVM. Taking different actions based on
whether or not a vCPU is reachable isn't fundamentally broken, but would
all but guarantee other code _is_ flawed and/or brittle, e.g. that unwind
error code relies on common functionality to behave differently.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
include/linux/kvm_host.h | 19 +++++++++++++++++--
1 file changed, 17 insertions(+), 2 deletions(-)
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index ba8ea62b7304..28a0c01d6a55 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -984,11 +984,26 @@ static inline struct kvm_io_bus *kvm_get_bus(struct kvm *kvm, enum kvm_bus idx)
lockdep_is_held(&kvm->slots_lock));
}
+static inline bool kvm_is_vcpu_unreachable(struct kvm_vcpu *vcpu)
+{
+ /*
+ * There is no actual dependency on lockdep, but generally speaking KVM
+ * shouldn't be making runtime decisions based on whether or not a vCPU
+ * is reachable.
+ */
+ BUILD_BUG_ON(!IS_ENABLED(CONFIG_LOCKDEP));
+ return vcpu->vcpu_idx < 0 || !refcount_read(&vcpu->kvm->users_count);
+}
+
+static inline void kvm_lockdep_assert_vcpu_is_unreachable(struct kvm_vcpu *vcpu)
+{
+ lockdep_assert_once(kvm_is_vcpu_unreachable(vcpu));
+}
+
static inline void kvm_lockdep_assert_vcpu_is_locked_or_unreachable(struct kvm_vcpu *vcpu)
{
lockdep_assert_once(lockdep_is_held(&vcpu->mutex) ||
- vcpu->vcpu_idx < 0 ||
- !refcount_read(&vcpu->kvm->users_count));
+ kvm_is_vcpu_unreachable(vcpu));
}
static inline struct kvm_vcpu *kvm_get_vcpu(struct kvm *kvm, int i)
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 20/23] KVM: VMX: Explicitly remove dying vCPUs from the PI wakeup list
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (18 preceding siblings ...)
2026-10-30 21:21 ` [PATCH v4 19/23] KVM: Add lockdep-only APIs to query and assert that a vCPU is unreachable Sean Christopherson
@ 2026-10-30 21:21 ` Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 21/23] KVM: nVMX: Don't switch to vmcs01 when freeing a vCPU Sean Christopherson
` (2 subsequent siblings)
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:21 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.
Reviewed-by: James Houghton <jthoughton@google.com>
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..4406e344ae6a 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)
+{
+ kvm_lockdep_assert_vcpu_is_unreachable(vcpu);
+
+ 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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 21/23] KVM: nVMX: Don't switch to vmcs01 when freeing a vCPU
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (19 preceding siblings ...)
2026-10-30 21:21 ` [PATCH v4 20/23] KVM: VMX: Explicitly remove dying vCPUs from the PI wakeup list Sean Christopherson
@ 2026-10-30 21:21 ` Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 22/23] KVM: nVMX: Don't load+put the vCPU when freeing nested state for a dying vCPU Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 23/23] KVM: x86: Rename __x86_set_memory_region() => x86_set_internal_memslot() Sean Christopherson
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:21 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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 22/23] KVM: nVMX: Don't load+put the vCPU when freeing nested state for a dying vCPU
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (20 preceding siblings ...)
2026-10-30 21:21 ` [PATCH v4 21/23] KVM: nVMX: Don't switch to vmcs01 when freeing a vCPU Sean Christopherson
@ 2026-10-30 21:21 ` Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 23/23] KVM: x86: Rename __x86_set_memory_region() => x86_set_internal_memslot() Sean Christopherson
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:21 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.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
* [PATCH v4 23/23] KVM: x86: Rename __x86_set_memory_region() => x86_set_internal_memslot()
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
` (21 preceding siblings ...)
2026-10-30 21:21 ` [PATCH v4 22/23] KVM: nVMX: Don't load+put the vCPU when freeing nested state for a dying vCPU Sean Christopherson
@ 2026-10-30 21:21 ` Sean Christopherson
22 siblings, 0 replies; 24+ messages in thread
From: Sean Christopherson @ 2026-10-30 21:21 UTC (permalink / raw)
To: Madhavan Srinivasan, Sean Christopherson, Paolo Bonzini
Cc: Nicholas Piggin, linuxppc-dev, kvm, linux-kernel, Jim Mattson,
James Houghton, Chris Mason
Rename __x86_set_memory_region() to x86_set_internal_memslot() and add
double-underscores to the existing x86_set_internal_memslot(), so that the
x86 APIs better align common KVM's kvm_set_internal_memslot().
No functional change intended.
Signed-off-by: Sean Christopherson <seanjc@google.com>
---
arch/x86/kvm/lapic.c | 6 +++---
arch/x86/kvm/vmx/vmx.c | 12 ++++++------
arch/x86/kvm/x86.c | 14 +++++++-------
arch/x86/kvm/x86.h | 4 ++--
4 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/arch/x86/kvm/lapic.c b/arch/x86/kvm/lapic.c
index c20e2a25834f..b4a7f02f5c48 100644
--- a/arch/x86/kvm/lapic.c
+++ b/arch/x86/kvm/lapic.c
@@ -2917,8 +2917,8 @@ int kvm_alloc_apic_access_page(struct kvm *kvm)
kvm->arch.apic_access_memslot_inhibited)
return 0;
- hva = __x86_set_memory_region(kvm, APIC_ACCESS_PAGE_PRIVATE_MEMSLOT,
- APIC_DEFAULT_PHYS_BASE, PAGE_SIZE);
+ hva = x86_set_internal_memslot(kvm, APIC_ACCESS_PAGE_PRIVATE_MEMSLOT,
+ APIC_DEFAULT_PHYS_BASE, PAGE_SIZE);
if (IS_ERR(hva))
return PTR_ERR(hva);
@@ -2943,7 +2943,7 @@ void kvm_inhibit_apic_access_page(struct kvm_vcpu *vcpu)
if (!kvm->arch.apic_access_memslot_enabled)
goto out;
- p = __x86_set_memory_region(kvm, APIC_ACCESS_PAGE_PRIVATE_MEMSLOT, 0, 0);
+ p = x86_set_internal_memslot(kvm, APIC_ACCESS_PAGE_PRIVATE_MEMSLOT, 0, 0);
if (IS_ERR(p)) {
kvm_make_request(KVM_REQ_APICV_UPDATE, vcpu);
goto out;
diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index 1f9e43c5b4c4..00fb9459de59 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -4055,10 +4055,10 @@ static int init_rmode_identity_map(struct kvm *kvm)
if (!kvm_vmx->ept_identity_map_addr)
kvm_vmx->ept_identity_map_addr = VMX_EPT_IDENTITY_PAGETABLE_ADDR;
- uaddr = __x86_set_memory_region(kvm,
- IDENTITY_PAGETABLE_PRIVATE_MEMSLOT,
- kvm_vmx->ept_identity_map_addr,
- PAGE_SIZE);
+ uaddr = x86_set_internal_memslot(kvm,
+ IDENTITY_PAGETABLE_PRIVATE_MEMSLOT,
+ kvm_vmx->ept_identity_map_addr,
+ PAGE_SIZE);
if (IS_ERR(uaddr)) {
r = PTR_ERR(uaddr);
goto out;
@@ -5295,8 +5295,8 @@ int vmx_set_tss_addr(struct kvm *kvm, unsigned int addr)
return 0;
mutex_lock(&kvm->slots_lock);
- ret = __x86_set_memory_region(kvm, TSS_PRIVATE_MEMSLOT, addr,
- PAGE_SIZE * 3);
+ ret = x86_set_internal_memslot(kvm, TSS_PRIVATE_MEMSLOT, addr,
+ PAGE_SIZE * 3);
mutex_unlock(&kvm->slots_lock);
if (IS_ERR(ret))
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index 8cb58040dd81..85dd52c6beca 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -10007,8 +10007,8 @@ int kvm_arch_init_vm(struct kvm *kvm, unsigned long type)
return ret;
}
-static int x86_set_internal_memslot(struct kvm *kvm, int id, int as_id,
- gpa_t gpa, struct file *f, u32 size)
+static int __x86_set_internal_memslot(struct kvm *kvm, int id, int as_id,
+ gpa_t gpa, struct file *f, u32 size)
{
struct kvm_userspace_memory_region2 m;
unsigned long hva;
@@ -10038,7 +10038,7 @@ static int x86_set_internal_memslot(struct kvm *kvm, int id, int as_id,
}
/**
- * __x86_set_memory_region: Setup KVM internal memory slot
+ * x86_set_internal_memslot: Setup KVM internal memory slot
*
* @kvm: the kvm pointer to the VM.
* @id: the slot ID to setup.
@@ -10059,7 +10059,7 @@ static int x86_set_internal_memslot(struct kvm *kvm, int id, int as_id,
* address, i.e. its accessibility is not guaranteed, and must be
* accessed via __copy_{to,from}_user().
*/
-void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
+void __user *x86_set_internal_memslot(struct kvm *kvm, int id, gpa_t gpa,
u32 size)
{
struct file *f __free(fput) = NULL;
@@ -10081,12 +10081,12 @@ void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
}
for (i = 0; i < kvm_arch_nr_memslot_as_ids(kvm); i++) {
- r = x86_set_internal_memslot(kvm, id, i, gpa, f, size);
+ r = __x86_set_internal_memslot(kvm, id, i, gpa, f, size);
if (!r)
continue;
for (i--; i >= 0 && size; i--)
- x86_set_internal_memslot(kvm, id, i, gpa, NULL, 0);
+ __x86_set_internal_memslot(kvm, id, i, gpa, NULL, 0);
return ERR_PTR_USR(r);
}
@@ -10097,7 +10097,7 @@ void __user * __x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
return (void __user *)(slot ? slot->userspace_addr : 0);
}
-EXPORT_SYMBOL_FOR_KVM_INTERNAL(__x86_set_memory_region);
+EXPORT_SYMBOL_FOR_KVM_INTERNAL(x86_set_internal_memslot);
void kvm_arch_pre_destroy_vm(struct kvm *kvm)
{
diff --git a/arch/x86/kvm/x86.h b/arch/x86/kvm/x86.h
index cfe96962a4e4..c467d7347145 100644
--- a/arch/x86/kvm/x86.h
+++ b/arch/x86/kvm/x86.h
@@ -529,8 +529,8 @@ static inline void kvm_inject_gp(struct kvm_vcpu *vcpu, u32 error_code)
void kvm_inject_nmi(struct kvm_vcpu *vcpu);
int kvm_get_nr_pending_nmis(struct kvm_vcpu *vcpu);
-void __user *__x86_set_memory_region(struct kvm *kvm, int id, gpa_t gpa,
- u32 size);
+void __user *x86_set_internal_memslot(struct kvm *kvm, int id, gpa_t gpa,
+ u32 size);
int memslot_rmap_alloc(struct kvm_memory_slot *slot, unsigned long npages);
bool kvm_vcpu_is_reset_bsp(struct kvm_vcpu *vcpu);
--
2.56.0.385.gac671c819a-goog
^ permalink raw reply [flat|nested] 24+ messages in thread
end of thread, other threads:[~2026-10-30 21:21 UTC | newest]
Thread overview: 24+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-30 21:20 [PATCH v4 00/23] KVM: Fix+harden against bad uaccess using dying VM Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 01/23] KVM: Reject user accesses to guest memory if current->mm != kvm->mm Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 02/23] KVM: nVMX: Don't force a full nested VM-Exit when destroying a vCPU Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 03/23] KVM: PPC: Flush/zap all memslots on kvm_arch_flush_shadow_all() Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 04/23] KVM: x86: Drop dead check on an existing memslot not having any pages Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 05/23] KVM: WARN and error out if memslot update encounters existing slot with no pages Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 06/23] KVM: x86: Unmap VMAs for KVM-internal memslots when the memslot is freed Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 07/23] KVM: x86: Delete non-SMM internal memslot if creating SMM memslot fails Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 08/23] KVM: x86: Process all address spaces when deleting internal memslot Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 09/23] KVM: x86: Re-queue APICv update request if inhibiting APIC access page fails Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 10/23] KVM: x86: Map a separate VMA for each address space for internal memslots Sean Christopherson
2026-10-30 21:20 ` [PATCH v4 11/23] KVM: Disallow setting memslots when the VM is being destroyed Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 12/23] KVM: Destroy memslots immediately after mmu_notifiers are unregistered Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 13/23] KVM: WARN if KVM attempts to do guest-related uaccess with "wrong" process Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 14/23] KVM: WARN and reject guest-based uaccess if VM is dying Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 15/23] KVM: Pre-check uaccesses in KVM's APIs to read/write guest memory Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 16/23] KVM: VMX: Explicility free vmcs01, not the current VMCS, in vCPU free paths Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 17/23] KVM: nVMX: Don't explicitly VMCLEAR+free() vmcs01's shadow VMCS when freeing vCPU Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 18/23] KVM: nVMX: Cancel the nested VMX Preemption Timer's hrtimer in free_nested() Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 19/23] KVM: Add lockdep-only APIs to query and assert that a vCPU is unreachable Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 20/23] KVM: VMX: Explicitly remove dying vCPUs from the PI wakeup list Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 21/23] KVM: nVMX: Don't switch to vmcs01 when freeing a vCPU Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 22/23] KVM: nVMX: Don't load+put the vCPU when freeing nested state for a dying vCPU Sean Christopherson
2026-10-30 21:21 ` [PATCH v4 23/23] KVM: x86: Rename __x86_set_memory_region() => x86_set_internal_memslot() 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®