xref: /freebsd/tests/sys/kern/prace.c (revision 6748d4e0eb8ab918d55f69ae59a60ecbac8df85f)
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