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