I have occasionally seen EINVAL and EEXIST as well. My suspicion is a race condition due to incorrect synchronization of the executor threads with close_fds(): 1) the failing executor thread calls KVM_CREATE_VM 2) the main thread calls close_fds() while the failing thread is still running, and closes the vm file descriptor 3) the failing thread then gets EBADF. For example, EEXIST could happen if before step 3, another executor thread calls KVM_CREATE_VM, receiving the same file descriptor, and manages to call KVM_CREATE_VCPU(0) before the failing thread. I attach a simplified reproducer. Paolo On Wed, Aug 27, 2025 at 10:57 AM Jiaming Zhang <r772577952@xxxxxxxxx> wrote: > > Hello KVM maintainers and developers, > > We are writing to report a potential bug discovered in the KVM > subsystem with our modified syzkaller. The issue is that a > KVM_CREATE_VCPU ioctl call can fail with EBADF on a valid VM file > descriptor. > > The attached C program (repro.c) sets up a high-concurrency > environment by forking multiple processes, each running the test logic > in a loop. In the core test function (syz_func), it sequentially > creates two VMs and then attempts to create one VCPU for each. > Intermittently, one of the two KVM_CREATE_VCPU calls fails, returning > -1 and setting errno to 9 (EBADF). > > The VM file descriptor (vm_fd1/vm_fd2) passed to KVM_CREATE_VCPU was > just successfully returned by a KVM_CREATE_VM ioctl within the same > thread. An EBADF error in this context is unexpected. In addition, the > threading model of test code ensures that the creation and use of > these file descriptors happen sequentially within a single thread, > ruling out a user-space race condition where another thread could have > closed the file descriptor prematurely. > > This issue was first found on v6.1.147 (commit > 3594f306da129190de25938b823f353ef7f9e322), and is still reproducible > on the latest version (v6.17-rc3, commit > 1b237f190eb3d36f52dffe07a40b5eb210280e00). > > Other environmental information: > - Architecture: x86_64 > - Distribution: Ubuntu 22.04 > > The complete C code that triggers this issue and the .config file used > for Linux Kernel v6.1.147 and v6.17-rc3 compilation are attached. > > Thank you for your time and for your incredible work on KVM. We hope > this report is helpful. Please let me know if any further information > is required. > > Best regards, > Jiaming Zhang
#define MAX_FDS 1024 // autogenerated by syzkaller (https://github.com/google/syzkaller) #define _GNU_SOURCE #include <arpa/inet.h> #include <dirent.h> #include <endian.h> #include <errno.h> #include <fcntl.h> #include <ifaddrs.h> #include <net/if.h> #include <net/if_arp.h> #include <netinet/in.h> #include <pthread.h> #include <sched.h> #include <signal.h> #include <stdarg.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/epoll.h> #include <sys/ioctl.h> #include <sys/mount.h> #include <sys/prctl.h> #include <sys/resource.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/swap.h> #include <sys/syscall.h> #include <sys/time.h> #include <sys/types.h> #include <sys/uio.h> #include <sys/wait.h> #include <time.h> #include <unistd.h> #include <linux/capability.h> #include <linux/ethtool.h> #include <linux/falloc.h> #include <linux/futex.h> #include <linux/genetlink.h> #include <linux/if_addr.h> #include <linux/if_ether.h> #include <linux/if_link.h> #include <linux/if_tun.h> #include <linux/in6.h> #include <linux/ip.h> #include <linux/kvm.h> #include <linux/neighbour.h> #include <linux/net.h> #include <linux/netlink.h> #include <linux/nl80211.h> #include <linux/rfkill.h> #include <linux/rtnetlink.h> #include <linux/sockios.h> #include <linux/tcp.h> #include <linux/veth.h> static void thread_start(void* (*fn)(void*), void* arg) { pthread_t th; pthread_attr_t attr; pthread_attr_init(&attr); pthread_attr_setstacksize(&attr, 128 << 10); int i = 0; for (; i < 100; i++) { if (pthread_create(&th, &attr, fn, arg) == 0) { pthread_attr_destroy(&attr); return; } if (errno == EAGAIN) { usleep(50); continue; } break; } exit(1); } typedef struct { int state; } event_t; static void event_init(event_t* ev) { ev->state = 0; } static void event_reset(event_t* ev) { ev->state = 0; } static void event_set(event_t* ev) { if (ev->state) exit(1); __atomic_store_n(&ev->state, 1, __ATOMIC_RELEASE); syscall(SYS_futex, &ev->state, FUTEX_WAKE | FUTEX_PRIVATE_FLAG, 1000000); } static void event_wait(event_t* ev) { while (!__atomic_load_n(&ev->state, __ATOMIC_ACQUIRE)) syscall(SYS_futex, &ev->state, FUTEX_WAIT | FUTEX_PRIVATE_FLAG, 0, 0); } static int event_isset(event_t* ev) { return __atomic_load_n(&ev->state, __ATOMIC_ACQUIRE); } static void setup_test() { prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0); setpgrp(); } static void close_fds() { for (int fd = 3; fd < MAX_FDS; fd++) close(fd); } static long syz_func(int iter, int i) { int kvm_fd1 = -1, vm_fd1 = -1, vcpu_fd1 = -1; int kvm_fd2 = -1, vm_fd2 = -1, vcpu_fd2 = -1; int kvm_check_fd; int err_irq1, err_irq2; int err_vcpu1, err_vcpu2; kvm_check_fd = open("/dev/kvm", O_RDWR); if (kvm_check_fd == -1) { return 0; } close(kvm_check_fd); kvm_fd1 = open("/dev/kvm", O_RDWR); if (kvm_fd1 == -1) goto cleanup; vm_fd1 = ioctl(kvm_fd1, KVM_CREATE_VM, 0); if (vm_fd1 == -1) goto cleanup; errno = 0; ioctl(vm_fd1, KVM_CREATE_IRQCHIP, 0); err_irq1 = errno; kvm_fd2 = open("/dev/kvm", O_RDWR); if (kvm_fd2 == -1) goto cleanup; vm_fd2 = ioctl(kvm_fd2, KVM_CREATE_VM, 0); if (vm_fd2 == -1) goto cleanup; errno = 0; ioctl(vm_fd2, KVM_CREATE_IRQCHIP, 0); err_irq2 = errno; if (err_irq1 || err_irq2) goto cleanup; errno = 0; vcpu_fd1 = ioctl(vm_fd1, KVM_CREATE_VCPU, 0); err_vcpu1 = errno; errno = 0; vcpu_fd2 = ioctl(vm_fd2, KVM_CREATE_VCPU, 0); err_vcpu2 = errno; errno = 0; if ((vcpu_fd1 == -1) ^ (vcpu_fd2 == -1)) { fprintf(stderr, "[pid %d] foobar %d %d!\n", getpid(), iter, i); fprintf(stderr, "kvm_fd1=%d\n", kvm_fd1); fprintf(stderr, "kvm_fd2=%d\n", kvm_fd2); fprintf(stderr, "vm_fd1=%d, err_irq1=%d\n", vm_fd1, err_irq1); fprintf(stderr, "vm_fd2=%d, err_irq2=%d\n", vm_fd2, err_irq2); fprintf(stderr, "vcpu_fd1=%d, err_vcpu1=%d\n", vcpu_fd1, err_vcpu1); fprintf(stderr, "vcpu_fd2=%d, err_vcpu2=%d\n", vcpu_fd2, err_vcpu2); vcpu_fd1 = ioctl(vm_fd1, KVM_CREATE_VCPU, 1); err_vcpu1 = errno; errno = 0; fprintf(stderr, "trying again vcpu_fd1=%d, err_vcpu1=%d\n", vcpu_fd1, err_vcpu1); vcpu_fd2 = ioctl(vm_fd2, KVM_CREATE_VCPU, 1); err_vcpu2 = errno; errno = 0; fprintf(stderr, "trying again vcpu_fd2=%d, err_vcpu2=%d\n", vcpu_fd2, err_vcpu2); char f[64]; sprintf(f, "ls -l /proc/%d/fd", getpid()); system(f); fprintf(stderr, "[pid %d] foobar %d %d done!\n", getpid(), iter, i); } cleanup: if (vcpu_fd1 != -1) close(vcpu_fd1); if (vcpu_fd2 != -1) close(vcpu_fd2); if (vm_fd1 != -1) close(vm_fd1); if (vm_fd2 != -1) close(vm_fd2); if (kvm_fd1 != -1) close(kvm_fd1); if (kvm_fd2 != -1) close(kvm_fd2); return 0; } struct thread_t { int created, iter; event_t ready, done; }; static struct thread_t threads[16]; static int running; static void* thr(void* arg) { struct thread_t* th = (struct thread_t*)arg; for (;;) { event_wait(&th->ready); event_reset(&th->ready); for (int i = 0; i <= 32; i++) { syz_func(th->iter, i); } __atomic_fetch_sub(&running, 1, __ATOMIC_RELAXED); event_set(&th->done); } return 0; } static void execute_one(int iter) { write(1, "executing program\n", sizeof("executing program\n") - 1); int i, thread; for (thread = 0; thread < (int)(sizeof(threads) / sizeof(threads[0])); thread++) { struct thread_t* th = &threads[thread]; if (!th->created) { th->created = 1; th->iter = iter; event_init(&th->ready); event_init(&th->done); event_set(&th->done); thread_start(thr, th); } if (event_isset(&th->done)) { event_reset(&th->done); __atomic_fetch_add(&running, 1, __ATOMIC_RELAXED); event_set(&th->ready); } } for (i = 0; i < 100 && __atomic_load_n(&running, __ATOMIC_RELAXED); i++) usleep(1000); } int main(void) { int iter = 0; setup_test(); for (;;) { execute_one(iter++); close_fds(); } return 0; }