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
handler(int signo)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
child(int rd,bool poll)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
prace(bool poll)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);
ATF_TC_BODY(ppoll_race,tc)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);
ATF_TC_BODY(pselect_race,tc)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
ATF_TP_ADD_TCS(tp)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