1 /* $OpenBSD: kqueue-regress.c,v 1.5 2022/03/29 19:04:19 millert Exp $ */
2 /*
3 * Written by Anton Lindqvist <anton@openbsd.org> 2018 Public Domain
4 */
5
6 #include <sys/types.h>
7 #include <sys/event.h>
8 #include <sys/mman.h>
9 #include <sys/resource.h>
10 #include <sys/select.h>
11 #include <sys/time.h>
12 #include <sys/wait.h>
13
14 #include <assert.h>
15 #include <err.h>
16 #include <poll.h>
17 #include <signal.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <unistd.h>
22
23 #include "main.h"
24
25 static int do_regress1(void);
26 static int do_regress2(void);
27 static int do_regress3(void);
28 static int do_regress4(void);
29 static int do_regress5(void);
30 static int do_regress6(void);
31
32 static void make_chain(int);
33
34 int
do_regress(int n)35 do_regress(int n)
36 {
37 switch (n) {
38 case 1:
39 return do_regress1();
40 case 2:
41 return do_regress2();
42 case 3:
43 return do_regress3();
44 case 4:
45 return do_regress4();
46 case 5:
47 return do_regress5();
48 case 6:
49 return do_regress6();
50 default:
51 errx(1, "unknown regress test number %d", n);
52 }
53 }
54
55 /*
56 * Regression test for NULL-deref in knote_processexit().
57 */
58 static int
do_regress1(void)59 do_regress1(void)
60 {
61 struct kevent kev[2];
62 int kq;
63
64 ASS((kq = kqueue()) >= 0,
65 warn("kqueue"));
66
67 EV_SET(&kev[0], kq, EVFILT_READ, EV_ADD, 0, 0, NULL);
68 EV_SET(&kev[1], SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
69 ASS(kevent(kq, kev, 2, NULL, 0, NULL) == 0,
70 warn("can't register events on kqueue"));
71
72 /* kq intentionally left open */
73
74 return 0;
75 }
76
77 /*
78 * Regression test for use-after-free in kqueue_close().
79 */
80 static int
do_regress2(void)81 do_regress2(void)
82 {
83 pid_t pid;
84 int i, status;
85
86 /* Run twice in order to trigger the panic faster, if still present. */
87 for (i = 0; i < 2; i++) {
88 pid = fork();
89 if (pid == -1)
90 err(1, "fork");
91
92 if (pid == 0) {
93 struct kevent kev[1];
94 int p0[2], p1[2];
95 int kq;
96
97 if (pipe(p0) == -1)
98 err(1, "pipe");
99 if (pipe(p1) == -1)
100 err(1, "pipe");
101
102 kq = kqueue();
103 if (kq == -1)
104 err(1, "kqueue");
105
106 EV_SET(&kev[0], p0[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
107 if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
108 err(1, "kevent");
109
110 EV_SET(&kev[0], p1[1], EVFILT_READ, EV_ADD, 0, 0, NULL);
111 if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
112 err(1, "kevent");
113
114 EV_SET(&kev[0], p1[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
115 if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
116 err(1, "kevent");
117
118 _exit(0);
119 }
120
121 if (waitpid(pid, &status, 0) == -1)
122 err(1, "waitpid");
123 assert(WIFEXITED(status));
124 assert(WEXITSTATUS(status) == 0);
125 }
126
127 return 0;
128 }
129
130 /*
131 * Regression test for kernel stack exhaustion.
132 */
133 static int
do_regress3(void)134 do_regress3(void)
135 {
136 pid_t pid;
137 int dir, status;
138
139 for (dir = 0; dir < 2; dir++) {
140 pid = fork();
141 if (pid == -1)
142 err(1, "fork");
143
144 if (pid == 0) {
145 make_chain(dir);
146 _exit(0);
147 }
148
149 if (waitpid(pid, &status, 0) == -1)
150 err(1, "waitpid");
151 assert(WIFEXITED(status));
152 assert(WEXITSTATUS(status) == 0);
153 }
154
155 return 0;
156 }
157
158 static void
make_chain(int dir)159 make_chain(int dir)
160 {
161 struct kevent kev[1];
162 int i, kq, prev;
163
164 /*
165 * Build a chain of kqueues and leave the files open.
166 * If the chain is long enough and properly oriented, a broken kernel
167 * can exhaust the stack when this process exits.
168 */
169 for (i = 0, prev = -1; i < 120; i++, prev = kq) {
170 kq = kqueue();
171 if (kq == -1)
172 err(1, "kqueue");
173 if (prev == -1)
174 continue;
175
176 if (dir == 0) {
177 EV_SET(&kev[0], prev, EVFILT_READ, EV_ADD, 0, 0, NULL);
178 if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
179 err(1, "kevent");
180 } else {
181 EV_SET(&kev[0], kq, EVFILT_READ, EV_ADD, 0, 0, NULL);
182 if (kevent(prev, kev, 1, NULL, 0, NULL) == -1)
183 err(1, "kevent");
184 }
185 }
186 }
187
188 /*
189 * Regression test for kernel stack exhaustion.
190 */
191 static int
do_regress4(void)192 do_regress4(void)
193 {
194 static const int nkqueues = 500;
195 struct kevent kev[1];
196 struct rlimit rlim;
197 struct timespec ts;
198 int fds[2], i, kq = -1, prev;
199
200 if (getrlimit(RLIMIT_NOFILE, &rlim) == -1)
201 err(1, "getrlimit");
202 if (rlim.rlim_cur < nkqueues + 8) {
203 rlim.rlim_cur = nkqueues + 8;
204 if (setrlimit(RLIMIT_NOFILE, &rlim) == -1) {
205 printf("RLIMIT_NOFILE is too low and can't raise it\n");
206 printf("SKIPPED\n");
207 exit(0);
208 }
209 }
210
211 if (pipe(fds) == -1)
212 err(1, "pipe");
213
214 /* Build a chain of kqueus. The first kqueue refers to the pipe. */
215 for (i = 0, prev = fds[0]; i < nkqueues; i++, prev = kq) {
216 kq = kqueue();
217 if (kq == -1)
218 err(1, "kqueue");
219
220 EV_SET(&kev[0], prev, EVFILT_READ, EV_ADD, 0, 0, NULL);
221 if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
222 err(1, "kevent");
223 }
224
225 /*
226 * Trigger a cascading event through the chain.
227 * If the chain is long enough, a broken kernel can run out
228 * of kernel stack space.
229 */
230 write(fds[1], "x", 1);
231
232 /*
233 * Check that the event gets propagated.
234 * The propagation is not instantaneous, so allow a brief pause.
235 */
236 ts.tv_sec = 5;
237 ts.tv_nsec = 0;
238 assert(kevent(kq, NULL, 0, kev, 1, NULL) == 1);
239
240 return 0;
241 }
242
243 /*
244 * Regression test for select and poll with kqueue.
245 */
246 static int
do_regress5(void)247 do_regress5(void)
248 {
249 fd_set fdset;
250 struct kevent kev[1];
251 struct pollfd pfd[1];
252 struct timeval tv;
253 int fds[2], kq, ret;
254
255 if (pipe(fds) == -1)
256 err(1, "pipe");
257
258 kq = kqueue();
259 if (kq == -1)
260 err(1, "kqueue");
261 EV_SET(&kev[0], fds[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
262 if (kevent(kq, kev, 1, NULL, 0, NULL) == -1)
263 err(1, "kevent");
264
265 /* Check that no event is reported. */
266
267 FD_ZERO(&fdset);
268 FD_SET(kq, &fdset);
269 tv.tv_sec = 0;
270 tv.tv_usec = 0;
271 ret = select(kq + 1, &fdset, NULL, NULL, &tv);
272 if (ret == -1)
273 err(1, "select");
274 assert(ret == 0);
275
276 pfd[0].fd = kq;
277 pfd[0].events = POLLIN;
278 pfd[0].revents = 0;
279 ret = poll(pfd, 1, 0);
280 if (ret == -1)
281 err(1, "poll");
282 assert(ret == 0);
283
284 /* Trigger an event. */
285 write(fds[1], "x", 1);
286
287 /* Check that the event gets reported. */
288
289 FD_ZERO(&fdset);
290 FD_SET(kq, &fdset);
291 tv.tv_sec = 5;
292 tv.tv_usec = 0;
293 ret = select(kq + 1, &fdset, NULL, NULL, &tv);
294 if (ret == -1)
295 err(1, "select");
296 assert(ret == 1);
297 assert(FD_ISSET(kq, &fdset));
298
299 pfd[0].fd = kq;
300 pfd[0].events = POLLIN;
301 pfd[0].revents = 0;
302 ret = poll(pfd, 1, 5000);
303 if (ret == -1)
304 err(1, "poll");
305 assert(ret == 1);
306 assert(pfd[0].revents & POLLIN);
307
308 return 0;
309 }
310
311 int
test_regress6(int kq,size_t len)312 test_regress6(int kq, size_t len)
313 {
314 const struct timespec nap_time = { 0, 1 };
315 int i, kstatus, wstatus;
316 struct kevent event;
317 pid_t child, pid;
318 void *addr;
319
320 child = fork();
321 switch (child) {
322 case -1:
323 warn("fork");
324 return -1;
325 case 0:
326 /* fork a bunch of zombies to keep the reaper busy, then exit */
327 signal(SIGCHLD, SIG_IGN);
328 for (i = 0; i < 1000; i++) {
329 if (fork() == 0) {
330 /* Dirty some memory so uvm_exit has work. */
331 addr = mmap(NULL, len, PROT_READ|PROT_WRITE,
332 MAP_ANON, -1, 0);
333 if (addr == MAP_FAILED)
334 err(1, "mmap");
335 memset(addr, 'A', len);
336 nanosleep(&nap_time, NULL);
337 _exit(2);
338 }
339 }
340 nanosleep(&nap_time, NULL);
341 _exit(1);
342 default:
343 /* parent */
344 break;
345 }
346
347 /* Register NOTE_EXIT and wait for child. */
348 EV_SET(&event, child, EVFILT_PROC, EV_ADD|EV_ONESHOT, NOTE_EXIT, 0,
349 NULL);
350 if (kevent(kq, &event, 1, &event, 1, NULL) != 1)
351 err(1, "kevent");
352 if (event.flags & EV_ERROR)
353 errx(1, "kevent: %s", strerror(event.data));
354 if (event.ident != child)
355 errx(1, "expected child %d, got %lu", child, event.ident);
356 kstatus = event.data;
357 if (!WIFEXITED(kstatus))
358 errx(1, "child did not exit?");
359
360 pid = waitpid(child, &wstatus, WNOHANG);
361 switch (pid) {
362 case -1:
363 err(1, "waitpid %d", child);
364 case 0:
365 printf("kevent: child %d exited %d\n", child,
366 WEXITSTATUS(kstatus));
367 printf("waitpid: child %d not ready\n", child);
368 break;
369 default:
370 if (wstatus != kstatus) {
371 /* macOS has a bug where kstatus is 0 */
372 warnx("kevent status 0x%x != waitpid status 0x%x",
373 kstatus, wstatus);
374 }
375 break;
376 }
377
378 return pid;
379 }
380
381 /*
382 * Regression test for NOTE_EXIT waitability.
383 */
384 static int
do_regress6(void)385 do_regress6(void)
386 {
387 int i, kq, page_size, rc;
388 struct rlimit rlim;
389
390 /* Bump process limits since we fork a lot. */
391 if (getrlimit(RLIMIT_NPROC, &rlim) == -1)
392 err(1, "getrlimit(RLIMIT_NPROC)");
393 rlim.rlim_cur = rlim.rlim_max;
394 if (setrlimit(RLIMIT_NPROC, &rlim) == -1)
395 err(1, "setrlimit(RLIMIT_NPROC)");
396
397 kq = kqueue();
398 if (kq == -1)
399 err(1, "kqueue");
400
401 page_size = getpagesize();
402
403 /* This test is inherently racey but fails within a few iterations. */
404 for (i = 0; i < 25; i++) {
405 rc = test_regress6(kq, page_size);
406 switch (rc) {
407 case -1:
408 goto done;
409 case 0:
410 printf("child not ready when NOTE_EXIT received");
411 if (i != 0)
412 printf(" (%d iterations)", i + 1);
413 putchar('\n');
414 goto done;
415 default:
416 /* keep trying */
417 continue;
418 }
419 }
420 printf("child exited as expected when NOTE_EXIT received\n");
421
422 done:
423 close(kq);
424 return rc <= 0;
425 }
426