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