1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2024 Oxide Computer Company 14 */ 15 16 /* 17 * This program is designed to operate alongside pr_target.c. It verifies that 18 * we can run a program with libproc and inject various system calls via the 19 * agent LWP. It has a contract with pr_target.c to inject operations at the 20 * function 'pr_target_hook()'. pr_target.c will then verify that those 21 * operations are visible. 22 */ 23 24 #include <stdlib.h> 25 #include <err.h> 26 #include <libproc.h> 27 #include <errno.h> 28 #include <sys/wait.h> 29 #include <stdbool.h> 30 #include <sys/debug.h> 31 #include <string.h> 32 33 #include "pr_target.h" 34 35 static uint_t pr_timeout = 5 * 1000; /* 5s in ms */ 36 37 /* 38 * Verify a few things about the state before we inject a bunch of things. 39 */ 40 static bool 41 pr_check_pre(struct ps_prochandle *P) 42 { 43 bool ret = true; 44 int fd; 45 struct stat st, targ; 46 47 if ((fd = open("/dev/null", O_RDONLY)) < 0) { 48 err(EXIT_FAILURE, "TEST FAILED: failed to open /dev/null"); 49 } 50 51 if (fstat(fd, &st) != 0) { 52 err(EXIT_FAILURE, "TEST FAILED: failed to fstat /dev/null"); 53 } 54 VERIFY0(close(fd)); 55 56 if (pr_fstat(P, PRT_NULL_FD, &targ) != 0) { 57 warn("TEST FAILED: pr_fstat() on target failed"); 58 ret = false; 59 } else { 60 (void) printf("TEST PASSED: successfully injected fstat()\n"); 61 } 62 63 if (st.st_ino != targ.st_ino || st.st_dev != targ.st_dev || 64 st.st_rdev != targ.st_rdev) { 65 warnx("TEST FAILED: pr_fstat() data does not match local " 66 "/dev/null fstat() data"); 67 ret = false; 68 } else { 69 (void) printf("TEST PASSED: pr_fstat() data on /dev/null " 70 "matched local data\n"); 71 } 72 73 if (pr_fstat(P, PRT_ZERO_FD, &targ) == 0) { 74 warnx("TEST FAILED: Injected pr_fstat() on fd %u worked " 75 "but expected it not to exist!", PRT_ZERO_FD); 76 ret = false; 77 } else if (errno != EBADF) { 78 int e = errno; 79 warnx("TEST FAILED: expected pr_fstat on fd %u to return %s " 80 "but found %s", PRT_ZERO_FD, strerrorname_np(EBADF), 81 strerrorname_np(e)); 82 ret = false; 83 } else { 84 (void) printf("TEST PASSED: pr_fstat() on bad fd returned " 85 "EBADF\n"); 86 } 87 88 return (ret); 89 } 90 91 static bool 92 pr_inject(struct ps_prochandle *P) 93 { 94 bool ret = true; 95 uintptr_t farg0, farg1; 96 int fd; 97 98 fd = pr_open(P, "/dev/zero", PRT_ZERO_OFLAG, 0); 99 if (fd < 0) { 100 warnx("TEST FAILED: failed to open /dev/zero in the target"); 101 ret = false; 102 } else if (fd != PRT_ZERO_FD) { 103 warnx("TEST FAILED: pr_open() didn't return FD %u as expected " 104 "but fd %u", PRT_ZERO_FD, fd); 105 ret = false; 106 } else { 107 (void) printf("TEST PASSED: open() successfully injected\n"); 108 } 109 110 farg0 = PRT_NULL_FD; 111 fd = pr_fcntl(P, PRT_NULL_FD, F_DUPFD, (void *)farg0, NULL); 112 if (fd < 0) { 113 warn("TEST FAILED: failed to inject F_DUPFD fcntl"); 114 ret = false; 115 } else if (fd != PRT_DUP_FD) { 116 warnx("TEST FAILED: F_DUPFD didn't return FD %u as expected " 117 "but fd %u", PRT_ZERO_FD, fd); 118 ret = false; 119 } else { 120 (void) printf("TEST PASSED: F_DUPFD successfully injected\n"); 121 } 122 123 farg0 = PRT_CLOFORK_FD; 124 fd = pr_fcntl(P, PRT_NULL_FD, F_DUP2FD_CLOFORK, (void *)farg0, NULL); 125 if (fd < 0) { 126 warn("TEST FAILED: failed to inject F_DUP2FD_CLOFORK fcntl"); 127 ret = false; 128 } else if (fd != PRT_CLOFORK_FD) { 129 warnx("TEST FAILED: F_DUP2FD_CLOFORK didn't return FD %u as " 130 "expected but fd %u", PRT_ZERO_FD, fd); 131 ret = false; 132 } else { 133 (void) printf("TEST PASSED: F_DUP2FD_CLOFORK successfully " 134 "injected\n"); 135 } 136 137 farg0 = PRT_DUP3_FD; 138 farg1 = PRT_DUP3_GETFD; 139 fd = pr_fcntl(P, PRT_ZERO_FD, F_DUP3FD, (void *)farg0, (void *)farg1); 140 if (fd < 0) { 141 warn("TEST FAILED: failed to inject F_DUP3FD fcntl"); 142 ret = false; 143 } else if (fd != PRT_DUP3_FD) { 144 warnx("TEST FAILED: F_DUP3FD didn't return FD %u as expected " 145 "but fd %u", PRT_ZERO_FD, fd); 146 ret = false; 147 } else { 148 (void) printf("TEST PASSED: F_DUP3FD successfully injected\n"); 149 } 150 151 if (pr_close(P, PRT_CLOSE_FD) != 0) { 152 warn("TEST FAILED: failed to inject close()"); 153 ret = false; 154 } else { 155 (void) printf("TEST PASSED: close() successfully injected\n"); 156 } 157 158 return (ret); 159 } 160 161 int 162 main(int argc, char *argv[]) 163 { 164 int ret = EXIT_SUCCESS, perr, wstat; 165 struct ps_prochandle *P; 166 GElf_Sym sym; 167 ulong_t bkpt; 168 pid_t pid; 169 170 if (argc != 2) { 171 errx(EXIT_FAILURE, "missing required program to inject " 172 "against"); 173 } 174 175 P = Pcreate(argv[1], &argv[1], &perr, NULL, 0); 176 if (P == NULL) { 177 errx(EXIT_FAILURE, "failed to create %s: %s (0x%x)", argv[1], 178 Pcreate_error(perr), perr); 179 } 180 181 (void) Punsetflags(P, PR_RLC); 182 if (Psetflags(P, PR_KLC | PR_BPTADJ) != 0) { 183 int e = errno; 184 Prelease(P, PRELEASE_KILL); 185 errc(EXIT_FAILURE, e, "failed to set PR_KLC | PR_BPTADJ flags"); 186 } 187 188 if (Pxlookup_by_name(P, LM_ID_BASE, PR_OBJ_EXEC, "pr_target_hook", &sym, 189 NULL) != 0) { 190 err(EXIT_FAILURE, "failed to find pr_target_hook symbol"); 191 } 192 193 pid = Ppsinfo(P)->pr_pid; 194 195 if (Pfault(P, FLTBPT, 1) != 0) { 196 errx(EXIT_FAILURE, "failed to set the FLTBPT disposition"); 197 } 198 199 if (Psetbkpt(P, sym.st_value, &bkpt) != 0) { 200 err(EXIT_FAILURE, "failed to set breakpoint on pr_target_hook " 201 "(0x%" PRIx64 ")", sym.st_value); 202 } 203 204 if (Psetrun(P, 0, 0) != 0) { 205 err(EXIT_FAILURE, "failed to resume running our target"); 206 } 207 208 if (Pwait(P, pr_timeout) != 0) { 209 err(EXIT_FAILURE, "%s did not hit our expected breakpoint", 210 argv[1]); 211 } 212 213 /* 214 * This is where we actually perform all of our injections and 215 * validations. By hitting the breakpoint the expected fd should exist. 216 */ 217 if (!pr_check_pre(P)) { 218 ret = EXIT_FAILURE; 219 } 220 221 if (!pr_inject(P)) { 222 ret = EXIT_FAILURE; 223 } 224 225 if (Pdelbkpt(P, sym.st_value, bkpt) != 0) { 226 err(EXIT_FAILURE, "failed to delete breakpoint"); 227 } 228 229 if (Psetrun(P, 0, PRCFAULT) != 0) { 230 err(EXIT_FAILURE, "failed to resume running our target"); 231 } 232 233 if (waitpid(pid, &wstat, 0) != pid) { 234 err(EXIT_FAILURE, "failed to get our %s's (%" _PRIdID "), " 235 "wait info", argv[1], pid); 236 } 237 238 if (WIFEXITED(wstat) == 0) { 239 errx(EXIT_FAILURE, "%s didn't actually exit!", 240 argv[1]); 241 } 242 243 if (WEXITSTATUS(wstat) != 0) { 244 errx(EXIT_FAILURE, "%s failed with 0x%x", argv[1], 245 WEXITSTATUS(wstat)); 246 } else { 247 (void) printf("TEST PASSED: target process self-verification " 248 "passed\n"); 249 } 250 251 if (ret == EXIT_SUCCESS) { 252 (void) printf("All tests passed successfully\n"); 253 } 254 255 return (ret); 256 } 257