1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2026 ConnectWise
5 * Copyright (c) 2026 Mark Johnston <markj@FreeBSD.org>
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29 #include <sys/types.h>
30 #include <sys/user.h>
31 #include <sys/proc.h>
32 #include <sys/procdesc.h>
33 #include <sys/sysctl.h>
34 #include <sys/wait.h>
35
36 #include <fcntl.h>
37 #include <poll.h>
38 #include <pthread.h>
39 #include <stdio.h>
40 #include <unistd.h>
41
42 #include <atf-c.h>
43 #include <kvm.h>
44
45 /* Tests for procdesc(4) that aren't specific to any one syscall */
46
47 /*
48 * Block until a thread in the specified process is sleeping in the specified
49 * wait message.
50 */
51 static void
wait_for_naptime(pid_t pid,const char * wmesg)52 wait_for_naptime(pid_t pid, const char *wmesg)
53 {
54 kvm_t *kd;
55 int count;
56
57 kd = kvm_openfiles(NULL, "/dev/null", NULL, O_RDONLY, NULL);
58 ATF_REQUIRE(kd != NULL);
59 for (;;) {
60 struct kinfo_proc *kip;
61 int i;
62
63 usleep(1000);
64 kip = kvm_getprocs(kd, KERN_PROC_PID | KERN_PROC_INC_THREAD,
65 pid, &count);
66 ATF_REQUIRE(kip != NULL);
67 for (i = 0; i < count; i++) {
68 ATF_REQUIRE(kip[i].ki_stat != SZOMB);
69 if (kip[i].ki_stat == SSLEEP &&
70 strcmp(kip[i].ki_wmesg, wmesg) == 0)
71 break;
72 }
73 if (i < count)
74 break;
75 }
76
77 kvm_close(kd);
78 }
79
80 /*
81 * Even after waiting on a process descriptor with waitpid(2), the kernel will
82 * not recycle the pid until after the process descriptor is closed. That is
83 * important to prevent users from trying to wait() twice, the second time
84 * using a dangling pid.
85 *
86 * Whether this same anti-recycling behavior is used with pdwait() is
87 * unimportant, because pdwait _always_ uses a process descriptor.
88 */
89 ATF_TC_WITHOUT_HEAD(pid_recycle);
ATF_TC_BODY(pid_recycle,tc)90 ATF_TC_BODY(pid_recycle, tc)
91 {
92 size_t len;
93 int i, pd, pid_max;
94 pid_t dangle_pid;
95
96 len = sizeof(pid_max);
97 ATF_REQUIRE_EQ_MSG(0,
98 sysctlbyname("kern.pid_max", &pid_max, &len, NULL, 0),
99 "sysctlbyname: %s", strerror(errno));
100
101 /* Create a process descriptor */
102 dangle_pid = pdfork(&pd, PD_CLOEXEC | PD_DAEMON);
103 ATF_REQUIRE_MSG(dangle_pid >= 0, "pdfork: %s", strerror(errno));
104 if (dangle_pid == 0) {
105 // In child
106 _exit(0);
107 }
108 /*
109 * Reap the child, but don't close the pd, creating a dangling pid.
110 * Notably, it isn't a Zombie, because the process is reaped.
111 */
112 ATF_REQUIRE_EQ(dangle_pid, waitpid(dangle_pid, NULL, WEXITED));
113
114 /*
115 * Now create and kill pid_max additional children. Test to see if pid
116 * gets reused. If not, that means the kernel is correctly reserving
117 * the dangling pid from reuse.
118 */
119 for (i = 0; i < pid_max; i++) {
120 pid_t pid;
121
122 pid = vfork();
123 ATF_REQUIRE_MSG(pid >= 0, "vfork: %s", strerror(errno));
124 if (pid == 0)
125 _exit(0);
126 ATF_REQUIRE_MSG(pid != dangle_pid,
127 "pid got recycled after %d forks", i);
128 ATF_REQUIRE_EQ(pid, waitpid(pid, NULL, WEXITED));
129 }
130 close(pd);
131 }
132
133 static void *
poll_procdesc(void * arg)134 poll_procdesc(void *arg)
135 {
136 struct pollfd pfd;
137
138 pfd.fd = *(int *)arg;
139 pfd.events = POLLHUP;
140 (void)poll(&pfd, 1, 5000);
141 return ((void *)(uintptr_t)pfd.revents);
142 }
143
144 /*
145 * Regression test to exercise the case where a procdesc is closed while a
146 * thread is poll()ing it.
147 */
148 ATF_TC_WITHOUT_HEAD(poll_close_race);
ATF_TC_BODY(poll_close_race,tc)149 ATF_TC_BODY(poll_close_race, tc)
150 {
151 pthread_t thr;
152 pid_t pid;
153 uintptr_t revents;
154 int error, pd;
155
156 pid = pdfork(&pd, PD_DAEMON);
157 ATF_REQUIRE_MSG(pid >= 0, "pdfork: %s", strerror(errno));
158 if (pid == 0) {
159 pause();
160 _exit(0);
161 }
162
163 error = pthread_create(&thr, NULL, poll_procdesc, &pd);
164 ATF_REQUIRE_MSG(error == 0, "pthread_create: %s", strerror(error));
165
166 wait_for_naptime(getpid(), "select");
167
168 ATF_REQUIRE_MSG(close(pd) == 0, "close: %s", strerror(errno));
169
170 error = pthread_join(thr, (void *)&revents);
171 ATF_REQUIRE_MSG(error == 0, "pthread_join: %s", strerror(error));
172 ATF_REQUIRE_EQ(revents, POLLNVAL);
173 }
174
175 /*
176 * Verify that poll(2) of a procdesc returns POLLHUP when the process exits.
177 */
178 ATF_TC_WITHOUT_HEAD(poll_exit_wakeup);
ATF_TC_BODY(poll_exit_wakeup,tc)179 ATF_TC_BODY(poll_exit_wakeup, tc)
180 {
181 pthread_t thr;
182 uintptr_t revents;
183 pid_t pid;
184 int error, pd;
185
186 pid = pdfork(&pd, PD_DAEMON);
187 ATF_REQUIRE_MSG(pid >= 0, "pdfork: %s", strerror(errno));
188 if (pid == 0) {
189 pause();
190 _exit(0);
191 }
192
193 error = pthread_create(&thr, NULL, poll_procdesc, &pd);
194 ATF_REQUIRE_MSG(error == 0, "pthread_create: %s", strerror(error));
195
196 wait_for_naptime(getpid(), "select");
197
198 ATF_REQUIRE_MSG(pdkill(pd, SIGKILL) == 0,
199 "pdkill: %s", strerror(errno));
200
201 error = pthread_join(thr, (void *)&revents);
202 ATF_REQUIRE_MSG(error == 0, "pthread_join: %s", strerror(error));
203 ATF_REQUIRE_EQ(revents, POLLHUP);
204
205 ATF_REQUIRE_MSG(close(pd) == 0, "close: %s", strerror(errno));
206 }
207
ATF_TP_ADD_TCS(tp)208 ATF_TP_ADD_TCS(tp)
209 {
210 ATF_TP_ADD_TC(tp, pid_recycle);
211 ATF_TP_ADD_TC(tp, poll_close_race);
212 ATF_TP_ADD_TC(tp, poll_exit_wakeup);
213
214 return (atf_no_error());
215 }
216