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