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
pr_check_pre(struct ps_prochandle * P)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
pr_inject(struct ps_prochandle * P)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
main(int argc,char * argv[])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