1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * This test is intended to reproduce a crash that happens when |
4 | * kvm_arch_hardware_disable is called and it attempts to unregister the user |
5 | * return notifiers. |
6 | */ |
7 | |
8 | #define _GNU_SOURCE |
9 | |
10 | #include <fcntl.h> |
11 | #include <pthread.h> |
12 | #include <semaphore.h> |
13 | #include <stdint.h> |
14 | #include <stdlib.h> |
15 | #include <unistd.h> |
16 | #include <sys/wait.h> |
17 | |
18 | #include <test_util.h> |
19 | |
20 | #include "kvm_util.h" |
21 | |
22 | #define VCPU_NUM 4 |
23 | #define SLEEPING_THREAD_NUM (1 << 4) |
24 | #define FORK_NUM (1ULL << 9) |
25 | #define DELAY_US_MAX 2000 |
26 | #define GUEST_CODE_PIO_PORT 4 |
27 | |
28 | sem_t *sem; |
29 | |
30 | static void guest_code(void) |
31 | { |
32 | for (;;) |
33 | ; /* Some busy work */ |
34 | printf("Should not be reached.\n" ); |
35 | } |
36 | |
37 | static void *run_vcpu(void *arg) |
38 | { |
39 | struct kvm_vcpu *vcpu = arg; |
40 | struct kvm_run *run = vcpu->run; |
41 | |
42 | vcpu_run(vcpu); |
43 | |
44 | TEST_ASSERT(false, "%s: exited with reason %d: %s" , |
45 | __func__, run->exit_reason, |
46 | exit_reason_str(run->exit_reason)); |
47 | pthread_exit(NULL); |
48 | } |
49 | |
50 | static void *sleeping_thread(void *arg) |
51 | { |
52 | int fd; |
53 | |
54 | while (true) { |
55 | fd = open("/dev/null" , O_RDWR); |
56 | close(fd); |
57 | } |
58 | TEST_ASSERT(false, "%s: exited" , __func__); |
59 | pthread_exit(NULL); |
60 | } |
61 | |
62 | static inline void check_create_thread(pthread_t *thread, pthread_attr_t *attr, |
63 | void *(*f)(void *), void *arg) |
64 | { |
65 | int r; |
66 | |
67 | r = pthread_create(thread, attr, f, arg); |
68 | TEST_ASSERT(r == 0, "%s: failed to create thread" , __func__); |
69 | } |
70 | |
71 | static inline void check_set_affinity(pthread_t thread, cpu_set_t *cpu_set) |
72 | { |
73 | int r; |
74 | |
75 | r = pthread_setaffinity_np(thread, sizeof(cpu_set_t), cpu_set); |
76 | TEST_ASSERT(r == 0, "%s: failed set affinity" , __func__); |
77 | } |
78 | |
79 | static inline void check_join(pthread_t thread, void **retval) |
80 | { |
81 | int r; |
82 | |
83 | r = pthread_join(thread, retval); |
84 | TEST_ASSERT(r == 0, "%s: failed to join thread" , __func__); |
85 | } |
86 | |
87 | static void run_test(uint32_t run) |
88 | { |
89 | struct kvm_vcpu *vcpu; |
90 | struct kvm_vm *vm; |
91 | cpu_set_t cpu_set; |
92 | pthread_t threads[VCPU_NUM]; |
93 | pthread_t throw_away; |
94 | void *b; |
95 | uint32_t i, j; |
96 | |
97 | CPU_ZERO(&cpu_set); |
98 | for (i = 0; i < VCPU_NUM; i++) |
99 | CPU_SET(i, &cpu_set); |
100 | |
101 | vm = vm_create(VCPU_NUM); |
102 | |
103 | pr_debug("%s: [%d] start vcpus\n" , __func__, run); |
104 | for (i = 0; i < VCPU_NUM; ++i) { |
105 | vcpu = vm_vcpu_add(vm, i, guest_code); |
106 | |
107 | check_create_thread(&threads[i], NULL, run_vcpu, vcpu); |
108 | check_set_affinity(threads[i], &cpu_set); |
109 | |
110 | for (j = 0; j < SLEEPING_THREAD_NUM; ++j) { |
111 | check_create_thread(&throw_away, NULL, sleeping_thread, |
112 | (void *)NULL); |
113 | check_set_affinity(throw_away, &cpu_set); |
114 | } |
115 | } |
116 | pr_debug("%s: [%d] all threads launched\n" , __func__, run); |
117 | sem_post(sem); |
118 | for (i = 0; i < VCPU_NUM; ++i) |
119 | check_join(threads[i], &b); |
120 | /* Should not be reached */ |
121 | TEST_ASSERT(false, "%s: [%d] child escaped the ninja" , __func__, run); |
122 | } |
123 | |
124 | void wait_for_child_setup(pid_t pid) |
125 | { |
126 | /* |
127 | * Wait for the child to post to the semaphore, but wake up periodically |
128 | * to check if the child exited prematurely. |
129 | */ |
130 | for (;;) { |
131 | const struct timespec wait_period = { .tv_sec = 1 }; |
132 | int status; |
133 | |
134 | if (!sem_timedwait(sem, &wait_period)) |
135 | return; |
136 | |
137 | /* Child is still running, keep waiting. */ |
138 | if (pid != waitpid(pid, &status, WNOHANG)) |
139 | continue; |
140 | |
141 | /* |
142 | * Child is no longer running, which is not expected. |
143 | * |
144 | * If it exited with a non-zero status, we explicitly forward |
145 | * the child's status in case it exited with KSFT_SKIP. |
146 | */ |
147 | if (WIFEXITED(status)) |
148 | exit(WEXITSTATUS(status)); |
149 | else |
150 | TEST_ASSERT(false, "Child exited unexpectedly" ); |
151 | } |
152 | } |
153 | |
154 | int main(int argc, char **argv) |
155 | { |
156 | uint32_t i; |
157 | int s, r; |
158 | pid_t pid; |
159 | |
160 | sem = sem_open("vm_sem" , O_CREAT | O_EXCL, 0644, 0); |
161 | sem_unlink("vm_sem" ); |
162 | |
163 | for (i = 0; i < FORK_NUM; ++i) { |
164 | pid = fork(); |
165 | TEST_ASSERT(pid >= 0, "%s: unable to fork" , __func__); |
166 | if (pid == 0) |
167 | run_test(i); /* This function always exits */ |
168 | |
169 | pr_debug("%s: [%d] waiting semaphore\n" , __func__, i); |
170 | wait_for_child_setup(pid); |
171 | r = (rand() % DELAY_US_MAX) + 1; |
172 | pr_debug("%s: [%d] waiting %dus\n" , __func__, i, r); |
173 | usleep(r); |
174 | r = waitpid(pid, &s, WNOHANG); |
175 | TEST_ASSERT(r != pid, |
176 | "%s: [%d] child exited unexpectedly status: [%d]" , |
177 | __func__, i, s); |
178 | pr_debug("%s: [%d] killing child\n" , __func__, i); |
179 | kill(pid, SIGKILL); |
180 | } |
181 | |
182 | sem_destroy(sem); |
183 | exit(0); |
184 | } |
185 | |