| // SPDX-License-Identifier: GPL-2.0-only | |
| /* | |
| * This test is intended to reproduce a crash that happens when | |
| * kvm_arch_hardware_disable is called and it attempts to unregister the user | |
| * return notifiers. | |
| */ | |
| #include <fcntl.h> | |
| #include <semaphore.h> | |
| #include <stdint.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <sys/wait.h> | |
| #include <test_util.h> | |
| #include "kvm_syscalls.h" | |
| #include "kvm_util.h" | |
| #include "ucall_common.h" | |
| #define NR_VCPUS 4 | |
| #define NR_SLEEPERS_PER_VCPU 16 | |
| #define NR_ITERATIONS 512 | |
| #define DELAY_US_MAX 2000 | |
| static cpu_set_t threads_cpu_set; | |
| static sem_t *sem; | |
| static void guest_code(void) | |
| { | |
| for (;;) | |
| ; /* Some busy work */ | |
| GUEST_ASSERT(0); | |
| } | |
| static void *run_vcpu(void *arg) | |
| { | |
| struct kvm_vcpu *vcpu = arg; | |
| struct kvm_run *run = vcpu->run; | |
| #ifndef __GLIBC__ | |
| kvm_sched_setaffinity(0, sizeof(cpu_set_t), &threads_cpu_set); | |
| #endif | |
| vcpu_run(vcpu); | |
| TEST_FAIL("vCPU%d exited with reason %d: %s", | |
| vcpu->id, run->exit_reason, exit_reason_str(run->exit_reason)); | |
| } | |
| static void *sleeping_thread(void *arg) | |
| { | |
| int fd; | |
| #ifndef __GLIBC__ | |
| kvm_sched_setaffinity(0, sizeof(cpu_set_t), &threads_cpu_set); | |
| #endif | |
| while (1) { | |
| fd = open("/dev/null", O_RDWR); | |
| close(fd); | |
| } | |
| TEST_FAIL("%s: exited", __func__); | |
| } | |
| static void run_test(u32 run) | |
| { | |
| struct kvm_vcpu *vcpu; | |
| pthread_attr_t attr; | |
| struct kvm_vm *vm; | |
| pthread_t thread; | |
| u32 i, j; | |
| TEST_ASSERT_EQ(pthread_attr_init(&attr), 0); | |
| #ifdef __GLIBC__ | |
| TEST_ASSERT_EQ(pthread_attr_setaffinity_np(&attr, sizeof(cpu_set_t), &threads_cpu_set), 0); | |
| #endif | |
| vm = vm_create(NR_VCPUS); | |
| pr_debug("%s: [%d] start vcpus\n", __func__, run); | |
| for (i = 0; i < NR_VCPUS; ++i) { | |
| vcpu = vm_vcpu_add(vm, i, guest_code); | |
| kvm_pthread_create(&thread, &attr, run_vcpu, vcpu); | |
| for (j = 0; j < NR_SLEEPERS_PER_VCPU; ++j) | |
| kvm_pthread_create(&thread, &attr, sleeping_thread, (void *)NULL); | |
| } | |
| pr_debug("%s: [%d] all threads launched\n", __func__, run); | |
| sem_post(sem); | |
| /* Wait for the parent to SIGKILL this child. */ | |
| while (1) | |
| pause(); | |
| } | |
| void wait_for_child_setup(pid_t pid) | |
| { | |
| /* | |
| * Wait for the child to post to the semaphore, but wake up periodically | |
| * to check if the child exited prematurely. | |
| */ | |
| for (;;) { | |
| const struct timespec wait_period = { .tv_sec = 1 }; | |
| int status; | |
| if (!sem_timedwait(sem, &wait_period)) | |
| return; | |
| /* Child is still running, keep waiting. */ | |
| if (pid != waitpid(pid, &status, WNOHANG)) | |
| continue; | |
| /* | |
| * Child is no longer running, which is not expected. | |
| * | |
| * If it exited with a non-zero status, we explicitly forward | |
| * the child's status in case it exited with KSFT_SKIP. | |
| */ | |
| if (WIFEXITED(status)) | |
| exit(WEXITSTATUS(status)); | |
| else | |
| TEST_ASSERT(false, "Child exited unexpectedly"); | |
| } | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| cpu_set_t allowed_cpu_set; | |
| int s, r, cpu, i; | |
| pid_t pid; | |
| kvm_sched_getaffinity(0, sizeof(cpu_set_t), &allowed_cpu_set); | |
| for (i = 0; i < NR_VCPUS && CPU_COUNT(&allowed_cpu_set); i++) { | |
| cpu = kvm_pick_random_cpu(&allowed_cpu_set); | |
| CPU_CLR(cpu, &allowed_cpu_set); | |
| CPU_SET(cpu, &threads_cpu_set); | |
| } | |
| sem = sem_open("vm_sem", O_CREAT | O_EXCL, 0644, 0); | |
| sem_unlink("vm_sem"); | |
| for (i = 0; i < NR_ITERATIONS; ++i) { | |
| pid = fork(); | |
| TEST_ASSERT(pid >= 0, "%s: unable to fork", __func__); | |
| if (pid == 0) | |
| run_test(i); /* This function always exits */ | |
| pr_debug("%s: [%d] waiting semaphore\n", __func__, i); | |
| wait_for_child_setup(pid); | |
| r = (rand() % DELAY_US_MAX) + 1; | |
| pr_debug("%s: [%d] waiting %dus\n", __func__, i, r); | |
| usleep(r); | |
| r = waitpid(pid, &s, WNOHANG); | |
| TEST_ASSERT(r != pid, | |
| "%s: [%d] child exited unexpectedly status: [%d]", | |
| __func__, i, s); | |
| pr_debug("%s: [%d] killing child\n", __func__, i); | |
| kill(pid, SIGKILL); | |
| } | |
| sem_destroy(sem); | |
| exit(0); | |
| } |