]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
KVM: selftests: Add a KVM_IRQFD test to verify uniqueness requirements
authorSean Christopherson <seanjc@google.com>
Thu, 22 May 2025 23:52:23 +0000 (16:52 -0700)
committerSean Christopherson <seanjc@google.com>
Mon, 23 Jun 2025 16:51:01 +0000 (09:51 -0700)
Add a selftest to verify that eventfd+irqfd bindings are globally unique,
i.e. that KVM doesn't allow multiple irqfds to bind to a single eventfd,
even across VMs.

Tested-by: K Prateek Nayak <kprateek.nayak@amd.com>
Acked-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Link: https://lore.kernel.org/r/20250522235223.3178519-14-seanjc@google.com
Signed-off-by: Sean Christopherson <seanjc@google.com>
tools/testing/selftests/kvm/Makefile.kvm
tools/testing/selftests/kvm/irqfd_test.c [new file with mode: 0644]

index 38b95998e1e6b5d3ebda841c40a6164546de2185..028456f1aae1b92aaef9d1745dfab403fa342a8d 100644 (file)
@@ -59,6 +59,7 @@ TEST_PROGS_x86 += x86/nx_huge_pages_test.sh
 TEST_GEN_PROGS_COMMON = demand_paging_test
 TEST_GEN_PROGS_COMMON += dirty_log_test
 TEST_GEN_PROGS_COMMON += guest_print_test
+TEST_GEN_PROGS_COMMON += irqfd_test
 TEST_GEN_PROGS_COMMON += kvm_binary_stats_test
 TEST_GEN_PROGS_COMMON += kvm_create_max_vcpus
 TEST_GEN_PROGS_COMMON += kvm_page_table_test
