1 /* $NetBSD: t_clofork.c,v 1.1 2025/07/17 19:50:40 kre 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