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