Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/tests/sys/kern/prace.c
39483 views
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
146