1 /* $NetBSD: t_fileactions.c,v 1.11 2025/07/12 16:25:29 gutteridge Exp $ */ 2 3 /*- 4 * Copyright (c) 2012 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_fileactions.c,v 1.11 2025/07/12 16:25:29 gutteridge Exp $"); 35 36 #include <sys/stat.h> 37 #include <sys/wait.h> 38 39 #include <atf-c.h> 40 #include <errno.h> 41 #include <fcntl.h> 42 #include <spawn.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <unistd.h> 47 48 #include "fa_spawn_utils.h" 49 #include "h_macros.h" 50 51 #define TESTFILE "./the_input_data" 52 #define CHECKFILE "./the_output_data" 53 #define TESTCONTENT "marry has a little lamb" 54 55 static void 56 make_testfile(const char *restrict file) 57 { 58 FILE *f; 59 ssize_t written; 60 61 REQUIRE_LIBC(f = fopen(file, "w"), NULL); 62 RL(written = fwrite(TESTCONTENT, 1, strlen(TESTCONTENT), f)); 63 REQUIRE_LIBC(fclose(f), EOF); 64 ATF_REQUIRE((size_t)written == strlen(TESTCONTENT)); 65 } 66 67 ATF_TC(t_spawn_openmode); 68 ATF_TC_HEAD(t_spawn_openmode, tc) 69 { 70 atf_tc_set_md_var(tc, "descr", 71 "Test the proper handling of 'mode' for 'open' fileactions"); 72 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 73 } 74 ATF_TC_BODY(t_spawn_openmode, tc) 75 { 76 int status; 77 pid_t pid; 78 size_t insize, outsize; 79 char * const args[2] = { __UNCONST("cat"), NULL }; 80 posix_spawn_file_actions_t fa; 81 82 /* 83 * try a "cat < testfile > checkfile" 84 */ 85 make_testfile(TESTFILE); 86 87 RZ(posix_spawn_file_actions_init(&fa)); 88 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdin), 89 TESTFILE, O_RDONLY, 0)); 90 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdout), 91 CHECKFILE, O_WRONLY|O_CREAT, 0600)); 92 RZ(posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL)); 93 RZ(posix_spawn_file_actions_destroy(&fa)); 94 95 /* ok, wait for the child to finish */ 96 RL(waitpid(pid, &status, 0)); 97 ATF_REQUIRE_MSG((WIFEXITED(status) && 98 WEXITSTATUS(status) == EXIT_SUCCESS), 99 "status=0x%x", status); 100 101 /* now check that input and output have the same size */ 102 insize = filesize(TESTFILE); 103 outsize = filesize(CHECKFILE); 104 ATF_CHECK_MSG(insize == strlen(TESTCONTENT), 105 "insize=%zu strlen(TESTCONTENT)=%zu", insize, strlen(TESTCONTENT)); 106 ATF_CHECK_MSG(insize == outsize, 107 "insize=%zu outsize=%zu", insize, outsize); 108 109 /* 110 * try a "cat < testfile >> checkfile" 111 */ 112 make_testfile(TESTFILE); 113 make_testfile(CHECKFILE); 114 115 RZ(posix_spawn_file_actions_init(&fa)); 116 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdin), 117 TESTFILE, O_RDONLY, 0)); 118 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdout), 119 CHECKFILE, O_WRONLY|O_APPEND, 0)); 120 RZ(posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL)); 121 RZ(posix_spawn_file_actions_destroy(&fa)); 122 123 /* ok, wait for the child to finish */ 124 RL(waitpid(pid, &status, 0)); 125 ATF_REQUIRE_MSG((WIFEXITED(status) && 126 WEXITSTATUS(status) == EXIT_SUCCESS), 127 "status=0x%x", status); 128 129 /* now check that output is twice as long as input */ 130 insize = filesize(TESTFILE); 131 outsize = filesize(CHECKFILE); 132 ATF_CHECK_MSG(insize == strlen(TESTCONTENT), 133 "insize=%zu strlen(TESTCONTENT)=%zu", insize, strlen(TESTCONTENT)); 134 ATF_CHECK_MSG(insize*2 == outsize, 135 "insize*2=%zu outsize=%zu", insize*2, outsize); 136 137 /* 138 * try a "cat < testfile > checkfile" with input and output swapped 139 */ 140 make_testfile(TESTFILE); 141 empty_outfile(CHECKFILE); 142 143 RZ(posix_spawn_file_actions_init(&fa)); 144 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdout), 145 TESTFILE, O_RDONLY, 0)); 146 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdin), 147 CHECKFILE, O_WRONLY, 0)); 148 RZ(posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL)); 149 RZ(posix_spawn_file_actions_destroy(&fa)); 150 151 /* ok, wait for the child to finish */ 152 RL(waitpid(pid, &status, 0)); 153 ATF_REQUIRE_MSG((WIFEXITED(status) && 154 WEXITSTATUS(status) == EXIT_FAILURE), 155 "status=0x%x", status); 156 157 /* now check that input and output are still the same size */ 158 insize = filesize(TESTFILE); 159 outsize = filesize(CHECKFILE); 160 ATF_CHECK_MSG(insize == strlen(TESTCONTENT), 161 "insize=%zu strlen(TESTCONTENT)=%zu", insize, strlen(TESTCONTENT)); 162 ATF_CHECK_MSG(outsize == 0, 163 "outsize=%zu", outsize); 164 } 165 166 ATF_TC(t_spawn_reopen); 167 ATF_TC_HEAD(t_spawn_reopen, tc) 168 { 169 atf_tc_set_md_var(tc, "descr", 170 "an open filehandle can be replaced by a 'open' fileaction"); 171 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 172 } 173 ATF_TC_BODY(t_spawn_reopen, tc) 174 { 175 int status; 176 pid_t pid; 177 char * const args[2] = { __UNCONST("cat"), NULL }; 178 posix_spawn_file_actions_t fa; 179 180 /* 181 * make sure stdin is open in the parent 182 */ 183 REQUIRE_LIBC(freopen("/dev/zero", "r", stdin), NULL); 184 /* 185 * now request an open for this fd again in the child 186 */ 187 RZ(posix_spawn_file_actions_init(&fa)); 188 RZ(posix_spawn_file_actions_addopen(&fa, fileno(stdin), 189 "/dev/null", O_RDONLY, 0)); 190 RZ(posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL)); 191 RZ(posix_spawn_file_actions_destroy(&fa)); 192 193 RL(waitpid(pid, &status, 0)); 194 ATF_REQUIRE_MSG((WIFEXITED(status) && 195 WEXITSTATUS(status) == EXIT_SUCCESS), 196 "status=0x%x", status); 197 } 198 199 ATF_TC(t_spawn_open_nonexistent); 200 ATF_TC_HEAD(t_spawn_open_nonexistent, tc) 201 { 202 atf_tc_set_md_var(tc, "descr", 203 "posix_spawn fails when a file to open does not exist"); 204 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 205 } 206 ATF_TC_BODY(t_spawn_open_nonexistent, tc) 207 { 208 int err, status; 209 pid_t pid; 210 char * const args[2] = { __UNCONST("cat"), NULL }; 211 posix_spawn_file_actions_t fa; 212 213 RZ(posix_spawn_file_actions_init(&fa)); 214 RZ(posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, 215 "./non/ex/ist/ent", O_RDONLY, 0)); 216 err = posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL); 217 if (err == 0) { 218 /* 219 * The child has been created - it should fail and 220 * return exit code 127 221 */ 222 waitpid(pid, &status, 0); 223 ATF_REQUIRE(WIFEXITED(status) && WEXITSTATUS(status) == 127); 224 } else { 225 /* 226 * The error has been noticed early enough, no child has 227 * been run 228 */ 229 ATF_REQUIRE_MSG(err == ENOENT, "err=%d (%s)", 230 err, strerror(err)); 231 } 232 RZ(posix_spawn_file_actions_destroy(&fa)); 233 } 234 235 ATF_TC(t_spawn_open_nonexistent_diag); 236 ATF_TC_HEAD(t_spawn_open_nonexistent_diag, tc) 237 { 238 atf_tc_set_md_var(tc, "descr", 239 "posix_spawn fails when a file to open does not exist " 240 "and delivers proper diagnostic"); 241 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 242 } 243 ATF_TC_BODY(t_spawn_open_nonexistent_diag, tc) 244 { 245 int err; 246 pid_t pid; 247 char * const args[2] = { __UNCONST("cat"), NULL }; 248 posix_spawnattr_t attr; 249 posix_spawn_file_actions_t fa; 250 251 RZ(posix_spawnattr_init(&attr)); 252 /* 253 * POSIX_SPAWN_RETURNERROR is a NetBSD specific flag that 254 * will cause a "proper" return value from posix_spawn(2) 255 * instead of a (potential) success there and a 127 exit 256 * status from the child process (c.f. the non-diag variant 257 * of this test). 258 */ 259 RZ(posix_spawnattr_setflags(&attr, POSIX_SPAWN_RETURNERROR)); 260 RZ(posix_spawn_file_actions_init(&fa)); 261 RZ(posix_spawn_file_actions_addopen(&fa, STDIN_FILENO, 262 "./non/ex/ist/ent", O_RDONLY, 0)); 263 err = posix_spawn(&pid, "/bin/cat", &fa, &attr, args, NULL); 264 ATF_REQUIRE_MSG(err == ENOENT, "err=%d (%s)", err, strerror(err)); 265 RZ(posix_spawn_file_actions_destroy(&fa)); 266 RZ(posix_spawnattr_destroy(&attr)); 267 } 268 269 ATF_TC(t_spawn_fileactions); 270 ATF_TC_HEAD(t_spawn_fileactions, tc) 271 { 272 atf_tc_set_md_var(tc, "descr", 273 "Tests various complex fileactions"); 274 } 275 ATF_TC_BODY(t_spawn_fileactions, tc) 276 { 277 int fd1, fd2, fd3, status; 278 pid_t pid; 279 char * const args[2] = { __UNCONST("h_fileactions"), NULL }; 280 char helper[FILENAME_MAX]; 281 posix_spawn_file_actions_t fa; 282 283 RZ(posix_spawn_file_actions_init(&fa)); 284 285 RL(closefrom(fileno(stderr) + 1)); 286 287 RL(fd1 = open("/dev/null", O_RDONLY)); 288 ATF_REQUIRE(fd1 == 3); 289 290 RL(fd2 = open("/dev/null", O_WRONLY, O_CLOEXEC)); 291 ATF_REQUIRE(fd2 == 4); 292 293 RL(fd3 = open("/dev/null", O_WRONLY)); 294 ATF_REQUIRE(fd3 == 5); 295 296 RZ(posix_spawn_file_actions_addclose(&fa, fd1)); 297 RZ(posix_spawn_file_actions_addopen(&fa, 6, "/dev/null", O_RDWR, 0)); 298 RZ(posix_spawn_file_actions_adddup2(&fa, 1, 7)); 299 300 snprintf(helper, sizeof helper, "%s/h_fileactions", 301 atf_tc_get_config_var(tc, "srcdir")); 302 RZ(posix_spawn(&pid, helper, &fa, NULL, args, NULL)); 303 RZ(posix_spawn_file_actions_destroy(&fa)); 304 305 RL(waitpid(pid, &status, 0)); 306 ATF_REQUIRE_MSG((WIFEXITED(status) && 307 WEXITSTATUS(status) == EXIT_SUCCESS), 308 "status=0x%x", status); 309 } 310 311 ATF_TC(t_spawn_close_already_closed); 312 ATF_TC_HEAD(t_spawn_close_already_closed, tc) 313 { 314 atf_tc_set_md_var(tc, "descr", 315 "file actions closing closed descriptors are allowed (PR 59523)"); 316 atf_tc_set_md_var(tc, "require.progs", "/bin/ls"); 317 } 318 319 ATF_TC_BODY(t_spawn_close_already_closed, tc) 320 { 321 int status, fd; 322 pid_t pid; 323 char * const args[2] = { __UNCONST("ls"), NULL }; 324 posix_spawn_file_actions_t fa; 325 326 /* get a free file descriptor number */ 327 fd = open("/dev/null", O_RDONLY); 328 ATF_REQUIRE(fd >= 0); 329 close(fd); 330 331 RZ(posix_spawn_file_actions_init(&fa)); 332 // known closed fd 333 RZ(posix_spawn_file_actions_addclose(&fa, fd)); 334 // a random fd we know nothing about (cross fingers!) 335 RZ(posix_spawn_file_actions_addclose(&fa, fd+1)); 336 // high fd probably not ever been allocated, likely to trigger 337 // a fd_getfile() failure in the kernel, which is another 338 // path that originally caused the fallout in PR 59523 339 RZ(posix_spawn_file_actions_addclose(&fa, 560)); 340 // redirect output to /dev/null to not garble atf test results 341 RZ(posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, "/dev/null", 342 O_WRONLY, 0)); 343 RZ(posix_spawn(&pid, "/bin/ls", &fa, NULL, args, NULL)); 344 RZ(posix_spawn_file_actions_destroy(&fa)); 345 346 /* ok, wait for the child to finish */ 347 RL(waitpid(pid, &status, 0)); 348 ATF_REQUIRE_MSG((WIFEXITED(status) && 349 WEXITSTATUS(status) == EXIT_SUCCESS), 350 "status=0x%x", status); 351 } 352 353 ATF_TC(t_spawn_close_already_closed_wait); 354 ATF_TC_HEAD(t_spawn_close_already_closed_wait, tc) 355 { 356 atf_tc_set_md_var(tc, "descr", 357 "file actions closing closed descriptors are allowed, " 358 "with parent process waiting (PR 59523)"); 359 atf_tc_set_md_var(tc, "require.progs", "/bin/ls"); 360 } 361 362 ATF_TC_BODY(t_spawn_close_already_closed_wait, tc) 363 { 364 int status, fd; 365 pid_t pid; 366 char * const args[2] = { __UNCONST("ls"), NULL }; 367 posix_spawn_file_actions_t fa; 368 posix_spawnattr_t attr; 369 370 /* get a free file descriptor number */ 371 fd = open("/dev/null", O_RDONLY); 372 ATF_REQUIRE(fd >= 0); 373 close(fd); 374 RZ(posix_spawn_file_actions_init(&fa)); 375 // known closed fd 376 RZ(posix_spawn_file_actions_addclose(&fa, fd)); 377 // a random fd we know nothing about (cross fingers!) 378 RZ(posix_spawn_file_actions_addclose(&fa, fd+1)); 379 // high fd probably not ever been allocated, likely to trigger 380 // a fd_getfile() failure in the kernel, which is another 381 // path that originally caused the fallout in PR 59523 382 RZ(posix_spawn_file_actions_addclose(&fa, 560)); 383 // redirect output to /dev/null to not garble atf test results 384 RZ(posix_spawn_file_actions_addopen(&fa, STDOUT_FILENO, "/dev/null", 385 O_WRONLY, 0)); 386 387 RZ(posix_spawnattr_init(&attr)); 388 RZ(posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETPGROUP)); 389 390 RZ(posix_spawn(&pid, "/bin/ls", &fa, &attr, args, NULL)); 391 RZ(posix_spawn_file_actions_destroy(&fa)); 392 393 /* ok, wait for the child to finish */ 394 RL(waitpid(pid, &status, 0)); 395 ATF_REQUIRE_MSG((WIFEXITED(status) && 396 WEXITSTATUS(status) == EXIT_SUCCESS), 397 "status=0x%x", status); 398 } 399 400 ATF_TC(t_spawn_empty_fileactions); 401 ATF_TC_HEAD(t_spawn_empty_fileactions, tc) 402 { 403 atf_tc_set_md_var(tc, "descr", 404 "posix_spawn with empty fileactions (PR kern/46038)"); 405 atf_tc_set_md_var(tc, "require.progs", "/bin/cat"); 406 } 407 ATF_TC_BODY(t_spawn_empty_fileactions, tc) 408 { 409 int status; 410 pid_t pid; 411 char * const args[2] = { __UNCONST("cat"), NULL }; 412 posix_spawn_file_actions_t fa; 413 size_t insize, outsize; 414 415 /* 416 * try a "cat < testfile > checkfile", but set up stdin/stdout 417 * already in the parent and pass empty file actions to the child. 418 */ 419 make_testfile(TESTFILE); 420 421 REQUIRE_LIBC(freopen(TESTFILE, "r", stdin), NULL); 422 REQUIRE_LIBC(freopen(CHECKFILE, "w", stdout), NULL); 423 424 RZ(posix_spawn_file_actions_init(&fa)); 425 RZ(posix_spawn(&pid, "/bin/cat", &fa, NULL, args, NULL)); 426 RZ(posix_spawn_file_actions_destroy(&fa)); 427 428 /* ok, wait for the child to finish */ 429 RL(waitpid(pid, &status, 0)); 430 ATF_REQUIRE_MSG((WIFEXITED(status) && 431 WEXITSTATUS(status) == EXIT_SUCCESS), 432 "status=0x%x", status); 433 434 /* now check that input and output have the same size */ 435 insize = filesize(TESTFILE); 436 outsize = filesize(CHECKFILE); 437 ATF_CHECK_MSG(insize == strlen(TESTCONTENT), 438 "insize=%zu strlen(TESTCONTENT)=%zu", insize, strlen(TESTCONTENT)); 439 ATF_CHECK_MSG(insize == outsize, 440 "insize=%zu outsize=%zu", insize, outsize); 441 } 442 443 ATF_TP_ADD_TCS(tp) 444 { 445 446 ATF_TP_ADD_TC(tp, t_spawn_fileactions); 447 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent); 448 ATF_TP_ADD_TC(tp, t_spawn_open_nonexistent_diag); 449 ATF_TP_ADD_TC(tp, t_spawn_reopen); 450 ATF_TP_ADD_TC(tp, t_spawn_openmode); 451 ATF_TP_ADD_TC(tp, t_spawn_empty_fileactions); 452 ATF_TP_ADD_TC(tp, t_spawn_close_already_closed); 453 ATF_TP_ADD_TC(tp, t_spawn_close_already_closed_wait); 454 455 return atf_no_error(); 456 } 457