1 /* $NetBSD: t_spawn.c,v 1.7 2021/11/22 15:09:16 christos 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@NetBSD.org> and 9 * Martin Husemann <martin@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 #include <sys/cdefs.h> 33 __RCSID("$NetBSD: t_spawn.c,v 1.7 2021/11/22 15:09:16 christos Exp $"); 34 35 #include <atf-c.h> 36 37 #include <sys/fcntl.h> 38 #include <sys/types.h> 39 #include <sys/wait.h> 40 #include <sys/stat.h> 41 42 #include <spawn.h> 43 #include <string.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <errno.h> 47 #include <stdarg.h> 48 #include <fcntl.h> 49 #include <unistd.h> 50 51 #include "fa_spawn_utils.h" 52 53 54 static void check_success(const char *, int, ...); 55 56 ATF_TC(t_spawn_ls); 57 58 ATF_TC_HEAD(t_spawn_ls, tc) 59 { 60 atf_tc_set_md_var(tc, "descr", 61 "Tests a simple posix_spawn executing /bin/ls"); 62 } 63 64 ATF_TC_BODY(t_spawn_ls, tc) 65 { 66 char * const args[] = { __UNCONST("ls"), __UNCONST("-la"), NULL }; 67 int err; 68 69 err = posix_spawn(NULL, "/bin/ls", NULL, NULL, args, NULL); 70 ATF_REQUIRE(err == 0); 71 } 72 73 ATF_TC(t_spawnp_ls); 74 75 ATF_TC_HEAD(t_spawnp_ls, tc) 76 { 77 atf_tc_set_md_var(tc, "descr", 78 "Tests a simple posix_spawnp executing ls via $PATH"); 79 } 80 81 ATF_TC_BODY(t_spawnp_ls, tc) 82 { 83 char * const args[] = { __UNCONST("ls"), __UNCONST("-la"), NULL }; 84 int err; 85 86 err = posix_spawnp(NULL, "ls", NULL, NULL, args, NULL); 87 ATF_REQUIRE(err == 0); 88 } 89 90 static void 91 spawn_error(const atf_tc_t *tc, const char *name, int error) 92 { 93 char buf[FILENAME_MAX]; 94 char * const args[] = { __UNCONST(name), NULL }; 95 int err; 96 97 snprintf(buf, sizeof buf, "%s/%s", 98 atf_tc_get_config_var(tc, "srcdir"), name); 99 err = posix_spawn(NULL, buf, NULL, NULL, args, NULL); 100 ATF_REQUIRE_MSG(err == error, "expected error %d, " 101 "got %d when spawning %s", error, err, buf); 102 } 103 104 ATF_TC(t_spawn_zero); 105 106 ATF_TC_HEAD(t_spawn_zero, tc) 107 { 108 atf_tc_set_md_var(tc, "descr", 109 "posix_spawn an invalid binary"); 110 } 111 112 ATF_TC_BODY(t_spawn_zero, tc) 113 { 114 spawn_error(tc, "h_zero", ENOEXEC); 115 } 116 117 ATF_TC(t_spawn_missing); 118 119 ATF_TC_HEAD(t_spawn_missing, tc) 120 { 121 atf_tc_set_md_var(tc, "descr", 122 "posix_spawn a non existant binary"); 123 } 124 125 ATF_TC_BODY(t_spawn_missing, tc) 126 { 127 spawn_error(tc, "h_nonexist", ENOENT); 128 } 129 130 ATF_TC(t_spawn_nonexec); 131 132 ATF_TC_HEAD(t_spawn_nonexec, tc) 133 { 134 atf_tc_set_md_var(tc, "descr", 135 "posix_spawn a script with non existing interpreter"); 136 } 137 138 ATF_TC_BODY(t_spawn_nonexec, tc) 139 { 140 spawn_error(tc, "h_nonexec", ENOENT); 141 } 142 143 ATF_TC(t_spawn_child); 144 145 ATF_TC_HEAD(t_spawn_child, tc) 146 { 147 atf_tc_set_md_var(tc, "descr", 148 "posix_spawn a child and get its return code"); 149 } 150 151 ATF_TC_BODY(t_spawn_child, tc) 152 { 153 char buf[FILENAME_MAX]; 154 char rv[2] = { '0', '\0' }; 155 char * const args0[] = { __UNCONST("h_spawn"), rv, NULL }; 156 int rets[] = { 0, 1, 7 }; 157 int err, status; 158 pid_t pid; 159 160 snprintf(buf, sizeof buf, "%s/h_spawn", 161 atf_tc_get_config_var(tc, "srcdir")); 162 163 for (size_t i = 0; i < __arraycount(rets); i++) { 164 rv[0] = rets[i] + '0'; 165 err = posix_spawn(&pid, buf, NULL, NULL, args0, NULL); 166 ATF_REQUIRE(err == 0); 167 ATF_REQUIRE(pid > 0); 168 waitpid(pid, &status, 0); 169 ATF_REQUIRE(WIFEXITED(status) && 170 WEXITSTATUS(status) == rets[i]); 171 } 172 } 173 174 #define CHDIRPATH "/tmp" 175 #define FILENAME "output" 176 #define FILEPATH "/tmp/output" 177 178 #define CHDIR 1 179 #define FCHDIR 2 180 181 static void 182 check_success(const char *file, int argc, ...) 183 { 184 va_list ap; 185 ssize_t bytesRead; 186 int fd; 187 size_t sizeOfFile = (size_t)filesize(file); 188 size_t sizeOfStr; 189 char *contents; 190 const char *dir; 191 192 contents = malloc(sizeOfFile); 193 ATF_REQUIRE(contents != NULL); 194 195 /* 196 * for now only 1 variadic argument expected 197 * only from t_spawn_[f]chdir_rel 198 */ 199 if (argc != 0) { 200 va_start(ap, argc); 201 dir = va_arg(ap, char *); 202 ATF_REQUIRE(dir != NULL); 203 va_end(ap); 204 } else 205 dir = CHDIRPATH; 206 207 fd = open(file, O_RDONLY); 208 ATF_REQUIRE_MSG(fd != -1, "Can't open `%s' (%s)", file, 209 strerror(errno)); 210 211 /* 212 * file contains form feed i.e ASCII - 10 at the end. 213 * Therefore sizeOfFile - 1 214 */ 215 sizeOfStr = strlen(dir); 216 ATF_CHECK_MSG(sizeOfStr == sizeOfFile - 1, "%zu (%s) != %zu (%s)", 217 sizeOfStr, dir, sizeOfFile - 1, file); 218 219 bytesRead = read(fd, contents, sizeOfFile - 1); 220 contents[sizeOfFile - 1] = '\0'; 221 ATF_REQUIRE_MSG(strcmp(dir, contents) == 0, 222 "[%s] != [%s] Directories dont match", dir, contents); 223 224 fd = close(fd); 225 ATF_REQUIRE(fd == 0); 226 227 unlink(file); 228 free(contents); 229 230 /* XXX not really required */ 231 ATF_REQUIRE((size_t)bytesRead == sizeOfStr); 232 } 233 234 static void 235 spawn_chdir(const char *dirpath, const char *filepath, int operation, 236 int expected_error) 237 { 238 int error, fd=-1, status; 239 char * const args[2] = { __UNCONST("pwd"), NULL }; 240 pid_t pid; 241 posix_spawnattr_t attr, *attr_p; 242 posix_spawn_file_actions_t fa; 243 244 if (filepath) 245 empty_outfile(filepath); 246 247 error = posix_spawn_file_actions_init(&fa); 248 ATF_REQUIRE(error == 0); 249 250 switch(operation) { 251 case CHDIR: 252 error = posix_spawn_file_actions_addchdir(&fa, dirpath); 253 break; 254 255 case FCHDIR: 256 fd = open(dirpath, O_RDONLY); 257 ATF_REQUIRE(fd != -1); 258 259 error = posix_spawn_file_actions_addfchdir(&fa, fd); 260 break; 261 } 262 ATF_REQUIRE(error == 0); 263 264 /* 265 * if POSIX_SPAWN_RETURNERROR is expected, then no need to open the 266 * file 267 */ 268 if (expected_error == 0) { 269 error = posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, 270 FILENAME, O_WRONLY, 0); 271 ATF_REQUIRE(error == 0); 272 attr_p = NULL; 273 } else { 274 error = posix_spawnattr_init(&attr); 275 ATF_REQUIRE(error == 0); 276 277 /* 278 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that 279 * will cause a "proper" return value from posix_spawn(2) 280 * instead of a (potential) success there and a 127 exit 281 * status from the child process (c.f. the non-diag variant 282 * of this test). 283 */ 284 error = posix_spawnattr_setflags(&attr, 285 POSIX_SPAWN_RETURNERROR); 286 ATF_REQUIRE(error == 0); 287 attr_p = &attr; 288 } 289 290 error = posix_spawn(&pid, "/bin/pwd", &fa, attr_p, args, NULL); 291 ATF_REQUIRE(error == expected_error); 292 293 /* wait for the child to finish only when no spawnattr */ 294 if (attr_p) { 295 posix_spawnattr_destroy(&attr); 296 } else { 297 waitpid(pid, &status, 0); 298 ATF_REQUIRE_MSG(WIFEXITED(status) && 299 WEXITSTATUS(status) == EXIT_SUCCESS, 300 "%s", "[f]chdir failed"); 301 } 302 303 posix_spawn_file_actions_destroy(&fa); 304 305 /* 306 * The file incase of fchdir(), 307 * should be closed before reopening in 'check_success' 308 */ 309 if (fd != -1) { 310 error = close(fd); 311 ATF_REQUIRE(error == 0); 312 } 313 } 314 315 ATF_TC(t_spawn_chdir_abs); 316 317 ATF_TC_HEAD(t_spawn_chdir_abs, tc) 318 { 319 atf_tc_set_md_var(tc, "descr", 320 "Test posix_spawn_fa_addchdir for absolute path"); 321 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 322 } 323 324 ATF_TC_BODY(t_spawn_chdir_abs, tc) 325 { 326 spawn_chdir(CHDIRPATH, FILEPATH, 1, 0); 327 328 /* finally cross check the output of "pwd" directory */ 329 check_success(FILEPATH, 0); 330 } 331 332 ATF_TC(t_spawn_chdir_rel); 333 334 ATF_TC_HEAD(t_spawn_chdir_rel, tc) 335 { 336 atf_tc_set_md_var(tc, "descr", 337 "Test posix_spawn_fa_addchdir for relative path"); 338 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 339 } 340 341 342 ATF_TC_BODY(t_spawn_chdir_rel, tc) 343 { 344 int error; 345 const char *relative_dir = "ch-dir"; 346 const char *testdir = getcwd(NULL, 0); 347 char *chdirwd, *filepath; 348 349 /* cleanup previous */ 350 error = asprintf(&filepath, "%s/%s", relative_dir, FILENAME); 351 ATF_CHECK(error != -1); 352 unlink(filepath); 353 free(filepath); 354 355 rmdir(relative_dir); 356 error = mkdir(relative_dir, 0777); 357 ATF_REQUIRE_MSG(error == 0, "mkdir `%s' (%s)", relative_dir, 358 strerror(errno)); 359 360 error = asprintf(&chdirwd, "%s/%s", testdir, relative_dir); 361 ATF_CHECK(error != -1); 362 363 error = asprintf(&filepath, "%s/%s", chdirwd, FILENAME); 364 ATF_CHECK(error != -1); 365 366 #ifdef DEBUG 367 printf("cwd: %s\n", testdir); 368 printf("chdirwd: %s\n", chdirwd); 369 printf("filepath: %s\n", filepath); 370 #endif 371 372 spawn_chdir(relative_dir, filepath, 1, 0); 373 374 /* finally cross check the directory */ 375 check_success(filepath, 1, chdirwd); 376 free(chdirwd); 377 free(filepath); 378 } 379 380 ATF_TC(t_spawn_chdir_file); 381 382 ATF_TC_HEAD(t_spawn_chdir_file, tc) 383 { 384 atf_tc_set_md_var(tc, "descr", 385 "Test posix_spawn_fa_addchdir on plain file (not a directory)"); 386 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 387 } 388 389 ATF_TC_BODY(t_spawn_chdir_file, tc) 390 { 391 spawn_chdir(FILEPATH, FILEPATH, 1, ENOTDIR); 392 393 unlink(FILEPATH); 394 } 395 396 ATF_TC(t_spawn_chdir_invalid); 397 398 ATF_TC_HEAD(t_spawn_chdir_invalid, tc) 399 { 400 atf_tc_set_md_var(tc, "descr", 401 "Test posix_spawn_fa_addchdir for an invalid dir"); 402 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 403 } 404 405 ATF_TC_BODY(t_spawn_chdir_invalid, tc) 406 { 407 spawn_chdir("/not/a/valid/dir", NULL, 1, ENOENT); 408 409 } 410 411 ATF_TC(t_spawn_chdir_permissions); 412 413 ATF_TC_HEAD(t_spawn_chdir_permissions, tc) 414 { 415 atf_tc_set_md_var(tc, "descr", 416 "Test posix_spawn_file_actions_addchdir for prohibited directory"); 417 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 418 atf_tc_set_md_var(tc, "require.user", "unprivileged"); 419 } 420 421 ATF_TC_BODY(t_spawn_chdir_permissions, tc) 422 { 423 int error; 424 const char *restrRelDir = "prohibited"; 425 426 rmdir(restrRelDir); 427 error = mkdir(restrRelDir, 0055); 428 ATF_REQUIRE(error == 0); 429 430 spawn_chdir(restrRelDir, NULL, 1, EACCES); 431 } 432 433 434 ATF_TC(t_spawn_fchdir_abs); 435 436 ATF_TC_HEAD(t_spawn_fchdir_abs, tc) 437 { 438 atf_tc_set_md_var(tc, "descr", "Test posix_spawn_fa_fchdir"); 439 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 440 } 441 442 ATF_TC_BODY(t_spawn_fchdir_abs, tc) 443 { 444 spawn_chdir(CHDIRPATH, FILEPATH, 2, 0); 445 446 /* finally cross check the directory */ 447 check_success(FILEPATH, 0); 448 } 449 450 ATF_TC(t_spawn_fchdir_rel); 451 452 ATF_TC_HEAD(t_spawn_fchdir_rel, tc) 453 { 454 atf_tc_set_md_var(tc, "descr", 455 "Testing posix_spawn_file_actions_addfchdir on a relative " 456 "directory"); 457 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 458 } 459 460 ATF_TC_BODY(t_spawn_fchdir_rel, tc) 461 { 462 int error; 463 const char *relative_dir = "ch-dir"; 464 const char *testdir = getcwd(NULL, 0); 465 char *chdirwd, *filepath; 466 467 rmdir(relative_dir); 468 error = mkdir(relative_dir, 0755); 469 ATF_REQUIRE(error == 0); 470 471 /* 472 * This is done in parts purposely. 473 * It enbales the abs path of the relative dir 474 * to be passed to 'check_success()' for comparing 475 */ 476 error = asprintf(&chdirwd, "%s/%s", testdir, relative_dir); 477 ATF_CHECK(error != -1); 478 479 error = asprintf(&filepath, "%s/%s", chdirwd, FILENAME); 480 ATF_CHECK(error != -1); 481 482 spawn_chdir(relative_dir, filepath, 2, 0); 483 484 /* finally cross check the directory */ 485 check_success(filepath, 1, chdirwd); 486 free(chdirwd); 487 free(filepath); 488 } 489 490 ATF_TC(t_spawn_fchdir_file); 491 492 ATF_TC_HEAD(t_spawn_fchdir_file, tc) 493 { 494 atf_tc_set_md_var(tc, "descr", 495 "Testing posix_spawn_file_actions_addfchdir on a " 496 "regular file (not a directory)"); 497 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 498 } 499 500 ATF_TC_BODY(t_spawn_fchdir_file, tc) 501 { 502 int error, fd; 503 504 fd = open(FILEPATH, O_WRONLY | O_CREAT | O_TRUNC, 0644); 505 ATF_REQUIRE_MSG(fd != -1, "Can't open `%s' (%s)", FILEPATH, 506 strerror(errno)); 507 508 error = close(fd); 509 ATF_REQUIRE(error == 0); 510 511 spawn_chdir(FILEPATH, NULL, 2, ENOTDIR); 512 513 unlink(FILEPATH); 514 515 } 516 517 ATF_TC(t_spawn_fchdir_neg_fd); 518 519 ATF_TC_HEAD(t_spawn_fchdir_neg_fd, tc) 520 { 521 atf_tc_set_md_var(tc, "descr", 522 "Testing posix_spawn_file_actions_addfchdir on a negative file " 523 "descriptor"); 524 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 525 } 526 527 ATF_TC_BODY(t_spawn_fchdir_neg_fd, tc) 528 { 529 int error, fd; 530 posix_spawn_file_actions_t fa; 531 532 fd = -1; 533 534 error = posix_spawn_file_actions_init(&fa); 535 ATF_REQUIRE(error == 0); 536 537 error = posix_spawn_file_actions_addfchdir(&fa, fd); 538 ATF_REQUIRE(error == EBADF); 539 540 posix_spawn_file_actions_destroy(&fa); 541 } 542 543 ATF_TC(t_spawn_fchdir_closed); 544 545 ATF_TC_HEAD(t_spawn_fchdir_closed, tc) 546 { 547 atf_tc_set_md_var(tc, "descr", 548 "Testing posix_spawn_file_actions_addfchdir for a closed fd"); 549 atf_tc_set_md_var(tc, "require.progs", "/bin/pwd"); 550 } 551 552 ATF_TC_BODY(t_spawn_fchdir_closed, tc) 553 { 554 int error, fd; 555 pid_t pid; 556 char * const args[2] = { __UNCONST("pwd"), NULL }; 557 posix_spawnattr_t attr; 558 posix_spawn_file_actions_t fa; 559 560 fd = 3; 561 error = posix_spawnattr_init(&attr); 562 ATF_CHECK(error == 0); 563 /* 564 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that 565 * will cause a "proper" return value from posix_spawn(2) 566 * instead of a (potential) success there and a 127 exit 567 * status from the child process (c.f. the non-diag variant 568 * of this test). 569 */ 570 error = posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR); 571 ATF_REQUIRE(error == 0); 572 573 error = posix_spawn_file_actions_init(&fa); 574 ATF_REQUIRE(error == 0); 575 576 error = posix_spawn_file_actions_addfchdir(&fa, fd); 577 ATF_REQUIRE(error == 0); 578 579 error = posix_spawn(&pid, "/bin/pwd", &fa, &attr, args, NULL); 580 ATF_REQUIRE(error == EBADF); 581 582 posix_spawn_file_actions_destroy(&fa); 583 posix_spawnattr_destroy(&attr); 584 } 585 586 #undef CHDIR 587 #undef FCHDIR 588 #undef CHDIRPATH 589 #undef FILENAME 590 #undef FILEPATH 591 592 ATF_TP_ADD_TCS(tp) 593 { 594 ATF_TP_ADD_TC(tp, t_spawn_ls); 595 ATF_TP_ADD_TC(tp, t_spawnp_ls); 596 ATF_TP_ADD_TC(tp, t_spawn_zero); 597 ATF_TP_ADD_TC(tp, t_spawn_missing); 598 ATF_TP_ADD_TC(tp, t_spawn_nonexec); 599 ATF_TP_ADD_TC(tp, t_spawn_child); 600 ATF_TP_ADD_TC(tp, t_spawn_chdir_abs); 601 ATF_TP_ADD_TC(tp, t_spawn_chdir_rel); 602 ATF_TP_ADD_TC(tp, t_spawn_chdir_file); 603 ATF_TP_ADD_TC(tp, t_spawn_chdir_invalid); 604 ATF_TP_ADD_TC(tp, t_spawn_chdir_permissions); 605 ATF_TP_ADD_TC(tp, t_spawn_fchdir_abs); 606 ATF_TP_ADD_TC(tp, t_spawn_fchdir_rel); 607 ATF_TP_ADD_TC(tp, t_spawn_fchdir_file); 608 ATF_TP_ADD_TC(tp, t_spawn_fchdir_neg_fd); 609 ATF_TP_ADD_TC(tp, t_spawn_fchdir_closed); 610 611 return atf_no_error(); 612 } 613