t_clofork.c revision 1.1.4.2 1 /* $NetBSD: t_clofork.c,v 1.1.4.2 2025/08/02 05:58:01 perseant Exp $ */
2
3 /*-
4 * Copyright (c) 2024 The NetBSD Foundation, Inc.
5 * All rights reserved.
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 NETBSD FOUNDATION, INC. AND CONTRIBUTORS
17 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
18 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
20 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 /* Adapted from t_cloexec.c */
30
31 #include <sys/cdefs.h>
32
33 #include <sys/types.h>
34
35 #include <sys/bitops.h>
36 #include <sys/event.h>
37 #include <sys/socket.h>
38 #include <sys/un.h>
39 #include <sys/wait.h>
40
41 #include <atf-c.h>
42 #include <fcntl.h>
43 #include <limits.h>
44 #include <spawn.h>
45 #include <stdio.h>
46 #include <unistd.h>
47
48 #include "h_macros.h"
49
50 #if defined(O_CLOFORK) && O_CLOFORK != 0
51 /*
52 * Test close-on-fork as set in various ways
53 */
54
55 static int
56 open_via_accept4(void)
57 {
58 static const union {
59 struct sockaddr sa;
60 struct sockaddr_un sun;
61 } name = { .sun = {
62 .sun_family = AF_LOCAL,
63 .sun_path = "socket",
64 } };
65 int slisten, saccept, c;
66
67 /*
68 * Create a listening server socket and bind it to the path.
69 */
70 RL(slisten = socket(PF_LOCAL, SOCK_STREAM, 0));
71 RL(bind(slisten, &name.sa, sizeof(name)));
72 RL(listen(slisten, SOMAXCONN));
73
74 /*
75 * Create an active client socket and connect it to the path --
76 * nonblocking, so we don't deadlock here. If connect doesn't
77 * succeed immediately, it had better fail immediately with
78 * EINPROGRESS.
79 */
80 RL(c = socket(PF_LOCAL, SOCK_STREAM|SOCK_NONBLOCK, 0));
81 if (connect(c, &name.sa, sizeof(name)) == -1) {
82 ATF_CHECK_EQ_MSG(errno, EINPROGRESS, "connect failed %d: %s",
83 errno, strerror(errno));
84 }
85
86 /*
87 * Accept a socket on the server side with SOCK_CLOFORK.
88 */
89 RL(saccept = accept4(slisten, /*addr*/NULL, /*addrlen*/NULL,
90 SOCK_CLOFORK));
91 return saccept;
92 }
93
94 static int
95 open_via_clonedev(void)
96 {
97 int fd;
98
99 RL(fd = open("/dev/drvctl", O_RDONLY|O_CLOFORK));
100
101 return fd;
102 }
103
104 static int
105 open_via_dup3(void)
106 {
107 int fd3;
108
109 RL(fd3 = dup3(STDIN_FILENO, 3, O_CLOFORK));
110 ATF_REQUIRE_EQ_MSG(fd3, 3, "dup3(STDIN_FILENO, 3, ...)"
111 " failed to return 3: %d", fd3);
112
113 return fd3;
114 }
115
116 static int
117 open_via_fcntldupfd(void)
118 {
119 int fd;
120
121 RL(fd = fcntl(STDIN_FILENO, F_DUPFD_CLOFORK, 0));
122
123 return fd;
124 }
125
126 static int
127 open_via_kqueue(void)
128 {
129 int fd;
130
131 RL(fd = kqueue1(O_CLOFORK));
132
133 return fd;
134 }
135
136 static int
137 open_via_openclofork(void)
138 {
139 int fd;
140
141 RL(fd = open("file", O_RDWR|O_CREAT|O_CLOFORK, 0644));
142
143 return fd;
144 }
145
146 static int
147 open_via_openfcntlclofork(void)
148 {
149 int fd;
150
151 RL(fd = open("file", O_RDWR|O_CREAT, 0644));
152 RL(fcntl(fd, F_SETFD, FD_CLOFORK));
153
154 return fd;
155 }
156
157 static int
158 open_via_pipe2rd(void)
159 {
160 int fd[2];
161
162 RL(pipe2(fd, O_CLOFORK));
163
164 return fd[0];
165 }
166
167 static int
168 open_via_pipe2wr(void)
169 {
170 int fd[2];
171
172 RL(pipe2(fd, O_CLOFORK));
173
174 return fd[1];
175 }
176
177 static int
178 open_via_paccept(void)
179 {
180 static const union {
181 struct sockaddr sa;
182 struct sockaddr_un sun;
183 } name = { .sun = {
184 .sun_family = AF_LOCAL,
185 .sun_path = "socket",
186 } };
187 int slisten, saccept, c;
188
189 /*
190 * Create a listening server socket and bind it to the path.
191 */
192 RL(slisten = socket(PF_LOCAL, SOCK_STREAM, 0));
193 RL(bind(slisten, &name.sa, sizeof(name)));
194 RL(listen(slisten, SOMAXCONN));
195
196 /*
197 * Create an active client socket and connect it to the path --
198 * nonblocking, so we don't deadlock here. If connect doesn't
199 * succeed immediately, it had better fail immediately with
200 * EINPROGRESS.
201 */
202 RL(c = socket(PF_LOCAL, SOCK_STREAM|SOCK_NONBLOCK, 0));
203 if (connect(c, &name.sa, sizeof(name)) == -1) {
204 ATF_CHECK_EQ_MSG(errno, EINPROGRESS, "connect failed %d: %s",
205 errno, strerror(errno));
206 }
207
208 /*
209 * Accept a socket on the server side with SOCK_CLOFORK.
210 */
211 RL(saccept = paccept(slisten, /*addr*/NULL, /*addrlen*/NULL,
212 /*sigmask*/NULL, SOCK_CLOFORK));
213 return saccept;
214 }
215
216 static int
217 open_via_socket(void)
218 {
219 int fd;
220
221 RL(fd = socket(PF_LOCAL, SOCK_STREAM|SOCK_CLOFORK, 0));
222
223 return fd;
224 }
225
226 static int
227 open_via_socketpair0(void)
228 {
229 int fd[2];
230
231 RL(socketpair(PF_LOCAL, SOCK_STREAM|SOCK_CLOFORK, 0, fd));
232
233 return fd[0];
234 }
235
236 static int
237 open_via_socketpair1(void)
238 {
239 int fd[2];
240
241 RL(socketpair(PF_LOCAL, SOCK_STREAM|SOCK_CLOFORK, 0, fd));
242
243 return fd[1];
244 }
245
246 static void
247 check_clofork(const struct atf_tc *tc, int fd,
248 pid_t (*execfn)(char *, char *const[]))
249 {
250 char h_clofork[PATH_MAX];
251 char fdstr[(ilog2(INT_MAX) + 1)/(ilog2(10) - 1) + 1];
252 char *const argv[] = {__UNCONST("h_cloexec"), fdstr, NULL};
253 pid_t child, waitedpid;
254 int status;
255
256 /*
257 * Format the h_clofork helper executable path, which lives in
258 * the test's directory (typically /usr/tests/kernel), and the
259 * argument of a file descriptor in decimal.
260 */
261 snprintf(h_clofork, sizeof(h_clofork), "%s/h_cloexec",
262 atf_tc_get_config_var(tc, "srcdir"));
263 snprintf(fdstr, sizeof(fdstr), "%d", fd);
264
265 /*
266 * Execute h_clofork as a subprocess.
267 */
268 child = (*execfn)(h_clofork, argv);
269
270 /*
271 * Wait for the child to complete.
272 */
273 RL(waitedpid = waitpid(child, &status, 0));
274 ATF_CHECK_EQ_MSG(child, waitedpid, "waited for %jd, got %jd",
275 (intmax_t)child, (intmax_t)waitedpid);
276
277 /*
278 * Verify the child exited normally.
279 */
280 if (WIFSIGNALED(status)) {
281 atf_tc_fail("subprocess terminated on signal %d",
282 WTERMSIG(status));
283 return;
284 } else if (!WIFEXITED(status)) {
285 atf_tc_fail("subprocess failed to exit normally: status=0x%x",
286 status);
287 return;
288 }
289
290 /*
291 * h_clofork is supposed to exit status 0 if an operation on
292 * the fd failed with EBADFD, 1 if it unexpectedly succeeded,
293 * 127 if exec returned, or something else if anything else
294 * happened.
295 */
296 switch (WEXITSTATUS(status)) {
297 case 0: /* success -- closed on exec */
298 return;
299 case 1: /* fail -- not closed on exec */
300 atf_tc_fail("fd was not closed on exec");
301 return;
302 case 127: /* exec failed */
303 atf_tc_fail("failed to exec h_cloexec");
304 return;
305 default: /* something else went wong */
306 atf_tc_fail("h_cloexec failed unexpectedly: %d",
307 WEXITSTATUS(status));
308 return;
309 }
310 }
311
312 static pid_t
313 exec_via_forkexecve(char *prog, char *const argv[])
314 {
315 pid_t pid;
316
317 RL(pid = fork());
318 if (pid == 0) { /* child */
319 if (execve(prog, argv, /*envp*/NULL) == -1)
320 _exit(127);
321 abort();
322 }
323
324 /* parent */
325 return pid;
326 }
327
328 static pid_t
329 exec_via_vforkexecve(char *prog, char *const argv[])
330 {
331 pid_t pid;
332
333 RL(pid = vfork());
334 if (pid == 0) { /* child */
335 if (execve(prog, argv, /*envp*/NULL) == -1)
336 _exit(127);
337 abort();
338 }
339
340 /* parent */
341 return pid;
342 }
343
344 static pid_t
345 exec_via_posixspawn(char *prog, char *const argv[])
346 {
347 pid_t pid;
348
349 RZ(posix_spawn(&pid, prog, /*file_actions*/NULL, /*attrp*/NULL, argv,
350 /*envp*/NULL));
351
352 return pid;
353 }
354
355 /*
356 * Full cartesian product is not really important here -- the paths for
357 * open and the paths for exec are independent. So we try
358 * pipe2(O_CLOFORK) with each exec path, and we try each open path with
359 * posix_spawn.
360 */
361
362 #define CLOFORK_TEST(test, openvia, execvia, descr) \
363 ATF_TC(test); \
364 ATF_TC_HEAD(test, tc) \
365 { \
366 atf_tc_set_md_var(tc, "descr", descr); \
367 } \
368 ATF_TC_BODY(test, tc) \
369 { \
370 check_clofork(tc, openvia(), &execvia); \
371 }
372
373 CLOFORK_TEST(pipe2rd_forkexecve, open_via_pipe2rd, exec_via_forkexecve,
374 "pipe2(O_CLOFORK) reader is closed in child on fork/exec")
375 CLOFORK_TEST(pipe2rd_vforkexecve, open_via_pipe2rd, exec_via_vforkexecve,
376 "pipe2(O_CLOFORK) reader is closed in child on vfork/exec")
377 CLOFORK_TEST(pipe2rd_posixspawn, open_via_pipe2rd, exec_via_posixspawn,
378 "pipe2(O_CLOFORK) reader is closed in child on posix_spawn")
379
380 CLOFORK_TEST(accept4_posixspawn, open_via_accept4, exec_via_posixspawn,
381 "accept4(SOCK_CLOFORK) is closed in child on posix_spawn");
382 CLOFORK_TEST(clonedev_posixspawn, open_via_clonedev, exec_via_posixspawn,
383 "open(\"/dev/drvctl\") is closed in child on posix_spawn");
384 CLOFORK_TEST(dup3_posixspawn, open_via_dup3, exec_via_posixspawn,
385 "dup3(..., O_CLOFORK) is closed in child on posix_spawn");
386 CLOFORK_TEST(fcntldupfd_posixspawn, open_via_fcntldupfd, exec_via_posixspawn,
387 "fcntl(STDIN_FILENO, F_DUPFD_CLOFORK) is closed in child on posix_spawn");
388 CLOFORK_TEST(kqueue_posixspawn, open_via_kqueue, exec_via_posixspawn,
389 "kqueue1(O_CLOFORK) is closed in child on posix_spawn");
390 CLOFORK_TEST(openclofork_posixspawn, open_via_openclofork, exec_via_posixspawn,
391 "open(O_CLOFORK) is closed in child on posix_spawn");
392 CLOFORK_TEST(openfcntlclofork_posixspawn, open_via_openfcntlclofork,
393 exec_via_posixspawn,
394 "fcntl(open(...), F_SETFD, O_CLOFORK) is closed in child on posix_spawn");
395 CLOFORK_TEST(pipe2wr_posixspawn, open_via_pipe2wr, exec_via_posixspawn,
396 "pipe2(O_CLOFORK) writer is closed in child on posix_spawn")
397 CLOFORK_TEST(paccept_posixspawn, open_via_paccept, exec_via_posixspawn,
398 "paccept(..., SOCK_CLOFORK) is closed in child on posix_spawn")
399 CLOFORK_TEST(socket_posixspawn, open_via_socket, exec_via_posixspawn,
400 "socket(SOCK_CLOFORK) is closed in child on posix_spawn")
401 CLOFORK_TEST(socketpair0_posixspawn, open_via_socketpair0, exec_via_posixspawn,
402 "socketpair(SOCK_CLOFORK) side 0 is closed in child on posix_spawn")
403 CLOFORK_TEST(socketpair1_posixspawn, open_via_socketpair1, exec_via_posixspawn,
404 "socketpair(SOCK_CLOFORK) side 1 is closed in child on posix_spawn")
405
406 ATF_TP_ADD_TCS(tp)
407 {
408
409 ATF_TP_ADD_TC(tp, accept4_posixspawn);
410 ATF_TP_ADD_TC(tp, clonedev_posixspawn);
411 ATF_TP_ADD_TC(tp, dup3_posixspawn);
412 ATF_TP_ADD_TC(tp, fcntldupfd_posixspawn);
413 ATF_TP_ADD_TC(tp, kqueue_posixspawn);
414 ATF_TP_ADD_TC(tp, openclofork_posixspawn);
415 ATF_TP_ADD_TC(tp, openfcntlclofork_posixspawn);
416 ATF_TP_ADD_TC(tp, paccept_posixspawn);
417 ATF_TP_ADD_TC(tp, pipe2rd_forkexecve);
418 ATF_TP_ADD_TC(tp, pipe2rd_posixspawn);
419 ATF_TP_ADD_TC(tp, pipe2rd_vforkexecve);
420 ATF_TP_ADD_TC(tp, pipe2wr_posixspawn);
421 ATF_TP_ADD_TC(tp, socket_posixspawn);
422 ATF_TP_ADD_TC(tp, socketpair0_posixspawn);
423 ATF_TP_ADD_TC(tp, socketpair1_posixspawn);
424
425 return atf_no_error();
426 }
427
428 #else /* No O_CLOFORK */
429
430 ATF_TC(not_implemented);
431 ATF_TC_HEAD(not_implemented, tc)
432 {
433 atf_tc_set_md_var(tc, "descr", "Unimplemented O_CLOFORK");
434 }
435 ATF_TC_BODY(not_implemented, tc)
436 {
437 atf_tc_skip("close-on-fork not yet available");
438 }
439 ATF_TP_ADD_TCS(tp)
440 {
441 ATF_TP_ADD_TC(tp, not_implemented);
442
443 return atf_no_error();
444 }
445 #endif
446