xref: /openbsd-src/usr.sbin/syslogd/privsep.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /*	$OpenBSD: privsep.c,v 1.3 2003/07/31 21:28:28 deraadt Exp $	*/
2 
3 /*
4  * Copyright (c) 2003 Anil Madhavapeddy <anil@recoil.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 #include <sys/ioctl.h>
19 #include <sys/param.h>
20 #include <sys/queue.h>
21 #include <sys/uio.h>
22 #include <sys/types.h>
23 #include <sys/socket.h>
24 #include <sys/stat.h>
25 #include <sys/wait.h>
26 #include <err.h>
27 #include <errno.h>
28 #include <fcntl.h>
29 #include <netdb.h>
30 #include <paths.h>
31 #include <pwd.h>
32 #include <signal.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include <unistd.h>
37 #include <util.h>
38 #include <utmp.h>
39 #include "syslogd.h"
40 
41 /*
42  * syslogd can only go forward in these states; each state should represent
43  * less privilege.   After STATE_INIT, the child is allowed to parse its
44  * config file once, and communicate the information regarding what logfiles
45  * it needs access to back to the parent.  When that is done, it sends a
46  * message to the priv parent revoking this access, moving to STATE_RUNNING.
47  * In this state, any log-files not in the access list are rejected.
48  *
49  * This allows a HUP signal to the child to reopen its log files, and
50  * the config file to be parsed if it hasn't been changed (this is still
51  * useful to force resoluton of remote syslog servers again).
52  * If the config file has been modified, then the child dies, and
53  * the priv parent restarts itself.
54  */
55 enum priv_state {
56 	STATE_INIT,		/* just started up */
57 	STATE_CONFIG,		/* parsing config file for first time */
58 	STATE_RUNNING,		/* running and accepting network traffic */
59 	STATE_QUIT,		/* shutting down */
60 	STATE_RESTART		/* kill child and re-exec to restart */
61 };
62 
63 enum cmd_types {
64 	PRIV_OPEN_TTY,		/* open terminal or console device */
65 	PRIV_OPEN_LOG,		/* open logfile for appending */
66 	PRIV_OPEN_UTMP,		/* open utmp for reading only */
67 	PRIV_OPEN_CONFIG,	/* open config file for reading only */
68 	PRIV_CONFIG_MODIFIED,	/* check if config file has been modified */
69 	PRIV_GETHOSTBYNAME,	/* resolve hostname into numerical address */
70 	PRIV_GETHOSTBYADDR,	/* resolve numeric address into hostname */
71 	PRIV_DONE_CONFIG_PARSE	/* signal that the initial config parse is done */
72 };
73 
74 static int priv_fd = -1;
75 static pid_t child_pid;
76 static char config_file[MAXPATHLEN];
77 static struct stat cf_info;
78 static int allow_gethostbyaddr = 0;
79 static volatile sig_atomic_t cur_state = STATE_INIT;
80 
81 /* Queue for the allowed logfiles */
82 struct logname {
83 	char path[MAXPATHLEN];
84 	TAILQ_ENTRY(logname) next;
85 };
86 static TAILQ_HEAD(, logname) lognames;
87 
88 static void check_log_name(char *, size_t);
89 static void check_tty_name(char *, size_t);
90 static void increase_state(int);
91 static void sig_pass_to_chld(int);
92 static void sig_got_chld(int);
93 static void must_read(int, void *, size_t);
94 static void must_write(int, void *, size_t);
95 
96 int
97 priv_init(char *conf, int numeric, int lockfd, int nullfd, char *argv[])
98 {
99 	int i, fd, socks[2], cmd, addr_len, addr_af, result;
100 	char path[MAXPATHLEN], hostname[MAXHOSTNAMELEN];
101 	struct stat cf_stat;
102 	struct hostent *hp;
103 	struct passwd *pw;
104 
105 	/* Create sockets */
106 	if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1)
107 		err(1, "socketpair() failed");
108 
109 	pw = getpwnam("_syslogd");
110 	if (pw == NULL)
111 		errx(1, "unknown user _syslogd");
112 
113 	child_pid = fork();
114 	if (child_pid < 0)
115 		err(1, "fork() failed");
116 
117 	if (!child_pid) {
118 		/* Child - drop privileges and return */
119 		if (chroot(pw->pw_dir) != 0)
120 			err(1, "unable to chroot");
121 		chdir("/");
122 		if (setegid(pw->pw_gid) == -1)
123 			err(1, "setegid() failed");
124 		if (setgid(pw->pw_gid) == -1)
125 			err(1, "setgid() failed");
126 		if (seteuid(pw->pw_uid) == -1)
127 			err(1, "seteuid() failed");
128 		if (setuid(pw->pw_uid) == -1)
129 			err(1, "setuid() failed");
130 		close(socks[0]);
131 		priv_fd = socks[1];
132 		return 0;
133 	}
134 
135 	close(lockfd);
136 	if (!Debug) {
137 		dup2(nullfd, STDIN_FILENO);
138 		dup2(nullfd, STDOUT_FILENO);
139 		dup2(nullfd, STDERR_FILENO);
140 	}
141 
142 	if (nullfd > 2)
143 		close(nullfd);
144 
145 	/* Father */
146 	for (i = 1; i <= _NSIG; i++)
147 		signal(i, SIG_DFL);
148 
149 	/* Pass TERM/HUP through to child, and accept CHLD */
150 	signal(SIGTERM, sig_pass_to_chld);
151 	signal(SIGHUP, sig_pass_to_chld);
152 	signal(SIGCHLD, sig_got_chld);
153 
154 	setproctitle("[priv]");
155 	close(socks[1]);
156 
157 	/* Close descriptors that only the unpriv child needs */
158 	for (i = 0; i < nfunix; i++)
159 		if (funix[i] != -1)
160 			close(funix[i]);
161 	if (finet != -1)
162 		close(finet);
163 	if (fklog != -1)
164 		close(fklog);
165 
166 	/* Save the config file specified by the child process */
167 	if (strlcpy(config_file, conf, sizeof config_file) >= sizeof(config_file))
168 		errx(1, "config_file truncation");
169 
170 	if (stat(config_file, &cf_info) < 0)
171 		err(1, "stat config file failed");
172 
173 	/* Save whether or not the child can have access to gethostbyaddr(3) */
174 	if (numeric > 0)
175 		allow_gethostbyaddr = 0;
176 	else
177 		allow_gethostbyaddr = 1;
178 
179 	TAILQ_INIT(&lognames);
180 	increase_state(STATE_CONFIG);
181 
182 	while (cur_state < STATE_QUIT) {
183 		must_read(socks[0], &cmd, sizeof(int));
184 		switch (cmd) {
185 		case PRIV_OPEN_TTY:
186 			must_read(socks[0], &path, sizeof path);
187 			dprintf("[priv]: msg PRIV_OPEN_TTY received\n");
188 			check_tty_name(path, sizeof path);
189 			fd = open(path, O_WRONLY|O_NONBLOCK, 0);
190 			if (fd < 0)
191 				warnx("%s: priv_open_tty failed", __func__);
192 			send_fd(socks[0], fd);
193 			close(fd);
194 			break;
195 
196 		case PRIV_OPEN_LOG:
197 			must_read(socks[0], &path, sizeof path);
198 			dprintf("[priv]: msg PRIV_OPEN_LOG received: %s\n", path);
199 			check_log_name(path, sizeof path);
200 			fd = open(path, O_WRONLY|O_APPEND|O_NONBLOCK, 0);
201 			if (fd < 0)
202 				warnx("%s: priv_open_log failed", __func__);
203 			send_fd(socks[0], fd);
204 			close(fd);
205 			break;
206 
207 		case PRIV_OPEN_UTMP:
208 			dprintf("[priv]: msg PRIV_OPEN_UTMP received\n");
209 			fd = open(_PATH_UTMP, O_RDONLY|O_NONBLOCK, 0);
210 			if (fd < 0)
211 				warnx("%s: priv_open_utmp failed", __func__);
212 			send_fd(socks[0], fd);
213 			close(fd);
214 			break;
215 
216 		case PRIV_OPEN_CONFIG:
217 			dprintf("[priv]: msg PRIV_OPEN_CONFIG received\n");
218 			stat(config_file, &cf_info);
219 			fd = open(config_file, O_RDONLY|O_NONBLOCK, 0);
220 			if (fd < 0)
221 				warnx("%s: priv_open_config failed", __func__);
222 			send_fd(socks[0], fd);
223 			close(fd);
224 			break;
225 
226 		case PRIV_CONFIG_MODIFIED:
227 			dprintf("[priv]: msg PRIV_CONFIG_MODIFIED received\n");
228 			if (stat(config_file, &cf_stat) < 0 ||
229 			    timespeccmp(&cf_info.st_mtimespec,
230 			    &cf_stat.st_mtimespec, <) ||
231 			    cf_info.st_size != cf_stat.st_size) {
232 				dprintf("config file modified: restarting\n");
233 				result = 1;
234 				must_write(socks[0], &result, sizeof(int));
235 				increase_state(STATE_RESTART);
236 			} else {
237 				result = 0;
238 				must_write(socks[0], &result, sizeof(int));
239 			}
240 			break;
241 
242 		case PRIV_DONE_CONFIG_PARSE:
243 			dprintf("[priv]: msg PRIV_DONE_CONFIG_PARSE received\n");
244 			increase_state(STATE_RUNNING);
245 			break;
246 
247 		case PRIV_GETHOSTBYNAME:
248 			dprintf("[priv]: msg PRIV_GETHOSTBYNAME received\n");
249 			/* Expecting: hostname[MAXHOSTNAMELEN] */
250 			must_read(socks[0], &hostname, sizeof hostname);
251 			hp = gethostbyname(hostname);
252 			if (hp == NULL) {
253 				addr_len = 0;
254 				must_write(socks[0], &addr_len, sizeof(int));
255 			} else {
256 				must_write(socks[0], &hp->h_length, sizeof(int));
257 				must_write(socks[0], hp->h_addr, hp->h_length);
258 			}
259 			break;
260 
261 		case PRIV_GETHOSTBYADDR:
262 			dprintf("[priv]: msg PRIV_GETHOSTBYADDR received\n");
263 			if (!allow_gethostbyaddr)
264 				errx(1, "%s: rejected attempt to gethostbyaddr",
265 				    __func__);
266 			/* Expecting: length, address, address family */
267 			must_read(socks[0], &addr_len, sizeof(int));
268 			if (addr_len > sizeof(hostname))
269 				_exit(0);
270 			must_read(socks[0], hostname, addr_len);
271 			must_read(socks[0], &addr_af, sizeof(int));
272 			hp = gethostbyaddr(hostname, addr_len, addr_af);
273 			if (hp == NULL) {
274 				addr_len = 0;
275 				must_write(socks[0], &addr_len, sizeof(int));
276 			} else {
277 				addr_len = strlen(hp->h_name) + 1;
278 				must_write(socks[0], &addr_len, sizeof(int));
279 				must_write(socks[0], hp->h_name, addr_len);
280 			}
281 			break;
282 		default:
283 			errx(1, "%s: unknown command %d", __func__, cmd);
284 			break;
285 		}
286 	}
287 
288 	dprintf("%s: shutting down priv parent\n", __func__);
289 	/* Unlink any domain sockets that have been opened */
290 	for (i = 0; i < nfunix; i++)
291 		if (funixn[i] && funix[i] != -1)
292 			(void)unlink(funixn[i]);
293 
294 	if (cur_state == STATE_RESTART) {
295 		int r;
296 
297 		wait(&r);
298 		execvp(argv[0], argv);
299 	}
300 	_exit(1);
301 }
302 
303 /* Check that the terminal device is ok, and if not, rewrite to /dev/null.
304  * Either /dev/console or /dev/tty* are allowed.
305  */
306 static void
307 check_tty_name(char *tty, size_t ttylen)
308 {
309 	const char ttypre[] = "/dev/tty";
310 	char *p;
311 
312 	/* Any path containing '..' is invalid.  */
313 	for (p = tty; *p && (p - tty) < ttylen; p++)
314 		if (*p == '.' && *(p + 1) == '.')
315 			goto bad_path;
316 
317 	if (strcmp(_PATH_CONSOLE, tty) && strncmp(tty, ttypre, strlen(ttypre)))
318 		goto bad_path;
319 	return;
320 
321 bad_path:
322 	warnx ("%s: invalid attempt to open %s: rewriting to /dev/null",
323 	    __func__, tty);
324 	strlcpy(tty, "/dev/null", ttylen);
325 }
326 
327 /* If we are in the initial configuration state, accept a logname and add
328  * it to the list of acceptable logfiles.  Otherwise, check against this list
329  * and rewrite to /dev/null if it's a bad path.
330  */
331 static void
332 check_log_name(char *log, size_t loglen)
333 {
334 	struct logname *lg;
335 	char *p;
336 
337 	/* Any path containing '..' is invalid.  */
338 	for (p = log; *p && (p - log) < loglen; p++)
339 		if (*p == '.' && *(p + 1) == '.')
340 			goto bad_path;
341 
342 	switch (cur_state) {
343 	case STATE_CONFIG:
344 		lg = malloc(sizeof(struct logname));
345 		if (!lg)
346 			err(1, "check_log_name() malloc");
347 		strlcpy(lg->path, log, MAXPATHLEN);
348 		TAILQ_INSERT_TAIL(&lognames, lg, next);
349 		break;
350 	case STATE_RUNNING:
351 		TAILQ_FOREACH(lg, &lognames, next)
352 			if (!strcmp(lg->path, log))
353 				return;
354 		goto bad_path;
355 		break;
356 	default:
357 		/* Any other state should just refuse the request */
358 		goto bad_path;
359 		break;
360 	}
361 	return;
362 
363 bad_path:
364 	warnx("%s: invalid attempt to open %s: rewriting to /dev/null",
365 	    __func__, log);
366 	strlcpy(log, "/dev/null", loglen);
367 }
368 
369 /* Crank our state into less permissive modes */
370 static void
371 increase_state(int state)
372 {
373 	if (state <= cur_state)
374 		errx(1, "attempt to decrease or match current state");
375 	if (state < STATE_INIT || state > STATE_RESTART)
376 		errx(1, "attempt to switch to invalid state");
377 	cur_state = state;
378 }
379 
380 /* Open console or a terminal device for writing */
381 int
382 priv_open_tty(const char *tty)
383 {
384 	char path[MAXPATHLEN];
385 	int cmd, fd;
386 
387 	dprintf("[unpriv] priv_open_tty\n");
388 	if (priv_fd < 0)
389 		errx(1, "%s: called from privileged portion", __func__);
390 
391 	if (strlcpy(path, tty, sizeof path) >= sizeof(path))
392 		return -1;
393 	cmd = PRIV_OPEN_TTY;
394 	must_write(priv_fd, &cmd, sizeof(int));
395 	must_write(priv_fd, path, sizeof(path));
396 	fd = receive_fd(priv_fd);
397 	return fd;
398 }
399 
400 /* Open log-file */
401 int
402 priv_open_log(const char *log)
403 {
404 	char path[MAXPATHLEN];
405 	int cmd, fd;
406 
407 	dprintf("[unpriv] priv_open_log %s\n", log);
408 	if (priv_fd < 0)
409 		errx(1, "%s: called from privileged child\n", __func__);
410 
411 	if (strlcpy(path, log, sizeof path) >= sizeof(path))
412 		return -1;
413 	cmd = PRIV_OPEN_LOG;
414 	must_write(priv_fd, &cmd, sizeof(int));
415 	must_write(priv_fd, path, sizeof(path));
416 	fd = receive_fd(priv_fd);
417 	return fd;
418 }
419 
420 /* Open utmp for reading */
421 FILE *
422 priv_open_utmp(void)
423 {
424 	int cmd, fd;
425 	FILE *fp;
426 
427 	dprintf("[unpriv] priv_open_utmp\n");
428 	if (priv_fd < 0)
429 		errx(1, "%s: called from privileged portion", __func__);
430 
431 	cmd = PRIV_OPEN_UTMP;
432 	must_write(priv_fd, &cmd, sizeof(int));
433 	fd = receive_fd(priv_fd);
434 	if (fd < 0)
435 		return NULL;
436 
437 	fp = fdopen(fd, "r");
438 	if (!fp) {
439 		warn("priv_open_utmp: fdopen() failed");
440 		close(fd);
441 		return NULL;
442 	}
443 
444 	return fp;
445 }
446 
447 /* Open syslog config file for reading */
448 FILE *
449 priv_open_config(void)
450 {
451 	int cmd, fd;
452 	FILE *fp;
453 
454 	dprintf("[unpriv] priv_open_config\n");
455 	if (priv_fd < 0)
456 		errx(1, "%s: called from privileged portion", __func__);
457 
458 	cmd = PRIV_OPEN_CONFIG;
459 	must_write(priv_fd, &cmd, sizeof(int));
460 	fd = receive_fd(priv_fd);
461 	if (fd < 0)
462 		return NULL;
463 
464 	fp = fdopen(fd, "r");
465 	if (!fp) {
466 		warn("priv_open_config: fdopen() failed");
467 		close(fd);
468 		return NULL;
469 	}
470 
471 	return fp;
472 }
473 
474 /* Ask if config file has been modified since last attempt to read it */
475 int
476 priv_config_modified()
477 {
478 	int cmd, res;
479 
480 	dprintf("[unpriv] priv_config_modified called\n");
481 	if (priv_fd < 0)
482 		errx(1, "%s: called from privileged portion", __func__);
483 	cmd = PRIV_CONFIG_MODIFIED;
484 	must_write(priv_fd, &cmd, sizeof(int));
485 
486 	/* Expect back integer signalling 1 for modification */
487 	must_read(priv_fd, &res, sizeof(int));
488 	return res;
489 }
490 
491 /* Child can signal that its initial parsing is done, so that parent
492  * can revoke further logfile permissions.  This call only works once. */
493 void
494 priv_config_parse_done(void)
495 {
496 	int cmd;
497 
498 	if (priv_fd < 0)
499 		errx(1, "%s: called from privileged portion", __func__);
500 
501 	cmd = PRIV_DONE_CONFIG_PARSE;
502 	must_write(priv_fd, &cmd, sizeof(int));
503 }
504 
505 /* Resolve hostname into address.  Response is placed into addr, and
506  * the length is returned (zero on error) */
507 int
508 priv_gethostbyname(char *host, char *addr, size_t addr_len)
509 {
510 	char hostcpy[MAXHOSTNAMELEN];
511 	int cmd, ret_len;
512 
513 	dprintf("[unpriv] %s called\n", __func__);
514 
515 	if (strlcpy(hostcpy, host, sizeof hostcpy) >= sizeof(hostcpy))
516 		errx(1, "%s: overflow attempt in hostname", __func__);
517 
518 	if (priv_fd < 0)
519 		errx(1, "%s: called from privileged portion", __func__);
520 
521 	cmd = PRIV_GETHOSTBYNAME;
522 	must_write(priv_fd, &cmd, sizeof(int));
523 	must_write(priv_fd, hostcpy, sizeof(hostcpy));
524 
525 	/* Expect back an integer size, and then a string of that length */
526 	must_read(priv_fd, &ret_len, sizeof(int));
527 
528 	/* Check there was no error (indicated by a return of 0) */
529 	if (!ret_len)
530 		return 0;
531 
532 	/* Make sure we aren't overflowing the passed in buffer */
533 	if (addr_len < ret_len)
534 		errx(1, "%s: overflow attempt in return", __func__);
535 
536 	/* Read the resolved address and make sure we got all of it */
537 	must_read(priv_fd, addr, ret_len);
538 	return ret_len;
539 }
540 
541 /* Reverse address resolution; response is placed into res, and length of
542  * response is returned (zero on error) */
543 int
544 priv_gethostbyaddr(char *addr, int addr_len, int af, char *res, size_t res_len)
545 {
546 	int cmd, ret_len;
547 
548 	dprintf("[unpriv] %s called\n", __func__);
549 
550 	if (priv_fd < 0)
551 		errx(1, "%s called from privileged portion", __func__);
552 
553 	cmd = PRIV_GETHOSTBYADDR;
554 	must_write(priv_fd, &cmd, sizeof(int));
555 	must_write(priv_fd, &addr_len, sizeof(int));
556 	must_write(priv_fd, addr, addr_len);
557 	must_write(priv_fd, &af, sizeof(int));
558 
559 	/* Expect back an integer size, and then a string of that length */
560 	must_read(priv_fd, &ret_len, sizeof(int));
561 
562 	/* Check there was no error (indicated by a return of 0) */
563 	if (!res_len)
564 		return 0;
565 
566 	/* Check we don't overflow the passed in buffer */
567 	if (res_len < ret_len)
568 		errx(1, "%s: overflow attempt in return", __func__);
569 
570 	/* Read the resolved hostname */
571 	must_read(priv_fd, res, ret_len);
572 	return ret_len;
573 }
574 
575 /* If priv parent gets a TERM or HUP, pass it through to child instead */
576 static void
577 sig_pass_to_chld(int sig)
578 {
579 	kill(child_pid, sig);
580 }
581 
582 /* When child dies, move into the shutdown state */
583 static void
584 sig_got_chld(int sig)
585 {
586 	if (cur_state < STATE_QUIT)
587 		cur_state = STATE_QUIT;
588 }
589 
590 /* Read data with the assertion that it all must come through, or
591  * else abort the process.  Based on atomicio() from openssh. */
592 static void
593 must_read(int fd, void *buf, size_t n)
594 {
595 	char *s = buf;
596 	ssize_t res, pos = 0;
597 
598 	while (n > pos) {
599 		res = read(fd, s + pos, n - pos);
600 		switch (res) {
601 		case -1:
602 			if (errno == EINTR || errno == EAGAIN)
603 				continue;
604 		case 0:
605 			_exit(0);
606 		default:
607 			pos += res;
608 		}
609 	}
610 }
611 
612 /* Write data with the assertion that it all has to be written, or
613  * else abort the process.  Based on atomicio() from openssh. */
614 static void
615 must_write(int fd, void *buf, size_t n)
616 {
617 	char *s = buf;
618 	ssize_t res, pos = 0;
619 
620 	while (n > pos) {
621 		res = write(fd, s + pos, n - pos);
622 		switch (res) {
623 		case -1:
624 			if (errno == EINTR || errno == EAGAIN)
625 				continue;
626 		case 0:
627 			_exit(0);
628 		default:
629 			pos += res;
630 		}
631 	}
632 }
633