Skip to content

Commit afb2acb

Browse files
mmhalbonzini
authored andcommitted
KVM: Fix vcpu_array[0] races
In kvm_vm_ioctl_create_vcpu(), add vcpu to vcpu_array iff it's safe to access vcpu via kvm_get_vcpu() and kvm_for_each_vcpu(), i.e. when there's no failure path requiring vcpu removal and destruction. Such order is important because vcpu_array accessors may end up referencing vcpu at vcpu_array[0] even before online_vcpus is set to 1. When online_vcpus=0, any call to kvm_get_vcpu() goes through array_index_nospec() and ends with an attempt to xa_load(vcpu_array, 0): int num_vcpus = atomic_read(&kvm->online_vcpus); i = array_index_nospec(i, num_vcpus); return xa_load(&kvm->vcpu_array, i); Similarly, when online_vcpus=0, a kvm_for_each_vcpu() does not iterate over an "empty" range, but actually [0, ULONG_MAX]: xa_for_each_range(&kvm->vcpu_array, idx, vcpup, 0, \ (atomic_read(&kvm->online_vcpus) - 1)) In both cases, such online_vcpus=0 edge case, even if leading to unnecessary calls to XArray API, should not be an issue; requesting unpopulated indexes/ranges is handled by xa_load() and xa_for_each_range(). However, this means that when the first vCPU is created and inserted in vcpu_array *and* before online_vcpus is incremented, code calling kvm_get_vcpu()/kvm_for_each_vcpu() already has access to that first vCPU. This should not pose a problem assuming that once a vcpu is stored in vcpu_array, it will remain there, but that's not the case: kvm_vm_ioctl_create_vcpu() first inserts to vcpu_array, then requests a file descriptor. If create_vcpu_fd() fails, newly inserted vcpu is removed from the vcpu_array, then destroyed: vcpu->vcpu_idx = atomic_read(&kvm->online_vcpus); r = xa_insert(&kvm->vcpu_array, vcpu->vcpu_idx, vcpu, GFP_KERNEL_ACCOUNT); kvm_get_kvm(kvm); r = create_vcpu_fd(vcpu); if (r < 0) { xa_erase(&kvm->vcpu_array, vcpu->vcpu_idx); kvm_put_kvm_no_destroy(kvm); goto unlock_vcpu_destroy; } atomic_inc(&kvm->online_vcpus); This results in a possible race condition when a reference to a vcpu is acquired (via kvm_get_vcpu() or kvm_for_each_vcpu()) moments before said vcpu is destroyed. Signed-off-by: Michal Luczaj <mhal@rbox.co> Message-Id: <20230510140410.1093987-2-mhal@rbox.co> Cc: stable@vger.kernel.org Fixes: c5b0775 ("KVM: Convert the kvm->vcpus array to a xarray", 2021-12-08) Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
1 parent 3367eea commit afb2acb

File tree

1 file changed

+10
-6
lines changed

1 file changed

+10
-6
lines changed

virt/kvm/kvm_main.c

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3962,18 +3962,19 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, u32 id)
39623962
}
39633963

39643964
vcpu->vcpu_idx = atomic_read(&kvm->online_vcpus);
3965-
r = xa_insert(&kvm->vcpu_array, vcpu->vcpu_idx, vcpu, GFP_KERNEL_ACCOUNT);
3966-
BUG_ON(r == -EBUSY);
3965+
r = xa_reserve(&kvm->vcpu_array, vcpu->vcpu_idx, GFP_KERNEL_ACCOUNT);
39673966
if (r)
39683967
goto unlock_vcpu_destroy;
39693968

39703969
/* Now it's all set up, let userspace reach it */
39713970
kvm_get_kvm(kvm);
39723971
r = create_vcpu_fd(vcpu);
3973-
if (r < 0) {
3974-
xa_erase(&kvm->vcpu_array, vcpu->vcpu_idx);
3975-
kvm_put_kvm_no_destroy(kvm);
3976-
goto unlock_vcpu_destroy;
3972+
if (r < 0)
3973+
goto kvm_put_xa_release;
3974+
3975+
if (KVM_BUG_ON(!!xa_store(&kvm->vcpu_array, vcpu->vcpu_idx, vcpu, 0), kvm)) {
3976+
r = -EINVAL;
3977+
goto kvm_put_xa_release;
39773978
}
39783979

39793980
/*
@@ -3988,6 +3989,9 @@ static int kvm_vm_ioctl_create_vcpu(struct kvm *kvm, u32 id)
39883989
kvm_create_vcpu_debugfs(vcpu);
39893990
return r;
39903991

3992+
kvm_put_xa_release:
3993+
kvm_put_kvm_no_destroy(kvm);
3994+
xa_release(&kvm->vcpu_array, vcpu->vcpu_idx);
39913995
unlock_vcpu_destroy:
39923996
mutex_unlock(&kvm->lock);
39933997
kvm_dirty_ring_free(&vcpu->dirty_ring);

0 commit comments

Comments
 (0)