Kernel KVM virtualization development
 help / color / mirror / Atom feed
* [PATCH v2 0/2] KVM: VMX: Fix IPIv use-after-free + improve checking duplicate vcpu_id
@ 2026-07-29 17:06 Dmytro Maluka
  2026-07-29 17:06 ` [PATCH v2 1/2] KVM: Check for duplicate vcpu_id as early as possible Dmytro Maluka
  2026-07-29 17:06 ` [PATCH v2 2/2] KVM: VMX: Fix stale PID-pointer table entry left after vCPU free Dmytro Maluka
  0 siblings, 2 replies; 5+ messages in thread
From: Dmytro Maluka @ 2026-07-29 17:06 UTC (permalink / raw)
  To: Sean Christopherson
  Cc: Paolo Bonzini, Dave Hansen, Chao Gao, Kai Huang, Naveen N Rao,
	kvm, linux-kernel, Vineeth Pillai, Chuanxiao Dong, Aashish Sharma,
	Grzegorz Jaszczyk, Dmytro Maluka

vCPU creation in kvm_vm_ioctl_create_vcpu() may fail after
kvm_arch_vcpu_create() -> vmx_vcpu_create() already succeeded. In such
case kvm_vm_ioctl_create_vcpu() destroys the newly created vCPU in the
failure path. However, that leaves a side effect: the IPIv pid_table
entry remains configured with this vCPU's pi_desc address. As a result,
when another vCPU sends an IPI to the APIC ID of this failed-to-create
vCPU, it will cause HW to write to this (freed!) pi_desc memory.

The straightforward way to fix this is to clear the pid_table entry in
vmx_vcpu_free(), which is what patch 2 in this series does.

But that requires addressing a related meta-issue first: if userspace
tries to create a vCPU with the same vcpu_id as an existing one,
kvm_vm_ioctl_create_vcpu() checks for that and fails with -EEXIST only
after it already created the vCPU via kvm_arch_vcpu_create(). As a
result, clearing the pid_table entry in the vmx_vcpu_free() in the
failure path would clear the pid_table entry for that already existing
good vCPU, i.e. effectively disable IPIv for that vCPU.

For this reason, the v1 patch [1] addressed the IPIv issue by postponing
the pid_table entry setup until kvm_arch_vcpu_postcreate() when we are
sure that the vCPU creation succeeded, instead of clearing it when
destroying the vCPU.

However, as pointed out by Sean [2], insufficient validation of vcpu_id
before calling kvm_arch_vcpu_create() is a potential source of a whole
class of similar issues, so it's better to fix this meta-issue once and
for all. Another concern about the v1 approach is that initializing vCPU
state in vcpu_postcreate, after the vCPU is already reachable, is
generally tricky.

So patch 1 in this series moves checking for duplicated vcpu_id in
kvm_vm_ioctl_create_vcpu() earlier, before creating the vCPU. To do that
without races, it introduces the kvm->vcpu_ids bitmap (see patch 1
description for the details). It is a slightly adjusted version of
Sean's bitmap patch from [2].

P.S. Note that the same IPIv use-after-free issue exists on SVM as well,
as pointed out by sashiko and confirmed by Naveen [3]. This remains to
be fixed. (Supposedly it is as trivial to fix as for VMX, but I'll leave
that to someone who's familiar with that and has AMD hardware.)

[1] https://lore.kernel.org/kvm/20260716160801.3155582-1-dmaluka@chromium.org/
[2] https://lore.kernel.org/kvm/al6eg7C-2sDBEAFD@google.com/
[3] https://lore.kernel.org/kvm/al4rNqpBYy8FGKPw@blrnaveerao1/

Dmytro Maluka (2):
  KVM: Check for duplicate vcpu_id as early as possible
  KVM: VMX: Fix stale PID-pointer table entry left after vCPU free

 arch/x86/kvm/vmx/vmx.c   | 3 +++
 include/linux/kvm_host.h | 1 +
 virt/kvm/kvm_main.c      | 9 ++++++++-
 3 files changed, 12 insertions(+), 1 deletion(-)

-- 
2.55.0.508.g3f0d502094-goog


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH v2 1/2] KVM: Check for duplicate vcpu_id as early as possible
  2026-07-29 17:06 [PATCH v2 0/2] KVM: VMX: Fix IPIv use-after-free + improve checking duplicate vcpu_id Dmytro Maluka
