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(&regex, 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(&regex, 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(&regex);
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