xref: /illumos-gate/usr/src/test/libproc-tests/tests/agent/agent_destroy.c (revision a7cee4e9766ebda975dd156d1f10a70f51c242f0)
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 2026 Oxide Computer Company
14  */
15 
16 /*
17  * This program operates alongside agent_target.c and verifies that libproc
18  * can destroy the agent lwp even when an injected system call has to be
19  * abandoned part-way through. The private _libproc_test_fail_copyinargs
20  * flag makes Psyscall() treat the construction of the injected call's
21  * stack frame as failed, which leaves the agent stopped on entry to that
22  * call. libproc must abort the pending call and terminate the agent,
23  * leaving the target undamaged and controllable. It must do so even
24  * though the same failure applies to the _lwp_exit() injection which is
25  * used to terminate the agent.
26  *
27  * Historically this went wrong in two ways. Pdestroy_agent() did not abort
28  * a system call latched at an entry stop, so the agent was resumed with the
29  * abandoned call still pending and ran off into arbitrary code, crashing or
30  * silently corrupting the target. With that fixed, a failure to construct
31  * the _lwp_exit() frame abandoned the teardown instead, orphaning the agent
32  * and leaving the target stopped forever, since run-on-last-close is
33  * skipped for a process which still has an agent lwp. On a libproc with
34  * either defect this test crashes the target, or hangs and is timed out by
35  * the test runner.
36  */
37 
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <stdbool.h>
41 #include <limits.h>
42 #include <err.h>
43 #include <errno.h>
44 #include <libproc.h>
45 #include <sys/wait.h>
46 #include <sys/resource.h>
47 #include <sys/stat.h>
48 
49 extern int _libproc_test_fail_copyinargs;
50 
51 static uint_t at_timeout = 5 * 1000; /* 5s in ms */
52 
53 /*
54  * Verify that the target has no agent lwp, according to both libproc's
55  * cached status and the kernel.
56  */
57 static bool
58 agent_absent(struct ps_prochandle *P)
59 {
60 	char path[PATH_MAX];
61 	struct stat st;
62 
63 	if (Pstatus(P)->pr_agentid != 0) {
64 		warnx("TEST FAILED: pstatus reports an agent lwp (id %d)",
65 		    (int)Pstatus(P)->pr_agentid);
66 		return (false);
67 	}
68 
69 	(void) snprintf(path, sizeof (path), "/proc/%d/lwp/agent",
70 	    (int)Pstatus(P)->pr_pid);
71 	if (stat(path, &st) == 0) {
72 		warnx("TEST FAILED: %s exists: the agent lwp was orphaned",
73 		    path);
74 		return (false);
75 	} else if (errno != ENOENT) {
76 		warn("TEST FAILED: unexpected error from stat(%s)", path);
77 		return (false);
78 	}
79 
80 	return (true);
81 }
82 
83 static bool
84 agent_checks(struct ps_prochandle *P)
85 {
86 	bool ret = true;
87 	struct rlimit rl;
88 
89 	/*
90 	 * Baseline: a normal injection, and with it a full agent
91 	 * create/destroy cycle, must work.
92 	 */
93 	if (pr_getrlimit(P, RLIMIT_NOFILE, &rl) != 0) {
94 		warn("TEST FAILED: baseline pr_getrlimit() injection failed");
95 		ret = false;
96 	} else {
97 		(void) printf("TEST PASSED: baseline injection succeeded\n");
98 	}
99 
100 	if (!agent_absent(P))
101 		ret = false;
102 
103 	/*
104 	 * Force the write which constructs the injected call's stack frame
105 	 * to fail. The injection itself must fail cleanly, and libproc must
106 	 * still manage to abort the pending system call and destroy the
107 	 * agent.
108 	 */
109 	_libproc_test_fail_copyinargs = 1;
110 
111 	if (pr_getrlimit(P, RLIMIT_NOFILE, &rl) == 0) {
112 		warnx("TEST FAILED: injection unexpectedly succeeded with "
113 		    "_libproc_test_fail_copyinargs set");
114 		ret = false;
115 	} else {
116 		(void) printf("TEST PASSED: injection failed with "
117 		    "_libproc_test_fail_copyinargs set\n");
118 	}
119 
120 	_libproc_test_fail_copyinargs = 0;
121 
122 	if (!agent_absent(P)) {
123 		ret = false;
124 	} else {
125 		(void) printf("TEST PASSED: agent destroyed after failed "
126 		    "injection\n");
127 	}
128 
129 	/*
130 	 * The target must still be intact and controllable.
131 	 */
132 	if (pr_getrlimit(P, RLIMIT_NOFILE, &rl) != 0) {
133 		warn("TEST FAILED: pr_getrlimit() injection failed following "
134 		    "recovery");
135 		ret = false;
136 	} else {
137 		(void) printf("TEST PASSED: injection succeeded following "
138 		    "recovery\n");
139 	}
140 
141 	if (!agent_absent(P))
142 		ret = false;
143 
144 	return (ret);
145 }
146 
147 int
148 main(int argc, char *argv[])
149 {
150 	int ret = EXIT_SUCCESS, perr, wstat;
151 	struct ps_prochandle *P;
152 	GElf_Sym sym;
153 	ulong_t bkpt;
154 	pid_t pid;
155 
156 	if (argc != 2) {
157 		errx(EXIT_FAILURE, "missing required program to inject "
158 		    "against");
159 	}
160 
161 	P = Pcreate(argv[1], &argv[1], &perr, NULL, 0);
162 	if (P == NULL) {
163 		errx(EXIT_FAILURE, "failed to create %s: %s (0x%x)", argv[1],
164 		    Pcreate_error(perr), perr);
165 	}
166 
167 	(void) Punsetflags(P, PR_RLC);
168 	if (Psetflags(P, PR_KLC | PR_BPTADJ) != 0) {
169 		int e = errno;
170 		Prelease(P, PRELEASE_KILL);
171 		errc(EXIT_FAILURE, e, "failed to set PR_KLC | PR_BPTADJ flags");
172 	}
173 
174 	if (Pxlookup_by_name(P, LM_ID_BASE, PR_OBJ_EXEC, "agent_target_hook",
175 	    &sym, NULL) != 0) {
176 		err(EXIT_FAILURE, "failed to find agent_target_hook symbol");
177 	}
178 
179 	pid = Ppsinfo(P)->pr_pid;
180 
181 	if (Pfault(P, FLTBPT, 1) != 0)
182 		errx(EXIT_FAILURE, "failed to set the FLTBPT disposition");
183 
184 	if (Psetbkpt(P, sym.st_value, &bkpt) != 0) {
185 		err(EXIT_FAILURE, "failed to set breakpoint on "
186 		    "agent_target_hook (0x%" PRIx64 ")", sym.st_value);
187 	}
188 
189 	if (Psetrun(P, 0, 0) != 0)
190 		err(EXIT_FAILURE, "failed to resume running our target");
191 
192 	if (Pwait(P, at_timeout) != 0) {
193 		err(EXIT_FAILURE, "%s did not hit our expected breakpoint",
194 		    argv[1]);
195 	}
196 
197 	/*
198 	 * The target is stopped at the breakpoint. Run the injections.
199 	 */
200 	if (!agent_checks(P))
201 		ret = EXIT_FAILURE;
202 
203 	if (Pdelbkpt(P, sym.st_value, bkpt) != 0)
204 		err(EXIT_FAILURE, "failed to delete breakpoint");
205 
206 	if (Psetrun(P, 0, PRCFAULT) != 0)
207 		err(EXIT_FAILURE, "failed to resume running our target");
208 
209 	if (waitpid(pid, &wstat, 0) != pid) {
210 		err(EXIT_FAILURE, "failed to get our %s's (%" _PRIdID "), "
211 		    "wait info", argv[1], pid);
212 	}
213 
214 	if (WIFEXITED(wstat) == 0)
215 		errx(EXIT_FAILURE, "%s didn't actually exit!", argv[1]);
216 
217 	if (WEXITSTATUS(wstat) != 0) {
218 		errx(EXIT_FAILURE, "%s failed with 0x%x", argv[1],
219 		    WEXITSTATUS(wstat));
220 	} else {
221 		(void) printf("TEST PASSED: target ran to completion "
222 		    "undamaged\n");
223 	}
224 
225 	if (ret == EXIT_SUCCESS)
226 		(void) printf("All tests passed successfully\n");
227 
228 	return (ret);
229 }
230