1 /* $OpenBSD: manager.c,v 1.9 2024/06/03 08:02:22 anton Exp $ */
2 /*
3 * Copyright (c) 2015 Sebastien Marie <semarie@openbsd.org>
4 *
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
8 *
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16 */
17
18 #include <sys/syslimits.h>
19 #include <sys/types.h>
20 #include <sys/wait.h>
21
22 #include <ctype.h>
23 #include <err.h>
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <regex.h>
27 #include <signal.h>
28 #include <stdarg.h>
29 #include <stdlib.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include <unistd.h>
33
34 #include "manager.h"
35 #include "pty.h"
36
37 extern char *__progname;
38
39 static const char *
coredump_name()40 coredump_name()
41 {
42 static char coredump[PATH_MAX] = "";
43
44 if (*coredump)
45 return (coredump);
46
47 if (strlcpy(coredump, __progname, sizeof(coredump)) >= sizeof(coredump))
48 errx(1, "coredump: strlcpy");
49
50 if (strlcat(coredump, ".core", sizeof(coredump)) >= sizeof(coredump))
51 errx(1, "coredump: strlcat");
52
53 return (coredump);
54 }
55
56
57 static int
check_coredump()58 check_coredump()
59 {
60 const char *coredump = coredump_name();
61 int fd;
62
63 if ((fd = open(coredump, O_RDONLY)) == -1) {
64 if (errno == ENOENT)
65 return (1); /* coredump not found */
66 else
67 return (-1); /* error */
68 }
69
70 (void)close(fd);
71 return (0); /* coredump found */
72 }
73
74
75 static int
clear_coredump(int * ret,const char * test_name)76 clear_coredump(int *ret, const char *test_name)
77 {
78 int saved_errno = errno;
79 int u;
80
81 if (((u = unlink(coredump_name())) != 0) && (errno != ENOENT)) {
82 warn("test(%s): clear_coredump", test_name);
83 *ret = EXIT_FAILURE;
84 return (-1);
85 }
86 errno = saved_errno;
87
88 return (0);
89 }
90
91
92 static int
grab_syscall(pid_t pid,char * output)93 grab_syscall(pid_t pid, char *output)
94 {
95 int ret = -1;
96 char *pattern;
97 regex_t regex;
98 regmatch_t matches[2];
99 int error;
100 const char *errstr;
101
102 /* build searched string */
103 error = asprintf(&pattern,
104 "%s\\[%d\\]: pledge \"[a-z]+\", syscall ([0-9]+)",
105 __progname, pid);
106 if (error <= 0) {
107 warn("asprintf pattern");
108 return (-1);
109 }
110 error = regcomp(®ex, pattern, REG_EXTENDED);
111 if (error) {
112 warnx("regcomp pattern=%s error=%d", pattern, error);
113 free(pattern);
114 return (-1);
115 }
116 if (regex.re_nsub != 1) {
117 warnx("regcomp pattern=%s nsub=%zu", pattern, regex.re_nsub);
118 goto out;
119 }
120
121 /* search the string */
122 error = regexec(®ex, output, 2, matches, 0);
123 if (error == REG_NOMATCH) {
124 ret = 0;
125 goto out;
126 }
127 if (error) {
128 warnx("regexec pattern=%s output=%s error=%d",
129 pattern, output, error);
130 ret = -1;
131 goto out;
132 }
133
134 /* convert it */
135 output[matches[1].rm_eo] = '\0';
136 ret = strtonum(&output[matches[1].rm_so], 0, 255, &errstr);
137 if (errstr) {
138 warnx("strtonum: number=%s error=%s",
139 &output[matches[1].rm_so], errstr);
140 ret = -1;
141 goto out;
142 }
143
144 out:
145 free(pattern);
146 regfree(®ex);
147 return (ret);
148 }
149
150 /* mainly stolen from src/bin/cat/cat.c */
151 static int
drainfd(int rfd,int wfd)152 drainfd(int rfd, int wfd)
153 {
154 char buf[1024];
155 ssize_t nr, nw, off;
156
157 while ((nr = read(rfd, buf, sizeof(buf))) != -1 && nr != 0)
158 for (off = 0; nr; nr -= nw, off += nw)
159 if ((nw = write(wfd, buf + off, (size_t)nr)) == 0 ||
160 nw == -1)
161 return (-1);
162 if (nr < 0)
163 return (-1);
164
165 return (0);
166 }
167
168 void
_start_test(int * ret,const char * test_name,const char * request,void (* test_func)(void))169 _start_test(int *ret, const char *test_name, const char *request,
170 void (*test_func)(void))
171 {
172 struct pty pty = {0};
173 int fildes[2];
174 pid_t pid;
175 int status;
176
177 /* early print testname */
178 printf("test(%s): pledge=", test_name);
179 if (request) {
180 printf("(\"%s\",", request);
181 printf("NULL)");
182 } else
183 printf("skip");
184
185 /* unlink previous coredump (if exists) */
186 if (clear_coredump(ret, test_name) == -1)
187 return;
188
189 /* flush outputs (for STDOUT_FILENO manipulation) */
190 if (fflush(NULL) != 0) {
191 warn("test(%s) fflush", test_name);
192 *ret = EXIT_FAILURE;
193 return;
194 }
195
196 /* make pipe to grab output */
197 if (pipe(fildes) != 0) {
198 warn("test(%s) pipe", test_name);
199 *ret = EXIT_FAILURE;
200 return;
201 }
202
203 if (pty_open(&pty)) {
204 *ret = EXIT_FAILURE;
205 return;
206 }
207
208 /* fork and launch the test */
209 switch (pid = fork()) {
210 case -1:
211 (void)close(fildes[0]);
212 (void)close(fildes[1]);
213
214 warn("test(%s) fork", test_name);
215 *ret = EXIT_FAILURE;
216 return;
217
218 case 0:
219 /* output to pipe */
220 (void)close(fildes[0]);
221 while (dup2(fildes[1], STDOUT_FILENO) == -1)
222 if (errno != EINTR)
223 err(errno, "dup2");
224
225 if (pty_detach(&pty)) {
226 *ret = EXIT_FAILURE;
227 return;
228 }
229
230 /* create a new session (for kill) */
231 setsid();
232
233 if (pty_attach(&pty)) {
234 *ret = EXIT_FAILURE;
235 return;
236 }
237
238 /* set pledge policy */
239 if (request && pledge(request, NULL) != 0)
240 err(errno, "pledge");
241
242 /* reset errno and launch test */
243 errno = 0;
244 test_func();
245
246 if (errno != 0)
247 _exit(errno);
248
249 _exit(EXIT_SUCCESS);
250 /* NOTREACHED */
251 }
252
253 if (pty_drain(&pty)) {
254 *ret = EXIT_FAILURE;
255 return;
256 }
257
258 /* copy pipe to output */
259 (void)close(fildes[1]);
260 if (drainfd(fildes[0], STDOUT_FILENO) != 0) {
261 warn("test(%s): drainfd", test_name);
262 *ret = EXIT_FAILURE;
263 return;
264 }
265 if (close(fildes[0]) != 0) {
266 warn("test(%s): close", test_name);
267 *ret = EXIT_FAILURE;
268 return;
269 }
270
271 /* wait for test to terminate */
272 while (waitpid(pid, &status, 0) < 0) {
273 if (errno == EAGAIN)
274 continue;
275 warn("test(%s): waitpid", test_name);
276 *ret = EXIT_FAILURE;
277 return;
278 }
279
280 /* show status and details */
281 printf(" status=%d", status);
282
283 if (WIFCONTINUED(status))
284 printf(" continued");
285
286 if (WIFEXITED(status)) {
287 int e = WEXITSTATUS(status);
288 printf(" exit=%d", e);
289 if (e > 0 && e <= ELAST)
290 printf(" (errno: \"%s\")", strerror(e));
291 }
292
293 if (WIFSIGNALED(status)) {
294 int signal = WTERMSIG(status);
295 printf(" signal=%d", signal);
296
297 /* check if core file is really here ? */
298 if (WCOREDUMP(status)) {
299 int coredump = check_coredump();
300
301 switch(coredump) {
302 case -1: /* error */
303 warn("test(%s): check_coredump", test_name);
304 *ret = EXIT_FAILURE;
305 return;
306
307 case 0: /* found */
308 printf(" coredump=present");
309 break;
310
311 case 1: /* not found */
312 printf(" coredump=absent");
313 break;
314
315 default:
316 warnx("test(%s): unknown coredump code %d",
317 test_name, coredump);
318 *ret = EXIT_FAILURE;
319 return;
320 }
321
322 }
323
324 /* grab pledged syscall from dmesg */
325 if (signal == SIGKILL || signal == SIGABRT) {
326 int syscall = grab_syscall(pid, pty_buffer(&pty));
327 switch (syscall) {
328 case -1: /* error */
329 warn("test(%s): grab_syscall pid=%d", test_name,
330 pid);
331 *ret = EXIT_FAILURE;
332 return;
333
334 case 0: /* not found */
335 printf(" pledged_syscall=not_found");
336 break;
337
338 default:
339 printf(" pledged_syscall=%d", syscall);
340 }
341 }
342 }
343
344 if (WIFSTOPPED(status))
345 printf(" stop=%d", WSTOPSIG(status));
346
347 pty_close(&pty);
348
349 printf("\n");
350 }
351