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