xref: /illumos-gate/usr/src/test/libproc-tests/tests/syscall/pr_inject.c (revision 1a2d662a91cee3bf82f41cd47c7ae6f3825d9db2)
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