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
handler(int signo)33 handler(int signo)
34 {
35 caught[signo]++;
36 }
37
38 static void
child(int rd,bool poll)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
prace(bool poll)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);
ATF_TC_BODY(ppoll_race,tc)128 ATF_TC_BODY(ppoll_race, tc)
129 {
130 prace(true);
131 }
132
133 ATF_TC_WITHOUT_HEAD(pselect_race);
ATF_TC_BODY(pselect_race,tc)134 ATF_TC_BODY(pselect_race, tc)
135 {
136 prace(false);
137 }
138
ATF_TP_ADD_TCS(tp)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