1 /*- 2 * Copyright (c) 2024 Klara, Inc. 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 * 6 * These tests demonstrate a bug in ppoll() and pselect() where a blocked 7 * signal can fire after the timer runs out but before the signal mask is 8 * restored. To do this, we fork a child process which installs a SIGINT 9 * handler and repeatedly calls either ppoll() or pselect() with a 1 ms 10 * timeout, while the parent repeatedly sends SIGINT to the child at 11 * intervals that start out at 1100 us and gradually decrease to 900 us. 12 * Each SIGINT resynchronizes parent and child, and sooner or later the 13 * parent hits the sweet spot and the SIGINT arrives at just the right 14 * time to demonstrate the bug. 15 */ 16 17 #include <sys/select.h> 18 #include <sys/wait.h> 19 20 #include <err.h> 21 #include <errno.h> 22 #include <poll.h> 23 #include <signal.h> 24 #include <stdbool.h> 25 #include <stdlib.h> 26 #include <unistd.h> 27 28 #include <atf-c.h> 29 30 static volatile sig_atomic_t caught[NSIG]; 31 32 static void 33 handler(int signo) 34 { 35 caught[signo]++; 36 } 37 38 static void 39 child(int rd, bool poll) 40 { 41 struct timespec timeout = { .tv_nsec = 1000000 }; 42 sigset_t set0, set1; 43 int ret; 44 45 /* empty mask for ppoll() / pselect() */ 46 sigemptyset(&set0); 47 48 /* block SIGINT, then install a handler for it */ 49 sigemptyset(&set1); 50 sigaddset(&set1, SIGINT); 51 sigprocmask(SIG_BLOCK, &set1, NULL); 52 signal(SIGINT, handler); 53 54 /* signal parent that we are ready */ 55 close(rd); 56 for (;;) { 57 /* sleep for 1 ms with signals unblocked */ 58 ret = poll ? ppoll(NULL, 0, &timeout, &set0) : 59 pselect(0, NULL, NULL, NULL, &timeout, &set0); 60 /* 61 * At this point, either ret == 0 (timer ran out) errno == 62 * EINTR (a signal was received). Any other outcome is 63 * abnormal. 64 */ 65 if (ret != 0 && errno != EINTR) 66 err(1, "p%s()", poll ? "poll" : "select"); 67 /* if ret == 0, we should not have caught any signals */ 68 if (ret == 0 && caught[SIGINT]) { 69 /* 70 * We successfully demonstrated the race. Restore 71 * the default action and re-raise SIGINT. 72 */ 73 signal(SIGINT, SIG_DFL); 74 raise(SIGINT); 75 /* Not reached */ 76 } 77 /* reset for next attempt */ 78 caught[SIGINT] = 0; 79 } 80 /* Not reached */ 81 } 82 83 static void 84 prace(bool poll) 85 { 86 int pd[2], status; 87 pid_t pid; 88 89 /* fork child process */ 90 if (pipe(pd) != 0) 91 err(1, "pipe()"); 92 if ((pid = fork()) < 0) 93 err(1, "fork()"); 94 if (pid == 0) { 95 close(pd[0]); 96 child(pd[1], poll); 97 /* Not reached */ 98 } 99 close(pd[1]); 100 101 /* wait for child to signal readiness */ 102 (void)read(pd[0], &pd[0], sizeof(pd[0])); 103 close(pd[0]); 104 105 /* repeatedly attempt to signal at just the right moment */ 106 for (useconds_t timeout = 1100; timeout > 900; timeout--) { 107 usleep(timeout); 108 if (kill(pid, SIGINT) != 0) { 109 if (errno != ENOENT) 110 err(1, "kill()"); 111 /* ENOENT means the child has terminated */ 112 break; 113 } 114 } 115 116 /* we're done, kill the child for sure */ 117 (void)kill(pid, SIGKILL); 118 if (waitpid(pid, &status, 0) < 0) 119 err(1, "waitpid()"); 120 121 /* assert that the child died of SIGKILL */ 122 ATF_REQUIRE(WIFSIGNALED(status)); 123 ATF_REQUIRE_MSG(WTERMSIG(status) == SIGKILL, 124 "child caught SIG%s", sys_signame[WTERMSIG(status)]); 125 } 126 127 ATF_TC_WITHOUT_HEAD(ppoll_race); 128 ATF_TC_BODY(ppoll_race, tc) 129 { 130 prace(true); 131 } 132 133 ATF_TC_WITHOUT_HEAD(pselect_race); 134 ATF_TC_BODY(pselect_race, tc) 135 { 136 prace(false); 137 } 138 139 ATF_TP_ADD_TCS(tp) 140 { 141 ATF_TP_ADD_TC(tp, ppoll_race); 142 ATF_TP_ADD_TC(tp, pselect_race); 143 return (atf_no_error()); 144 } 145