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