1 /* $NetBSD: t_spawn.c,v 1.12 2025/03/16 15:35:36 riastradh Exp $ */ 2 3 /*- 4 * Copyright (c) 2012, 2021 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Charles Zhang <charles (at) NetBSD.org> and 9 * Martin Husemann <martin (at) NetBSD.org>. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. Redistributions in binary form must reproduce the above copyright 17 * notice, this list of conditions and the following disclaimer in the 18 * documentation and/or other materials provided with the distribution. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 30 * POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 #include <sys/cdefs.h> 34 __RCSID("$NetBSD: t_spawn.c,v 1.12 2025/03/16 15:35:36 riastradh Exp $"); 35 36 #include <sys/fcntl.h> 37 #include <sys/stat.h> 38 #include <sys/types.h> 39 #include <sys/wait.h> 40 41 #include <atf-c.h> 42 #include <errno.h> 43 #include <fcntl.h> 44 #include <limits.h> 45 #include <signal.h> 46 #include <spawn.h> 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <string.h> 50 #include <time.h> 51 #include <unistd.h> 52 53 #include "fa_spawn_utils.h" 54 #include "h_macros.h" 55 56 static void check_success(const char *, const char *); 57 58 ATF_TC(t_spawn_ls); 59 ATF_TC_HEAD(t_spawn_ls, tc) 60 { 61 atf_tc_set_md_var(tc, "descr", 62 "Tests a simple posix_spawn executing /bin/ls"); 63 } 64 ATF_TC_BODY(t_spawn_ls, tc) 65 { 66 char * const args[] = { __UNCONST("ls"), __UNCONST("-la"), NULL }; 67 68 RZ(posix_spawn(NULL, "/bin/ls", NULL, NULL, args, NULL)); 69 } 70 71 ATF_TC(t_spawnp_ls); 72 ATF_TC_HEAD(t_spawnp_ls, tc) 73 { 74 atf_tc_set_md_var(tc, "descr", 75 "Tests a simple posix_spawnp executing ls via $PATH"); 76 } 77 ATF_TC_BODY(t_spawnp_ls, tc) 78 { 79 char * const args[] = { __UNCONST("ls"), __UNCONST("-la"), NULL }; 80 81 RZ(posix_spawnp(NULL, "ls", NULL, NULL, args, NULL)); 82 } 83 84 static void 85 spawn_error(const atf_tc_t *tc, const char *name, int error) 86 { 87 char buf[PATH_MAX]; 88 char * const args[] = { __UNCONST(name), NULL }; 89 int err; 90 91 snprintf(buf, sizeof(buf), "%s/%s", 92 atf_tc_get_config_var(tc, "srcdir"), name); 93 err = posix_spawn(NULL, buf, NULL, NULL, args, NULL); 94 ATF_REQUIRE_MSG(err == error, "expected error %d, " 95 "got %d when spawning %s", error, err, buf); 96 } 97 98 ATF_TC(t_spawn_zero); 99 ATF_TC_HEAD(t_spawn_zero, tc) 100 { 101 atf_tc_set_md_var(tc, "descr", 102 "posix_spawn an invalid binary"); 103 } 104 ATF_TC_BODY(t_spawn_zero, tc) 105 { 106 spawn_error(tc, "h_zero", ENOEXEC); 107 } 108 109 ATF_TC(t_spawn_missing); 110 ATF_TC_HEAD(t_spawn_missing, tc) 111 { 112 atf_tc_set_md_var(tc, "descr", 113 "posix_spawn a nonexistent binary"); 114 } 115 ATF_TC_BODY(t_spawn_missing, tc) 116 { 117 spawn_error(tc, "h_nonexist", ENOENT); 118 } 119 120 ATF_TC(t_spawn_nonexec); 121 ATF_TC_HEAD(t_spawn_nonexec, tc) 122 { 123 atf_tc_set_md_var(tc, "descr", 124 "posix_spawn a script with non existing interpreter"); 125 } 126 ATF_TC_BODY(t_spawn_nonexec, tc) 127 { 128 spawn_error(tc, "h_nonexec", ENOENT); 129 } 130 131 ATF_TC(t_spawn_child); 132 ATF_TC_HEAD(t_spawn_child, tc) 133 { 134 atf_tc_set_md_var(tc, "descr", 135 "posix_spawn a child and get its return code"); 136 } 137 138 ATF_TC_BODY(t_spawn_child, tc) 139 { 140 char buf[PATH_MAX]; 141 char rv[2] = { '0', '\0' }; 142 char * const args0[] = { __UNCONST("h_spawn"), rv, NULL }; 143 int rets[] = { 0, 1, 7 }; 144 int status; 145 pid_t pid; 146 147 snprintf(buf, sizeof(buf), "%s/h_spawn", 148 atf_tc_get_config_var(tc, "srcdir")); 149 150 for (size_t i = 0; i < __arraycount(rets); i++) { 151 rv[0] = rets[i] + '0'; 152 RZ(posix_spawn(&pid, buf, NULL, NULL, args0, NULL)); 153 ATF_REQUIRE_MSG(pid > 0, "pid=%lld", (long long)pid); 154 RL(waitpid(pid, &status, 0)); 155 ATF_REQUIRE_MSG((WIFEXITED(status) && 156 WEXITSTATUS(status) == rets[i]), 157 "status=0x%x", status); 158 } 159 } 160 161 #define FILENAME "output" 162 163 enum chdirop { 164 OP_CHDIR = 1, 165 OP_FCHDIR = 2, 166 }; 167 168 static void 169 check_success(const char *dir, const char *file) 170 { 171 ssize_t bytes_read; 172 int fd; 173 size_t sizeof_file = (size_t)filesize(file); 174 size_t sizeof_str; 175 char *contents; 176 177 REQUIRE_LIBC(contents = malloc(sizeof_file), NULL); 178 179 RL(fd = open(file, O_RDONLY)); 180 181 /* 182 * file contains form feed i.e ASCII - 10 at the end. 183 * Therefore sizeof_file - 1 184 */ 185 sizeof_str = strlen(dir); 186 ATF_CHECK_MSG(sizeof_str == sizeof_file - 1, "%zu (%s) != %zu (%s)", 187 sizeof_str, dir, sizeof_file - 1, file); 188 189 bytes_read = read(fd, contents, sizeof_file - 1); 190 contents[sizeof_file - 1] = '\0'; 191 ATF_REQUIRE_MSG(strcmp(dir, contents) == 0, 192 "[%s] != [%s] Directories don't match", dir, contents); 193 194 RL(close(fd)); 195 196 RL(unlink(file)); 197 free(contents); 198 199 /* XXX not really required */ 200 ATF_REQUIRE_MSG((size_t)bytes_read == sizeof_str, 201 "bytes_read=%zu sizeof_str=%zu", bytes_read, sizeof_str); 202 } 203 204 static void 205 spawn_chdir(const char *dirpath, const char *filepath, enum chdirop operation, 206 int expected_error) 207 { 208 int error, fd = -1, status; 209 char * const args[2] = { __UNCONST("pwd"), NULL }; 210 pid_t pid; 211 posix_spawnattr_t attr, *attr_p; 212 posix_spawn_file_actions_t fa; 213 214 if (filepath) 215 empty_outfile(filepath); 216 217 RZ(posix_spawn_file_actions_init(&fa)); 218 219 switch (operation) { 220 case OP_CHDIR: 221 RZ(posix_spawn_file_actions_addchdir(&fa, dirpath)); 222 break; 223 224 case OP_FCHDIR: 225 RL(fd = open(dirpath, O_RDONLY)); 226 RZ(posix_spawn_file_actions_addfchdir(&fa, fd)); 227 break; 228 } 229 230 /* 231 * if POSIX_SPAWN_RETURNERROR is expected, then no need to open the 232 * file 233 */ 234 if (expected_error == 0) { 235 RZ(posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, 236 FILENAME, O_WRONLY, 0)); 237 attr_p = NULL; 238 } else { 239 RZ(posix_spawnattr_init(&attr)); 240 241 /* 242 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that 243 * will cause a "proper" return value from posix_spawn(2) 244 * instead of a (potential) success there and a 127 exit 245 * status from the child process (c.f. the non-diag variant 246 * of this test). 247 */ 248 RZ(posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR)); 249 attr_p = &attr; 250 } 251 252 error = posix_spawn(&pid, "/bin/pwd", &fa, attr_p, args, NULL); 253 ATF_REQUIRE_MSG(error == expected_error, "error=%d expected_error=%d", 254 error, expected_error); 255 256 /* wait for the child to finish only when no spawnattr */ 257 if (attr_p) { 258 RZ(posix_spawnattr_destroy(&attr)); 259 } else { 260 RL(waitpid(pid, &status, 0)); 261 ATF_REQUIRE_MSG((WIFEXITED(status) && 262 WEXITSTATUS(status) == EXIT_SUCCESS), 263 "[f]chdir failed"); 264 } 265 266 RZ(posix_spawn_file_actions_destroy(&fa)); 267 268 /* 269 * The file incase of fchdir(), 270 * should be closed before reopening in 'check_success' 271 */ 272 if (fd != -1) { 273 RL(close(fd)); 274 } 275 } 276 277 ATF_TC(t_spawn_chdir_abs); 278 ATF_TC_HEAD(t_spawn_chdir_abs, tc) 279 { 280 atf_tc_set_md_var(tc, "descr", 281 "Test posix_spawn_fa_addchdir for absolute path"); 282 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 283 } 284 ATF_TC_BODY(t_spawn_chdir_abs, tc) 285 { 286 char chdirpath[PATH_MAX], filepath[PATH_MAX]; 287 288 REQUIRE_LIBC(getcwd(chdirpath, sizeof(chdirpath)), NULL); 289 RL(chdir("/")); 290 if (snprintf(filepath, sizeof(filepath), "%s/%s", chdirpath, FILENAME) 291 >= (int)sizeof(filepath)) 292 atf_tc_fail("too deep: %s", chdirpath); 293 294 spawn_chdir(chdirpath, filepath, OP_CHDIR, 0); 295 296 /* finally cross check the output of "pwd" directory */ 297 check_success(chdirpath, filepath); 298 } 299 300 ATF_TC(t_spawn_chdir_rel); 301 302 ATF_TC_HEAD(t_spawn_chdir_rel, tc) 303 { 304 atf_tc_set_md_var(tc, "descr", 305 "Test posix_spawn_fa_addchdir for relative path"); 306 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 307 } 308 309 ATF_TC_BODY(t_spawn_chdir_rel, tc) 310 { 311 const char *relative_dir = "ch-dir"; 312 const char *testdir = getcwd(NULL, 0); 313 char *chdirwd, *filepath; 314 315 RL(mkdir(relative_dir, 0777)); 316 RL(asprintf(&chdirwd, "%s/%s", testdir, relative_dir)); 317 RL(asprintf(&filepath, "%s/%s", chdirwd, FILENAME)); 318 319 #ifdef DEBUG 320 printf("cwd: %s\n", testdir); 321 printf("chdirwd: %s\n", chdirwd); 322 printf("filepath: %s\n", filepath); 323 #endif 324 325 spawn_chdir(relative_dir, filepath, 1, 0); 326 327 /* finally cross check the directory */ 328 check_success(chdirwd, filepath); 329 free(chdirwd); 330 free(filepath); 331 } 332 333 ATF_TC(t_spawn_chdir_file); 334 ATF_TC_HEAD(t_spawn_chdir_file, tc) 335 { 336 atf_tc_set_md_var(tc, "descr", 337 "Test posix_spawn_fa_addchdir on plain file (not a directory)"); 338 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 339 } 340 ATF_TC_BODY(t_spawn_chdir_file, tc) 341 { 342 char cwd[PATH_MAX], filepath[PATH_MAX]; 343 344 REQUIRE_LIBC(getcwd(cwd, sizeof(cwd)), NULL); 345 if (snprintf(filepath, sizeof(filepath), "%s/%s", cwd, FILENAME) 346 >= (int)sizeof(filepath)) 347 atf_tc_fail("too deep: %s", cwd); 348 349 spawn_chdir(filepath, filepath, 1, ENOTDIR); 350 } 351 352 ATF_TC(t_spawn_chdir_invalid); 353 ATF_TC_HEAD(t_spawn_chdir_invalid, tc) 354 { 355 atf_tc_set_md_var(tc, "descr", 356 "Test posix_spawn_fa_addchdir for an invalid dir"); 357 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 358 } 359 ATF_TC_BODY(t_spawn_chdir_invalid, tc) 360 { 361 spawn_chdir("/not/a/valid/dir", NULL, 1, ENOENT); 362 } 363 364 ATF_TC(t_spawn_chdir_permissions); 365 ATF_TC_HEAD(t_spawn_chdir_permissions, tc) 366 { 367 atf_tc_set_md_var(tc, "descr", 368 "Test posix_spawn_file_actions_addchdir for prohibited directory"); 369 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 370 atf_tc_set_md_var(tc, "require.user", "unprivileged"); 371 } 372 ATF_TC_BODY(t_spawn_chdir_permissions, tc) 373 { 374 const char *restricted_dir = "prohibited"; 375 376 RL(mkdir(restricted_dir, 0055)); 377 378 spawn_chdir(restricted_dir, NULL, 1, EACCES); 379 } 380 381 ATF_TC(t_spawn_fchdir_abs); 382 ATF_TC_HEAD(t_spawn_fchdir_abs, tc) 383 { 384 atf_tc_set_md_var(tc, "descr", "Test posix_spawn_fa_fchdir"); 385 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 386 } 387 ATF_TC_BODY(t_spawn_fchdir_abs, tc) 388 { 389 char chdirpath[PATH_MAX], filepath[PATH_MAX]; 390 391 REQUIRE_LIBC(getcwd(chdirpath, sizeof(chdirpath)), NULL); 392 RL(chdir("/")); 393 if (snprintf(filepath, sizeof(filepath), "%s/%s", chdirpath, FILENAME) 394 >= (int)sizeof(filepath)) 395 atf_tc_fail("too deep: %s", chdirpath); 396 397 spawn_chdir(chdirpath, filepath, OP_FCHDIR, 0); 398 399 /* finally cross check the directory */ 400 check_success(chdirpath, filepath); 401 } 402 403 ATF_TC(t_spawn_fchdir_rel); 404 ATF_TC_HEAD(t_spawn_fchdir_rel, tc) 405 { 406 atf_tc_set_md_var(tc, "descr", 407 "Testing posix_spawn_file_actions_addfchdir on a relative " 408 "directory"); 409 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 410 } 411 ATF_TC_BODY(t_spawn_fchdir_rel, tc) 412 { 413 const char *relative_dir = "ch-dir"; 414 const char *testdir = getcwd(NULL, 0); 415 char *chdirwd, *filepath; 416 417 RL(mkdir(relative_dir, 0755)); 418 419 /* 420 * This is done in parts purposely. 421 * It enables the abs path of the relative dir 422 * to be passed to 'check_success()' for comparing 423 */ 424 RL(asprintf(&chdirwd, "%s/%s", testdir, relative_dir)); 425 RL(asprintf(&filepath, "%s/%s", chdirwd, FILENAME)); 426 427 spawn_chdir(relative_dir, filepath, 2, 0); 428 429 /* finally cross check the directory */ 430 check_success(chdirwd, filepath); 431 free(chdirwd); 432 free(filepath); 433 } 434 435 ATF_TC(t_spawn_fchdir_file); 436 ATF_TC_HEAD(t_spawn_fchdir_file, tc) 437 { 438 atf_tc_set_md_var(tc, "descr", 439 "Testing posix_spawn_file_actions_addfchdir on a " 440 "regular file (not a directory)"); 441 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 442 } 443 ATF_TC_BODY(t_spawn_fchdir_file, tc) 444 { 445 char cwd[PATH_MAX], filepath[PATH_MAX]; 446 int fd; 447 448 REQUIRE_LIBC(getcwd(cwd, sizeof(cwd)), NULL); 449 if (snprintf(filepath, sizeof(filepath), "%s/%s", cwd, FILENAME) 450 >= (int)sizeof(filepath)) 451 atf_tc_fail("too deep: %s", cwd); 452 RL(fd = open(FILENAME, O_WRONLY | O_CREAT | O_TRUNC, 0644)); 453 RL(close(fd)); 454 455 spawn_chdir(filepath, NULL, 2, ENOTDIR); 456 } 457 458 ATF_TC(t_spawn_fchdir_neg_fd); 459 ATF_TC_HEAD(t_spawn_fchdir_neg_fd, tc) 460 { 461 atf_tc_set_md_var(tc, "descr", 462 "Testing posix_spawn_file_actions_addfchdir on a negative file " 463 "descriptor"); 464 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 465 } 466 ATF_TC_BODY(t_spawn_fchdir_neg_fd, tc) 467 { 468 int error, fd; 469 posix_spawn_file_actions_t fa; 470 471 fd = -1; 472 473 RZ(posix_spawn_file_actions_init(&fa)); 474 475 error = posix_spawn_file_actions_addfchdir(&fa, fd); 476 ATF_REQUIRE_MSG(error == EBADF, "error=%d", error); 477 478 RZ(posix_spawn_file_actions_destroy(&fa)); 479 } 480 481 ATF_TC(t_spawn_fchdir_closed); 482 ATF_TC_HEAD(t_spawn_fchdir_closed, tc) 483 { 484 atf_tc_set_md_var(tc, "descr", 485 "Testing posix_spawn_file_actions_addfchdir for a closed fd"); 486 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 487 } 488 ATF_TC_BODY(t_spawn_fchdir_closed, tc) 489 { 490 int error, fd; 491 pid_t pid; 492 char * const args[2] = { __UNCONST("pwd"), NULL }; 493 posix_spawnattr_t attr; 494 posix_spawn_file_actions_t fa; 495 496 fd = 3; 497 RZ(posix_spawnattr_init(&attr)); 498 /* 499 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that 500 * will cause a "proper" return value from posix_spawn(2) 501 * instead of a (potential) success there and a 127 exit 502 * status from the child process (c.f. the non-diag variant 503 * of this test). 504 */ 505 RZ(posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR)); 506 RZ(posix_spawn_file_actions_init(&fa)); 507 RZ(posix_spawn_file_actions_addfchdir(&fa, fd)); 508 509 error = posix_spawn(&pid, "/bin/pwd", &fa, &attr, args, NULL); 510 ATF_REQUIRE_MSG(error == EBADF, "error=%d", error); 511 512 RZ(posix_spawn_file_actions_destroy(&fa)); 513 RZ(posix_spawnattr_destroy(&attr)); 514 } 515 516 ATF_TC(t_spawn_sig); 517 ATF_TC_HEAD(t_spawn_sig, tc) 518 { 519 atf_tc_set_md_var(tc, "descr", 520 "Checks that posix_spawn does not drop pending signals"); 521 } 522 ATF_TC_BODY(t_spawn_sig, tc) 523 { 524 const char *srcdir = atf_tc_get_config_var(tc, "srcdir"); 525 char h_execsig[PATH_MAX]; 526 time_t start; 527 528 snprintf(h_execsig, sizeof(h_execsig), "%s/../h_execsig", srcdir); 529 REQUIRE_LIBC(signal(SIGPIPE, SIG_IGN), SIG_ERR); 530 531 for (start = time(NULL); time(NULL) - start <= 10;) { 532 int fd[2]; 533 char *const argv[] = {h_execsig, NULL}; 534 posix_spawn_file_actions_t fa; 535 pid_t pid; 536 int status; 537 538 RL(pipe2(fd, O_CLOEXEC)); 539 RZ(posix_spawn_file_actions_init(&fa)); 540 RZ(posix_spawn_file_actions_adddup2(&fa, fd[0], STDIN_FILENO)); 541 RZ(posix_spawn(&pid, argv[0], &fa, NULL, argv, NULL)); 542 RL(close(fd[0])); 543 RL(kill(pid, SIGTERM)); 544 if (write(fd[1], (char[]){0}, 1) == -1 && errno != EPIPE) 545 atf_tc_fail("write failed: %s", strerror(errno)); 546 RL(waitpid(pid, &status, 0)); 547 ATF_REQUIRE_MSG(WIFSIGNALED(status), 548 "child exited with status 0x%x", status); 549 ATF_REQUIRE_EQ_MSG(WTERMSIG(status), SIGTERM, 550 "child exited on signal %d (%s)", 551 WTERMSIG(status), strsignal(WTERMSIG(status))); 552 RL(close(fd[1])); 553 RZ(posix_spawn_file_actions_destroy(&fa)); 554 } 555 } 556 557 ATF_TP_ADD_TCS(tp) 558 { 559 ATF_TP_ADD_TC(tp, t_spawn_ls); 560 ATF_TP_ADD_TC(tp, t_spawnp_ls); 561 ATF_TP_ADD_TC(tp, t_spawn_zero); 562 ATF_TP_ADD_TC(tp, t_spawn_missing); 563 ATF_TP_ADD_TC(tp, t_spawn_nonexec); 564 ATF_TP_ADD_TC(tp, t_spawn_child); 565 ATF_TP_ADD_TC(tp, t_spawn_chdir_abs); 566 ATF_TP_ADD_TC(tp, t_spawn_chdir_rel); 567 ATF_TP_ADD_TC(tp, t_spawn_chdir_file); 568 ATF_TP_ADD_TC(tp, t_spawn_chdir_invalid); 569 ATF_TP_ADD_TC(tp, t_spawn_chdir_permissions); 570 ATF_TP_ADD_TC(tp, t_spawn_fchdir_abs); 571 ATF_TP_ADD_TC(tp, t_spawn_fchdir_rel); 572 ATF_TP_ADD_TC(tp, t_spawn_fchdir_file); 573 ATF_TP_ADD_TC(tp, t_spawn_fchdir_neg_fd); 574 ATF_TP_ADD_TC(tp, t_spawn_fchdir_closed); 575 ATF_TP_ADD_TC(tp, t_spawn_sig); 576 577 return atf_no_error(); 578 } 579