diff --git a/tools/testing/selftests/kvm/irqfd_test.c b/tools/testing/selftests/kvm/irqfd_test.c
new file mode 100644 (file)
index 0000000..7c301b4
--- /dev/null
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <errno.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <stdint.h>
+#include <sys/sysinfo.h>
+
+#include "kvm_util.h"
+
+static struct kvm_vm *vm1;
+static struct kvm_vm *vm2;
+static int __eventfd;
+static bool done;
+
+/*
+ * KVM de-assigns based on eventfd *and* GSI, but requires unique eventfds when
+ * assigning (the API isn't symmetrical).  Abuse the oddity and use a per-task
+ * GSI base to avoid false failures due to cross-task de-assign, i.e. so that
+ * the secondary doesn't de-assign the primary's eventfd and cause assign to
+ * unexpectedly succeed on the primary.
+ */
+#define GSI_BASE_PRIMARY       0x20
+#define GSI_BASE_SECONDARY     0x30
+
+static void juggle_eventfd_secondary(struct kvm_vm *vm, int eventfd)
+{
+       int r, i;
+
+       /*
+        * The secondary task can encounter EBADF since the primary can close
+        * the eventfd at any time.  And because the primary can recreate the
+        * eventfd, at the safe fd in the file table, the secondary can also
+        * encounter "unexpected" success, e.g. if the close+recreate happens
+        * between the first and second assignments.  The secondary's role is
+        * mostly to antagonize KVM, not to detect bugs.
+        */
+       for (i = 0; i < 2; i++) {
+               r = __kvm_irqfd(vm, GSI_BASE_SECONDARY, eventfd, 0);
+               TEST_ASSERT(!r || errno == EBUSY || errno == EBADF,
+                           "Wanted success, EBUSY, or EBADF, r = %d, errno = %d",
+                           r, errno);
+
+               /* De-assign should succeed unless the eventfd was closed. */
+               r = __kvm_irqfd(vm, GSI_BASE_SECONDARY + i, eventfd, KVM_IRQFD_FLAG_DEASSIGN);
+               TEST_ASSERT(!r || errno == EBADF,
+                           "De-assign should succeed unless the fd was closed");
+       }
+}
+
+static void *secondary_irqfd_juggler(void *ign)
+{
+       while (!READ_ONCE(done)) {
+               juggle_eventfd_secondary(vm1, READ_ONCE(__eventfd));
+               juggle_eventfd_secondary(vm2, READ_ONCE(__eventfd));
+       }
+
+       return NULL;
+}
+
+static void juggle_eventfd_primary(struct kvm_vm *vm, int eventfd)
+{
+       int r1, r2;
+
+       /*
+        * At least one of the assigns should fail.  KVM disallows assigning a
+        * single eventfd to multiple GSIs (or VMs), so it's possible that both
+        * assignments can fail, too.
+        */
+       r1 = __kvm_irqfd(vm, GSI_BASE_PRIMARY, eventfd, 0);
+       TEST_ASSERT(!r1 || errno == EBUSY,
+                   "Wanted success or EBUSY, r = %d, errno = %d", r1, errno);
+
+       r2 = __kvm_irqfd(vm, GSI_BASE_PRIMARY + 1, eventfd, 0);
+       TEST_ASSERT(r1 || (r2 && errno == EBUSY),
+                   "Wanted failure (EBUSY), r1 = %d, r2 = %d, errno = %d",
+                   r1, r2, errno);
+
+       /*
+        * De-assign should always succeed, even if the corresponding assign
+        * failed.
+        */
+       kvm_irqfd(vm, GSI_BASE_PRIMARY, eventfd, KVM_IRQFD_FLAG_DEASSIGN);
+       kvm_irqfd(vm, GSI_BASE_PRIMARY + 1, eventfd, KVM_IRQFD_FLAG_DEASSIGN);
+}
+
+int main(int argc, char *argv[])
+{
+       pthread_t racing_thread;
+       int r, i;
+
+       /* Create "full" VMs, as KVM_IRQFD requires an in-kernel IRQ chip. */
+       vm1 = vm_create(1);
+       vm2 = vm_create(1);
+
+       WRITE_ONCE(__eventfd, kvm_new_eventfd());
+
+       kvm_irqfd(vm1, 10, __eventfd, 0);
+
+       r = __kvm_irqfd(vm1, 11, __eventfd, 0);
+       TEST_ASSERT(r && errno == EBUSY,
+                   "Wanted EBUSY, r = %d, errno = %d", r, errno);
+
+       r = __kvm_irqfd(vm2, 12, __eventfd, 0);
+       TEST_ASSERT(r && errno == EBUSY,
+                   "Wanted EBUSY, r = %d, errno = %d", r, errno);
+
+       /*
+        * De-assign all eventfds, along with multiple eventfds that were never
+        * assigned.  KVM's ABI is that de-assign is allowed so long as the
+        * eventfd itself is valid.
+        */
+       kvm_irqfd(vm1, 11, READ_ONCE(__eventfd), KVM_IRQFD_FLAG_DEASSIGN);
+       kvm_irqfd(vm1, 12, READ_ONCE(__eventfd), KVM_IRQFD_FLAG_DEASSIGN);
+       kvm_irqfd(vm1, 13, READ_ONCE(__eventfd), KVM_IRQFD_FLAG_DEASSIGN);
+       kvm_irqfd(vm1, 14, READ_ONCE(__eventfd), KVM_IRQFD_FLAG_DEASSIGN);
+       kvm_irqfd(vm1, 10, READ_ONCE(__eventfd), KVM_IRQFD_FLAG_DEASSIGN);
+
+       close(__eventfd);
+
+       pthread_create(&racing_thread, NULL, secondary_irqfd_juggler, vm2);
+
+       for (i = 0; i < 10000; i++) {
+               WRITE_ONCE(__eventfd, kvm_new_eventfd());
+
+               juggle_eventfd_primary(vm1, __eventfd);
+               juggle_eventfd_primary(vm2, __eventfd);
+               close(__eventfd);
+       }
+
+       WRITE_ONCE(done, true);
+       pthread_join(racing_thread, NULL);
+}