1 /*	$OpenBSD: manager.c,v 1.7 2017/12/15 14:45:51 bluhm 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 
36 extern char *__progname;
37 
38 static const char *
39 coredump_name()
40 {
41 	static char coredump[PATH_MAX] = "";
42 
43 	if (*coredump)
44 		return (coredump);
45 
46 	if (strlcpy(coredump, __progname, sizeof(coredump)) >= sizeof(coredump))
47 		errx(1, "coredump: strlcpy");
48 
49 	if (strlcat(coredump, ".core", sizeof(coredump)) >= sizeof(coredump))
50 		errx(1, "coredump: strlcat");
51 
52 	return (coredump);
53 }
54 
55 
56 static int
57 check_coredump()
58 {
59 	const char *coredump = coredump_name();
60 	int fd;
61 
62 	if ((fd = open(coredump, O_RDONLY)) == -1) {
63 		if (errno == ENOENT)
64 			return (1); /* coredump not found */
65 		else
66 			return (-1); /* error */
67 	}
68 
69 	(void)close(fd);
70 	return (0); /* coredump found */
71 }
72 
73 
74 static int
75 clear_coredump(int *ret, const char *test_name)
76 {
77 	int saved_errno = errno;
78 	int u;
79 
80 	if (((u = unlink(coredump_name())) != 0) && (errno != ENOENT)) {
81 		warn("test(%s): clear_coredump", test_name);
82 		*ret = EXIT_FAILURE;
83 		return (-1);
84 	}
85 	errno = saved_errno;
86 
87 	return (0);
88 }
89 
90 
91 static int
92 grab_syscall(pid_t pid)
93 {
94 	int		 ret = -1;
95 	char		*pattern;
96 	regex_t		 regex;
97 	regmatch_t	 matches[2];
98 	FILE		*fd;
99 	char		 line[1024];
100 	int		 error;
101 	const char	*errstr;
102 
103 	/* build searched string */
104 	error = asprintf(&pattern,
105 	    "^%s\\[%d\\]: pledge \"[a-z]+\", syscall ([0-9]+)\n?$",
106 	    __progname, pid);
107 	if (error <= 0) {
108 		warn("asprintf pattern");
109 		return (-1);
110 	}
111 	error = regcomp(&regex, pattern, REG_EXTENDED);
112 	if (error) {
113 		warnx("regcomp pattern=%s error=%d", pattern, error);
114 		free(pattern);
115 		return (-1);
116 	}
117 	if (regex.re_nsub != 1) {
118 		warnx("regcomp pattern=%s nsub=%zu", pattern, regex.re_nsub);
119 		goto out;
120 	}
121 
122 	/* call dmesg */
123 	if ((fd = popen("/sbin/dmesg", "r")) == NULL) {
124 		warn("popen /sbin/dmesg");
125 		goto out;
126 	}
127 
128 	/* search the string */
129 	while (1) {
130 		/* read a line */
131 		fgets(line, sizeof(line), fd);
132 
133 		/* error checking */
134 		if (ferror(fd)) {
135 			ret = -1;
136 			goto out;
137 		}
138 
139 		/* quit */
140 		if (feof(fd))
141 			break;
142 
143 		/* check if found */
144 		error = regexec(&regex, line, 2, matches, 0);
145 		if (error == REG_NOMATCH)
146 			continue;
147 		if (error) {
148 			warnx("regexec pattern=%s line=%s error=%d",
149 			    pattern, line, error);
150 			ret = -1;
151 			goto out;
152 		}
153 
154 		/* convert it */
155 		line[matches[1].rm_eo] = '\0';
156 		ret = strtonum(&line[matches[1].rm_so], 0, 255, &errstr);
157 		if (errstr) {
158 			warnx("strtonum: number=%s error=%s",
159 			    &line[matches[1].rm_so], errstr);
160 			ret = -1;
161 			goto out;
162 		}
163 	}
164 
165 	/* cleanup */
166 	if (pclose(fd) == -1)
167 		goto out;
168 
169 	/* not found */
170 	if (ret == -1)
171 		ret = 0;
172 
173 out:
174 	free(pattern);
175 	regfree(&regex);
176 	return (ret);
177 }
178 
179 /* mainly stolen from src/bin/cat/cat.c */
180 static int
181 drainfd(int rfd, int wfd)
182 {
183 	char buf[1024];
184 	ssize_t nr, nw, off;
185 
186 	while ((nr = read(rfd, buf, sizeof(buf))) != -1 && nr != 0)
187 		for (off = 0; nr; nr -= nw, off += nw)
188 			if ((nw = write(wfd, buf + off, (size_t)nr)) == 0 ||
189 			    nw == -1)
190 				return (-1);
191 	if (nr < 0)
192 		return (-1);
193 
194 	return (0);
195 }
196 
197 void
198 _start_test(int *ret, const char *test_name, const char *request,
199     void (*test_func)(void))
200 {
201 	int fildes[2];
202 	pid_t pid;
203 	int status;
204 
205 	/* early print testname */
206 	printf("test(%s): pledge=", test_name);
207 	if (request) {
208 		printf("(\"%s\",", request);
209 		printf("NULL)");
210 	} else
211 		printf("skip");
212 
213 	/* unlink previous coredump (if exists) */
214 	if (clear_coredump(ret, test_name) == -1)
215 		return;
216 
217 	/* flush outputs (for STDOUT_FILENO manipulation) */
218 	if (fflush(NULL) != 0) {
219 		warn("test(%s) fflush", test_name);
220 		*ret = EXIT_FAILURE;
221 		return;
222 	}
223 
224 	/* make pipe to grab output */
225 	if (pipe(fildes) != 0) {
226 		warn("test(%s) pipe", test_name);
227 		*ret = EXIT_FAILURE;
228 		return;
229 	}
230 
231 	/* fork and launch the test */
232 	switch (pid = fork()) {
233 	case -1:
234 		(void)close(fildes[0]);
235 		(void)close(fildes[1]);
236 
237 		warn("test(%s) fork", test_name);
238 		*ret = EXIT_FAILURE;
239 		return;
240 
241 	case 0:
242 		/* output to pipe */
243 		(void)close(fildes[0]);
244 		while (dup2(fildes[1], STDOUT_FILENO) == -1)
245 			if (errno != EINTR)
246 				err(errno, "dup2");
247 
248 		/* create a new session (for kill) */
249 		setsid();
250 
251 		/* set pledge policy */
252 		if (request && pledge(request, NULL) != 0)
253 			err(errno, "pledge");
254 
255 		/* reset errno and launch test */
256 		errno = 0;
257 		test_func();
258 
259 		if (errno != 0)
260 			_exit(errno);
261 
262 		_exit(EXIT_SUCCESS);
263 		/* NOTREACHED */
264 	}
265 
266 	/* copy pipe to output */
267 	(void)close(fildes[1]);
268 	if (drainfd(fildes[0], STDOUT_FILENO) != 0) {
269 		warn("test(%s): drainfd", test_name);
270 		*ret = EXIT_FAILURE;
271 		return;
272 	}
273 	if (close(fildes[0]) != 0) {
274 		warn("test(%s): close", test_name);
275 		*ret = EXIT_FAILURE;
276 		return;
277 	}
278 
279 	/* wait for test to terminate */
280 	while (waitpid(pid, &status, 0) < 0) {
281 		if (errno == EAGAIN)
282 			continue;
283 		warn("test(%s): waitpid", test_name);
284 		*ret = EXIT_FAILURE;
285 		return;
286 	}
287 
288 	/* show status and details */
289 	printf(" status=%d", status);
290 
291 	if (WIFCONTINUED(status))
292 		printf(" continued");
293 
294 	if (WIFEXITED(status)) {
295 		int e = WEXITSTATUS(status);
296 		printf(" exit=%d", e);
297 		if (e > 0 && e <= ELAST)
298 			printf(" (errno: \"%s\")", strerror(e));
299 	}
300 
301 	if (WIFSIGNALED(status)) {
302 		int signal = WTERMSIG(status);
303 		printf(" signal=%d", signal);
304 
305 		/* check if core file is really here ? */
306 		if (WCOREDUMP(status)) {
307 			int coredump = check_coredump();
308 
309 			switch(coredump) {
310 			case -1: /* error */
311 				warn("test(%s): check_coredump", test_name);
312 				*ret = EXIT_FAILURE;
313 				return;
314 
315 			case 0: /* found */
316 				printf(" coredump=present");
317 				break;
318 
319 			case 1:	/* not found */
320 				printf(" coredump=absent");
321 				break;
322 
323 			default:
324 				warnx("test(%s): unknown coredump code %d",
325 				    test_name, coredump);
326 				*ret = EXIT_FAILURE;
327 				return;
328 			}
329 
330 		}
331 
332 		/* grab pledged syscall from dmesg */
333 		if ((signal == SIGKILL) || (signal = SIGABRT)) {
334 			int syscall = grab_syscall(pid);
335 			switch (syscall) {
336 			case -1:	/* error */
337 				warn("test(%s): grab_syscall pid=%d", test_name,
338 				    pid);
339 				*ret = EXIT_FAILURE;
340 				return;
341 
342 			case 0:		/* not found */
343 				printf(" pledged_syscall=not_found");
344 				break;
345 
346 			default:
347 				printf(" pledged_syscall=%d", syscall);
348 			}
349 		}
350 	}
351 
352 	if (WIFSTOPPED(status))
353 		printf(" stop=%d", WSTOPSIG(status));
354 
355 	printf("\n");
356 }
357