@ 2026-07-29 17:06 ` Dmytro Maluka
  2026-07-29 17:06 ` [PATCH v2 2/2] KVM: VMX: Fix stale PID-pointer table entry left after vCPU free Dmytro Maluka
  1 sibling, 0 replies; 5+ messages in thread
From: Dmytro Maluka @ 2026-07-29 17:06 UTC (permalink / raw)
  To: Sean Christopherson
  Cc: Paolo Bonzini, Dave Hansen, Chao Gao, Kai Huang, Naveen N Rao,
	kvm, linux-kernel, Vineeth Pillai, Chuanxiao Dong, Aashish Sharma,
	Grzegorz Jaszczyk, Dmytro Maluka

If userspace tries to create a vCPU with the same vcpu_id as an existing
one, kvm_vm_ioctl_create_vcpu() checks for that and fails with -EEXIST
only after it already created the vCPU via kvm_arch_vcpu_create(). As a
result, even though this newly created vCPU is destroyed in the failure
path, the fact that it is temporarily created with an invalid vcpu_id
and that there are temporarily two vCPUs with the same vcpu_id is a
potential source of subtle issues.

In particular, this prevents fixing the VMX IPIv issue fixed in the next
patch: a stale entry left in the VM's PI descriptor table after the vCPU
is destroyed in the failure path. The right way to fix that issue is to
clear that entry when destroying the vCPU, however right now that would
have a nasty side effect: since the same entry is used for the other,
previously created vCPU with same vcpu_id, clearing it would mean
effectively disabling IPIv for that existing good vCPU.

So to avoid this and similar problems, check for duplicate vcpu_id as
early in the vCPU creation path as possible, before
kvm_arch_vcpu_create() and even before kvm_arch_vcpu_precreate().

We cannot just move the existing kvm_get_vcpu_by_id() check earlier,
since we drop kvm->lock and then take it again, so if we just moved
the kvm_get_vcpu_by_id() check before the first unlock of kvm->lock,
we would introduce a race:

1. vCPU A is being created but not installed in kvm->vcpu_array yet.
2. vCPU B with the same vcpu_id is being created. It passes the
   duplicated vcpu_id check, since the check doesn't find vCPU A in
   kvm->vcpu_array.
3. vCPU A is installed in kvm->vcpu_array, vCPU creation succeeds.
4. vCPU B with the same vcpu_id is installed in kvm->vcpu_array, vCPU
   creation succeeds.

So introduce the bitmap of vcpu_ids used by the VM, in order to safely
check if the given vcpu_id is used and mark is as used before releasing
kvm->lock first time.

Suggested-by: Sean Christopherson <seanjc@google.com>
Link: https://lore.kernel.org/kvm/al6eg7C-2sDBEAFD@google.com/
Signed-off-by: Dmytro Maluka <dmaluka@chromium.org>
---
 include/linux/kvm_host.h | 1 +
 virt/kvm/kvm_main.c      | 9 ++++++++-
 2 files changed, 9 insertions(+), 1 deletion(-)

diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index ab8cfaec82d3..6f883ed82581 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -791,6 +791,7 @@ struct kvm {
 	/* The current active memslot set for each address space */
 	struct kvm_memslots __rcu *memslots[KVM_MAX_NR_ADDRESS_SPACES];
 	struct xarray vcpu_array;
+	DECLARE_BITMAP(vcpu_ids, KVM_MAX_VCPU_IDS);
 	/*
 	 * Protected by slots_lock, but can be read outside if an
 	 * incorrect answer is acceptable.
diff --git a/virt/kvm/kvm_main.c b/virt/kvm/kvm_main.c
index 45e784462ec6..1e3714c5daa7 100644
--- a/virt/kvm/kvm_main.c
+++ b/virt/kvm/kvm_main.c
@@ -4173,6 +4173,11 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id)
 		return -EINVAL;
 	}
 
+	if (test_bit(id, kvm->vcpu_ids)) {
+		mutex_unlock(&kvm->lock);
+		return -EEXIST;
+	}
+
 	r = kvm_arch_vcpu_precreate(kvm, id);
 	if (r) {
 		mutex_unlock(&kvm->lock);
@@ -4180,6 +4185,7 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id)
 	}
 
 	kvm->created_vcpus++;
+	__set_bit(id, kvm->vcpu_ids);
 	mutex_unlock(&kvm->lock);
 
 	vcpu = kmem_cache_zalloc(kvm_vcpu_cache, GFP_KERNEL_ACCOUNT);
@@ -4211,7 +4217,7 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id)
 
 	mutex_lock(&kvm->lock);
 
-	if (kvm_get_vcpu_by_id(kvm, id)) {
+	if (WARN_ON_ONCE(kvm_get_vcpu_by_id(kvm, id))) {
 		r = -EEXIST;
 		goto unlock_vcpu_destroy;
 	}
@@ -4265,6 +4271,7 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, unsigned long id)
 vcpu_decrement:
 	mutex_lock(&kvm->lock);
 	kvm->created_vcpus--;
+	__clear_bit(id, kvm->vcpu_ids);
 	mutex_unlock(&kvm->lock);
 	return r;
 }
-- 
2.55.0.508.g3f0d502094-goog


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH v2 2/2] KVM: VMX: Fix stale PID-pointer table entry left after vCPU free
  2026-07-29 17:06 [PATCH v2 0/2] KVM: VMX: Fix IPIv use-after-free + improve checking duplicate vcpu_id Dmytro Maluka
  2026-07-29 17:06 ` [PATCH v2 1/2] KVM: Check for duplicate vcpu_id as early as possible Dmytro Maluka
@ 2026-07-29 17:06 ` Dmytro Maluka
  2026-07-29 17:28   ` sashiko-bot
  1 sibling, 1 reply; 5+ messages in thread
From: Dmytro Maluka @ 2026-07-29 17:06 UTC (permalink / raw)
  To: Sean Christopherson
  Cc: Paolo Bonzini, Dave Hansen, Chao Gao, Kai Huang, Naveen N Rao,
	kvm, linux-kernel, Vineeth Pillai, Chuanxiao Dong, Aashish Sharma,
	Grzegorz Jaszczyk, Dmytro Maluka

vCPU creation in kvm_vm_ioctl_create_vcpu() may fail after
kvm_arch_vcpu_create() -> vmx_vcpu_create() already succeeded. In such
case kvm_vm_ioctl_create_vcpu() destroys the newly created vCPU in the
failure path. However, that leaves a side effect: the IPIv pid_table
entry remains configured with this vCPU's pi_desc address. As a result,
when another vCPU sends an IPI to the APIC ID of this failed-to-create
vCPU, it will cause HW to write to this (freed!) pi_desc memory. [*]

Fix this by clearing the pid_table entry when destroying the vCPU.

Note that the same issue exists for SVM AVIC as well [1], to be fixed.

[*] Although, since this memory is freed into the kvm_vcpu_cache kmem
    cache which is only used for allocating kvm_vcpus, _maybe_ this
    memory will only be reused for pi_desc of another vCPU, not for
    anything else. So _maybe_ this will only result in delivering the
    IPI to a wrong vCPU (possibly of another VM) in the worst case, not
    in a random corruption of kernel memory.

[1] https://lore.kernel.org/kvm/al4rNqpBYy8FGKPw@blrnaveerao1/

Signed-off-by: Dmytro Maluka <dmaluka@chromium.org>
---
 arch/x86/kvm/vmx/vmx.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
index cc75feec05da..f98c268b5150 100644
--- a/arch/x86/kvm/vmx/vmx.c
+++ b/arch/x86/kvm/vmx/vmx.c
@@ -7665,6 +7665,9 @@ void vmx_vcpu_free(struct kvm_vcpu *vcpu)
 	nested_vmx_free_vcpu(vcpu);
 	free_loaded_vmcs(vmx->loaded_vmcs);
 	free_page((unsigned long)vmx->ve_info);
+
+	if (vmx_can_use_ipiv(vcpu))
+		WRITE_ONCE(to_kvm_vmx(vcpu->kvm)->pid_table[vcpu->vcpu_id], 0);
 }
 
 int vmx_vcpu_create(struct kvm_vcpu *vcpu)
-- 
2.55.0.508.g3f0d502094-goog


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH v2 2/2] KVM: VMX: Fix stale PID-pointer table entry left after vCPU free
  2026-07-29 17:06 ` [PATCH v2 2/2] KVM: VMX: Fix stale PID-pointer table entry left after vCPU free Dmytro Maluka
@ 2026-07-29 17:28   ` sashiko-bot
  2026-07-29 18:06     ` Dmytro Maluka
  0 siblings, 1 reply; 5+ messages in thread
From: sashiko-bot @ 2026-07-29 17:28 UTC (permalink / raw)
  To: Dmytro Maluka; +Cc: kvm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Persistent Use-After-Free of AVIC backing page due to dangling pointer in `avic_physical_id_table` when SVM vCPU creation fails.
- [High] Transient Use-After-Free of vcpu memory due to missing hardware cache invalidation (VPID) and synchronization during VMX vCPU destruction.
--

--- Patch [2]: [PATCH v2 2/2] KVM: VMX: Fix stale PID-pointer table entry left after vCPU free ---
commit a1d19d2d3c1c1e5aad00c7f77f6736e2bfb89509
Author: Dmytro Maluka <dmaluka@chromium.org>

KVM: VMX: Fix stale PID-pointer table entry left after vCPU free

vCPU creation in kvm_vm_ioctl_create_vcpu() may fail after
kvm_arch_vcpu_create() -> vmx_vcpu_create() already succeeded. In such
case kvm_vm_ioctl_create_vcpu() destroys the newly created vCPU in the
failure path. However, that leaves a side effect: the IPIv pid_table
entry remains configured with this vCPU's pi_desc address.

Link: https://lore.kernel.org/kvm/al4rNqpBYy8FGKPw@blrnaveerao1/

> Note that the same issue exists for SVM AVIC as well [1], to be fixed.

[Severity: Critical]
This is a pre-existing issue, but does SVM vCPU destruction leave a dangling
pointer in avic_physical_id_table?

During KVM_CREATE_VCPU, avic_init_backing_page() sets
avic_physical_id_table[id] to point to the newly allocated AVIC backing page
and marks it valid.

If vCPU creation later fails in kvm_vm_ioctl_create_vcpu(), svm_vcpu_free() is
called but fails to clear this physical ID table entry. kvm_arch_vcpu_destroy()
then calls kvm_free_lapic(), which uses free_page() to release the backing page.

The avic_physical_id_table retains a valid physical pointer to this freed
page. If a guest sends an IPI to this freed vCPU ID, could the SVM AVIC
hardware use the stale table entry to perform memory writes on the freed page,
causing host kernel memory corruption?

> diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
> index 6b96f7aea20bd..ebf26ea84a50b 100644
> --- a/arch/x86/kvm/vmx/vmx.c
> +++ b/arch/x86/kvm/vmx/vmx.c
> @@ -7572,6 +7572,9 @@ void vmx_vcpu_free(struct kvm_vcpu *vcpu)
>  	nested_vmx_free_vcpu(vcpu);
>  	free_loaded_vmcs(vmx->loaded_vmcs);
>  	free_page((unsigned long)vmx->ve_info);
> +
> +	if (vmx_can_use_ipiv(vcpu))
> +		WRITE_ONCE(to_kvm_vmx(vcpu->kvm)->pid_table[vcpu->vcpu_id], 0);

[Severity: High]
This isn't a bug introduced by this patch, but does clearing the pid_table
entry alone prevent hardware from writing to the freed pi_desc?

According to the Intel SDM (Section 29.1.5), the processor caches PID-pointer
table entries in the VPID.

Because KVM immediately frees the vcpu memory into kvm_vcpu_cache after
kvm_arch_vcpu_destroy() without issuing a cross-vCPU flush (e.g., via
kvm_make_all_cpus_request(kvm, KVM_REQ_TLB_FLUSH)) to invalidate the VPID
caches of other running vCPUs and synchronize with in-flight IPI deliveries,
can other vCPUs still send hardware IPIs using the cached pi_desc pointer?

Could this result in the IPI hardware writing to the freed vcpu memory and
corrupting another newly created vCPU when the memory is reused from
kvm_vcpu_cache?

>  }

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260729170621.308809-1-dmaluka@chromium.org?part=2

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH v2 2/2] KVM: VMX: Fix stale PID-pointer table entry left after vCPU free
  2026-07-29 17:28   ` sashiko-bot
@ 2026-07-29 18:06     ` Dmytro Maluka
  0 siblings, 0 replies; 5+ messages in thread
From: Dmytro Maluka @ 2026-07-29 18:06 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: kvm

On Wed, Jul 29, 2026 at 05:28:30PM +0000, sashiko-bot@kernel.org wrote:
> > diff --git a/arch/x86/kvm/vmx/vmx.c b/arch/x86/kvm/vmx/vmx.c
> > index 6b96f7aea20bd..ebf26ea84a50b 100644
> > --- a/arch/x86/kvm/vmx/vmx.c
> > +++ b/arch/x86/kvm/vmx/vmx.c
> > @@ -7572,6 +7572,9 @@ void vmx_vcpu_free(struct kvm_vcpu *vcpu)
> >  	nested_vmx_free_vcpu(vcpu);
> >  	free_loaded_vmcs(vmx->loaded_vmcs);
> >  	free_page((unsigned long)vmx->ve_info);
> > +
> > +	if (vmx_can_use_ipiv(vcpu))
> > +		WRITE_ONCE(to_kvm_vmx(vcpu->kvm)->pid_table[vcpu->vcpu_id], 0);
> 
> [Severity: High]
> This isn't a bug introduced by this patch, but does clearing the pid_table
> entry alone prevent hardware from writing to the freed pi_desc?
> 
> According to the Intel SDM (Section 29.1.5), the processor caches PID-pointer
> table entries in the VPID.

I can't find this statement in the SDM, and I'm not sure what it has to
do with VPID, apart from the acronym similarity.

> Because KVM immediately frees the vcpu memory into kvm_vcpu_cache after
> kvm_arch_vcpu_destroy() without issuing a cross-vCPU flush (e.g., via
> kvm_make_all_cpus_request(kvm, KVM_REQ_TLB_FLUSH)) to invalidate the VPID
> caches of other running vCPUs and synchronize with in-flight IPI deliveries,
> can other vCPUs still send hardware IPIs using the cached pi_desc pointer?
> 
> Could this result in the IPI hardware writing to the freed vcpu memory and
> corrupting another newly created vCPU when the memory is reused from
> kvm_vcpu_cache?
> 
> >  }
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260729170621.308809-1-dmaluka@chromium.org?part=2

^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-07-29 18:06 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-29 17:06 [PATCH v2 0/2] KVM: VMX: Fix IPIv use-after-free + improve checking duplicate vcpu_id Dmytro Maluka
2026-07-29 17:06 ` [PATCH v2 1/2] KVM: Check for duplicate vcpu_id as early as possible Dmytro Maluka
2026-07-29 17:06 ` [PATCH v2 2/2] KVM: VMX: Fix stale PID-pointer table entry left after vCPU free Dmytro Maluka
2026-07-29 17:28   ` sashiko-bot
2026-07-29 18:06     ` Dmytro Maluka

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox