KVM: x86: fix deadlock for KVM_XEN_EVTCHN_RESET
authorPaolo Bonzini <pbonzini@redhat.com>
Wed, 28 Dec 2022 10:33:41 +0000 (05:33 -0500)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Sat, 25 Feb 2023 10:25:40 +0000 (11:25 +0100)
[ Upstream commit a79b53aaaab53de017517bf9579b6106397a523c ]

While KVM_XEN_EVTCHN_RESET is usually called with no vCPUs running,
if that happened it could cause a deadlock.  This is due to
kvm_xen_eventfd_reset() doing a synchronize_srcu() inside
a kvm->lock critical section.

To avoid this, first collect all the evtchnfd objects in an
array and free all of them once the kvm->lock critical section
is over and th SRCU grace period has expired.

Reported-by: Michal Luczaj <mhal@rbox.co>
Cc: David Woodhouse <dwmw@amazon.co.uk>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
arch/x86/kvm/xen.c
tools/testing/selftests/kvm/x86_64/xen_shinfo_test.c

index f3098c0e386a8a6a8eadf50f993ec2a00cfe2317..a58a426e6b1c0c8d4fe1dab2e13ee130d5c8d95c 100644 (file)
@@ -1757,18 +1757,42 @@ static int kvm_xen_eventfd_deassign(struct kvm *kvm, u32 port)
 
 static int kvm_xen_eventfd_reset(struct kvm *kvm)
 {
-       struct evtchnfd *evtchnfd;
+       struct evtchnfd *evtchnfd, **all_evtchnfds;
        int i;
+       int n = 0;
 
        mutex_lock(&kvm->lock);
+
+       /*
+        * Because synchronize_srcu() cannot be called inside the
+        * critical section, first collect all the evtchnfd objects
+        * in an array as they are removed from evtchn_ports.
+        */
+       idr_for_each_entry(&kvm->arch.xen.evtchn_ports, evtchnfd, i)
+               n++;
+
+       all_evtchnfds = kmalloc_array(n, sizeof(struct evtchnfd *), GFP_KERNEL);
+       if (!all_evtchnfds) {
+               mutex_unlock(&kvm->lock);
+               return -ENOMEM;
+       }
+
+       n = 0;
        idr_for_each_entry(&kvm->arch.xen.evtchn_ports, evtchnfd, i) {
+               all_evtchnfds[n++] = evtchnfd;
                idr_remove(&kvm->arch.xen.evtchn_ports, evtchnfd->send_port);
-               synchronize_srcu(&kvm->srcu);
+       }
+       mutex_unlock(&kvm->lock);
+
+       synchronize_srcu(&kvm->srcu);
+
+       while (n--) {
+               evtchnfd = all_evtchnfds[n];
                if (!evtchnfd->deliver.port.port)
                        eventfd_ctx_put(evtchnfd->deliver.eventfd.ctx);
                kfree(evtchnfd);
        }
-       mutex_unlock(&kvm->lock);
+       kfree(all_evtchnfds);
 
        return 0;
 }
index 2a5727188c8d30419f7a646cf645f1cf17af175b..8383457e66990db121768c05f6c4de29136bafc9 100644 (file)
@@ -942,6 +942,12 @@ int main(int argc, char *argv[])
        }
 
  done:
+       struct kvm_xen_hvm_attr evt_reset = {
+               .type = KVM_XEN_ATTR_TYPE_EVTCHN,
+               .u.evtchn.flags = KVM_XEN_EVTCHN_RESET,
+       };
+       vm_ioctl(vm, KVM_XEN_HVM_SET_ATTR, &evt_reset);
+
        alarm(0);
        clock_gettime(CLOCK_REALTIME, &max_ts);