1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2026 ConnectWise 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/types.h> 29 #include <sys/mman.h> 30 #include <sys/procdesc.h> 31 #include <sys/user.h> 32 #include <sys/wait.h> 33 34 #include <atf-c.h> 35 #include <stdio.h> 36 #include <string.h> 37 #include <unistd.h> 38 39 static void 40 basic_usage_tail(int pd, pid_t pid) 41 { 42 pid_t pd_pid, waited_pid; 43 int r, status; 44 45 ATF_REQUIRE_MSG(pd >= 0, "rfork did not return a process descriptor"); 46 r = pdgetpid(pd, &pd_pid); 47 ATF_CHECK_EQ_MSG(r, 0, "pdgetpid failed: %s", strerror(errno)); 48 ATF_CHECK_EQ(pd_pid, pid); 49 50 /* We should be able to collect the child's status */ 51 waited_pid = waitpid(pid, &status, WEXITED); 52 ATF_CHECK_EQ(waited_pid, pid); 53 54 /* But after closing the process descriptor, we won't */ 55 close(pd); 56 waited_pid = waitpid(pid, &status, WEXITED | WNOHANG); 57 ATF_CHECK_EQ(-1, waited_pid); 58 ATF_CHECK_EQ(ECHILD, errno); 59 } 60 61 static void 62 basic_usage(int rfflags) 63 { 64 int pd = -1; 65 pid_t pid; 66 67 pid = pdrfork(&pd, 0, rfflags); 68 ATF_REQUIRE_MSG(pid >= 0, "rfork failed with %s", strerror(errno)); 69 if (pid == 0) { 70 /* In child */ 71 _exit(0); 72 } 73 basic_usage_tail(pd, pid); 74 } 75 76 /* pdrfork does not return a process descriptor to the child */ 77 ATF_TC_WITHOUT_HEAD(child_gets_no_pidfd); 78 ATF_TC_BODY(child_gets_no_pidfd, tc) 79 { 80 int pd = -1; 81 pid_t pid, pd_pid, waited_pid; 82 int r, status; 83 84 pid = pdrfork(&pd, 0, RFPROC | RFPROCDESC); 85 ATF_REQUIRE_MSG(pid >= 0, "rfork failed with %s", strerror(errno)); 86 if (pid == 0) { 87 /* 88 * In child. We can't do very much here before we exec, so 89 * just use our exit status to report success. 90 */ 91 _exit(pd == -1); 92 } 93 ATF_REQUIRE_MSG(pd >= 0, "rfork did not return a process descriptor"); 94 r = pdgetpid(pd, &pd_pid); 95 ATF_CHECK_EQ_MSG(r, 0, "pdgetpid failed: %s", strerror(errno)); 96 97 waited_pid = waitpid(pid, &status, WEXITED); 98 ATF_CHECK_EQ(waited_pid, pid); 99 ATF_REQUIRE(WIFEXITED(status) && (WEXITSTATUS(status) == true)); 100 101 close(pd); 102 } 103 104 /* If the pidfd argument is invalid, the error should be handled gracefully */ 105 ATF_TC_WITHOUT_HEAD(efault); 106 ATF_TC_BODY(efault, tc) 107 { 108 void *unmapped; 109 pid_t my_pid; 110 111 unmapped = mmap(NULL, PAGE_SIZE, PROT_NONE, MAP_GUARD, -1, 0); 112 ATF_REQUIRE(unmapped != MAP_FAILED); 113 my_pid = getpid(); 114 ATF_REQUIRE_ERRNO(EFAULT, pdrfork(unmapped, 0, RFPROC | 115 RFPROCDESC) < 0); 116 117 /* 118 * EFAULT only means that the copyout of the procdesc failed. 119 * The runaway child was created anyway. Prevent 120 * double-destruction of the atf stuff. 121 */ 122 if (my_pid != getpid()) 123 _exit(0); 124 } 125 126 /* Invalid combinations of flags should return EINVAL */ 127 ATF_TC_WITHOUT_HEAD(einval); 128 ATF_TC_BODY(einval, tc) 129 { 130 int pd = -1; 131 132 ATF_CHECK_ERRNO(EINVAL, pdrfork(&pd, -1, RFSPAWN) < 0); 133 ATF_CHECK_ERRNO(EINVAL, pdrfork(&pd, 0, -1) < 0); 134 ATF_CHECK_ERRNO(EINVAL, pdrfork(&pd, 0, RFSPAWN | RFNOWAIT) < 0); 135 ATF_CHECK_ERRNO(EINVAL, pdrfork(&pd, 0, RFPROC | RFFDG| RFCFDG) < 0); 136 ATF_CHECK_ERRNO(EINVAL, pdrfork(&pd, 0, RFPROCDESC) < 0); 137 ATF_CHECK_ERRNO(EINVAL, pdrfork(&pd, 0, RFPROC) < 0); 138 ATF_CHECK_ERRNO(EINVAL, pdrfork(&pd, 0, 0) < 0); 139 } 140 141 /* basic usage with RFPROCDESC */ 142 ATF_TC_WITHOUT_HEAD(rfprocdesc); 143 ATF_TC_BODY(rfprocdesc, tc) 144 { 145 basic_usage(RFPROC | RFPROCDESC); 146 } 147 148 static int 149 rfspawn_fn(void *arg) 150 { 151 _exit(0); 152 return (0); 153 } 154 155 /* basic usage with RFSPAWN */ 156 ATF_TC_WITHOUT_HEAD(rfspawn); 157 ATF_TC_BODY(rfspawn, tc) 158 { 159 char *stack = NULL; 160 int pd = -1; 161 pid_t pid; 162 163 #if defined(__i386__) || defined(__amd64__) 164 #define STACK_SZ (PAGE_SIZE * 10) 165 stack = mmap(NULL, STACK_SZ, PROT_READ | PROT_WRITE, MAP_ANON, 166 -1, 0); 167 ATF_REQUIRE(stack != MAP_FAILED); 168 stack += STACK_SZ; 169 #endif 170 pid = pdrfork_thread(&pd, 0, RFSPAWN, stack, rfspawn_fn, NULL); 171 basic_usage_tail(pd, pid); 172 } 173 174 ATF_TP_ADD_TCS(tp) 175 { 176 ATF_TP_ADD_TC(tp, child_gets_no_pidfd); 177 ATF_TP_ADD_TC(tp, efault); 178 ATF_TP_ADD_TC(tp, einval); 179 ATF_TP_ADD_TC(tp, rfprocdesc); 180 ATF_TP_ADD_TC(tp, rfspawn); 181 182 return (atf_no_error()); 183 } 184