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 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 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 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 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 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 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 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 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 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