xref: /openbsd/usr.sbin/syslogd/privsep.c (revision 1821443c)
1 /*	$OpenBSD: privsep.c,v 1.5 2003/08/15 23:13:06 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("priv_open_tty failed");
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("priv_open_log failed");
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("priv_open_utmp failed");
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("priv_open_config failed");
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, "rejected attempt to gethostbyaddr");
265 			/* Expecting: length, address, address family */
266 			must_read(socks[0], &addr_len, sizeof(int));
267 			if (addr_len > sizeof(hostname))
268 				_exit(0);
269 			must_read(socks[0], hostname, addr_len);
270 			must_read(socks[0], &addr_af, sizeof(int));
271 			hp = gethostbyaddr(hostname, addr_len, addr_af);
272 			if (hp == NULL) {
273 				addr_len = 0;
274 				must_write(socks[0], &addr_len, sizeof(int));
275 			} else {
276 				addr_len = strlen(hp->h_name) + 1;
277 				must_write(socks[0], &addr_len, sizeof(int));
278 				must_write(socks[0], hp->h_name, addr_len);
279 			}
280 			break;
281 		default:
282 			errx(1, "unknown command %d", cmd);
283 			break;
284 		}
285 	}
286 
287 	/* Unlink any domain sockets that have been opened */
288 	for (i = 0; i < nfunix; i++)
289 		if (funixn[i] && funix[i] != -1)
290 			(void)unlink(funixn[i]);
291 
292 	if (cur_state == STATE_RESTART) {
293 		int r;
294 
295 		wait(&r);
296 		execvp(argv[0], argv);
297 	}
298 	_exit(1);
299 }
300 
301 /* Check that the terminal device is ok, and if not, rewrite to /dev/null.
302  * Either /dev/console or /dev/tty* are allowed.
303  */
304 static void
305 check_tty_name(char *tty, size_t ttylen)
306 {
307 	const char ttypre[] = "/dev/tty";
308 	char *p;
309 
310 	/* Any path containing '..' is invalid.  */
311 	for (p = tty; *p && (p - tty) < ttylen; p++)
312 		if (*p == '.' && *(p + 1) == '.')
313 			goto bad_path;
314 
315 	if (strcmp(_PATH_CONSOLE, tty) && strncmp(tty, ttypre, strlen(ttypre)))
316 		goto bad_path;
317 	return;
318 
319 bad_path:
320 	warnx ("%s: invalid attempt to open %s: rewriting to /dev/null",
321 	    __func__, tty);
322 	strlcpy(tty, "/dev/null", ttylen);
323 }
324 
325 /* If we are in the initial configuration state, accept a logname and add
326  * it to the list of acceptable logfiles.  Otherwise, check against this list
327  * and rewrite to /dev/null if it's a bad path.
328  */
329 static void
330 check_log_name(char *log, size_t loglen)
331 {
332 	struct logname *lg;
333 	char *p;
334 
335 	/* Any path containing '..' is invalid.  */
336 	for (p = log; *p && (p - log) < loglen; p++)
337 		if (*p == '.' && *(p + 1) == '.')
338 			goto bad_path;
339 
340 	switch (cur_state) {
341 	case STATE_CONFIG:
342 		lg = malloc(sizeof(struct logname));
343 		if (!lg)
344 			err(1, "check_log_name() malloc");
345 		strlcpy(lg->path, log, MAXPATHLEN);
346 		TAILQ_INSERT_TAIL(&lognames, lg, next);
347 		break;
348 	case STATE_RUNNING:
349 		TAILQ_FOREACH(lg, &lognames, next)
350 			if (!strcmp(lg->path, log))
351 				return;
352 		goto bad_path;
353 		break;
354 	default:
355 		/* Any other state should just refuse the request */
356 		goto bad_path;
357 		break;
358 	}
359 	return;
360 
361 bad_path:
362 	warnx("%s: invalid attempt to open %s: rewriting to /dev/null",
363 	    __func__, log);
364 	strlcpy(log, "/dev/null", loglen);
365 }
366 
367 /* Crank our state into less permissive modes */
368 static void
369 increase_state(int state)
370 {
371 	if (state <= cur_state)
372 		errx(1, "attempt to decrease or match current state");
373 	if (state < STATE_INIT || state > STATE_RESTART)
374 		errx(1, "attempt to switch to invalid state");
375 	cur_state = state;
376 }
377 
378 /* Open console or a terminal device for writing */
379 int
380 priv_open_tty(const char *tty)
381 {
382 	char path[MAXPATHLEN];
383 	int cmd, fd;
384 
385 	if (priv_fd < 0)
386 		errx(1, "%s: called from privileged portion", __func__);
387 
388 	if (strlcpy(path, tty, sizeof path) >= sizeof(path))
389 		return -1;
390 	cmd = PRIV_OPEN_TTY;
391 	must_write(priv_fd, &cmd, sizeof(int));
392 	must_write(priv_fd, path, sizeof(path));
393 	fd = receive_fd(priv_fd);
394 	return fd;
395 }
396 
397 /* Open log-file */
398 int
399 priv_open_log(const char *log)
400 {
401 	char path[MAXPATHLEN];
402 	int cmd, fd;
403 
404 	if (priv_fd < 0)
405 		errx(1, "%s: called from privileged child", __func__);
406 
407 	if (strlcpy(path, log, sizeof path) >= sizeof(path))
408 		return -1;
409 	cmd = PRIV_OPEN_LOG;
410 	must_write(priv_fd, &cmd, sizeof(int));
411 	must_write(priv_fd, path, sizeof(path));
412 	fd = receive_fd(priv_fd);
413 	return fd;
414 }
415 
416 /* Open utmp for reading */
417 FILE *
418 priv_open_utmp(void)
419 {
420 	int cmd, fd;
421 	FILE *fp;
422 
423 	if (priv_fd < 0)
424 		errx(1, "%s: called from privileged portion", __func__);
425 
426 	cmd = PRIV_OPEN_UTMP;
427 	must_write(priv_fd, &cmd, sizeof(int));
428 	fd = receive_fd(priv_fd);
429 	if (fd < 0)
430 		return NULL;
431 
432 	fp = fdopen(fd, "r");
433 	if (!fp) {
434 		warn("priv_open_utmp: fdopen() failed");
435 		close(fd);
436 		return NULL;
437 	}
438 
439 	return fp;
440 }
441 
442 /* Open syslog config file for reading */
443 FILE *
444 priv_open_config(void)
445 {
446 	int cmd, fd;
447 	FILE *fp;
448 
449 	if (priv_fd < 0)
450 		errx(1, "%s: called from privileged portion", __func__);
451 
452 	cmd = PRIV_OPEN_CONFIG;
453 	must_write(priv_fd, &cmd, sizeof(int));
454 	fd = receive_fd(priv_fd);
455 	if (fd < 0)
456 		return NULL;
457 
458 	fp = fdopen(fd, "r");
459 	if (!fp) {
460 		warn("priv_open_config: fdopen() failed");
461 		close(fd);
462 		return NULL;
463 	}
464 
465 	return fp;
466 }
467 
468 /* Ask if config file has been modified since last attempt to read it */
469 int
470 priv_config_modified()
471 {
472 	int cmd, res;
473 
474 	if (priv_fd < 0)
475 		errx(1, "%s: called from privileged portion", __func__);
476 
477 	cmd = PRIV_CONFIG_MODIFIED;
478 	must_write(priv_fd, &cmd, sizeof(int));
479 
480 	/* Expect back integer signalling 1 for modification */
481 	must_read(priv_fd, &res, sizeof(int));
482 	return res;
483 }
484 
485 /* Child can signal that its initial parsing is done, so that parent
486  * can revoke further logfile permissions.  This call only works once. */
487 void
488 priv_config_parse_done(void)
489 {
490 	int cmd;
491 
492 	if (priv_fd < 0)
493 		errx(1, "%s: called from privileged portion", __func__);
494 
495 	cmd = PRIV_DONE_CONFIG_PARSE;
496 	must_write(priv_fd, &cmd, sizeof(int));
497 }
498 
499 /* Resolve hostname into address.  Response is placed into addr, and
500  * the length is returned (zero on error) */
501 int
502 priv_gethostbyname(char *host, char *addr, size_t addr_len)
503 {
504 	char hostcpy[MAXHOSTNAMELEN];
505 	int cmd, ret_len;
506 
507 	if (strlcpy(hostcpy, host, sizeof hostcpy) >= sizeof(hostcpy))
508 		errx(1, "%s: overflow attempt in hostname", __func__);
509 
510 	if (priv_fd < 0)
511 		errx(1, "%s: called from privileged portion", __func__);
512 
513 	cmd = PRIV_GETHOSTBYNAME;
514 	must_write(priv_fd, &cmd, sizeof(int));
515 	must_write(priv_fd, hostcpy, sizeof(hostcpy));
516 
517 	/* Expect back an integer size, and then a string of that length */
518 	must_read(priv_fd, &ret_len, sizeof(int));
519 
520 	/* Check there was no error (indicated by a return of 0) */
521 	if (!ret_len)
522 		return 0;
523 
524 	/* Make sure we aren't overflowing the passed in buffer */
525 	if (addr_len < ret_len)
526 		errx(1, "%s: overflow attempt in return", __func__);
527 
528 	/* Read the resolved address and make sure we got all of it */
529 	must_read(priv_fd, addr, ret_len);
530 	return ret_len;
531 }
532 
533 /* Reverse address resolution; response is placed into res, and length of
534  * response is returned (zero on error) */
535 int
536 priv_gethostbyaddr(char *addr, int addr_len, int af, char *res, size_t res_len)
537 {
538 	int cmd, ret_len;
539 
540 	if (priv_fd < 0)
541 		errx(1, "%s called from privileged portion", __func__);
542 
543 	cmd = PRIV_GETHOSTBYADDR;
544 	must_write(priv_fd, &cmd, sizeof(int));
545 	must_write(priv_fd, &addr_len, sizeof(int));
546 	must_write(priv_fd, addr, addr_len);
547 	must_write(priv_fd, &af, sizeof(int));
548 
549 	/* Expect back an integer size, and then a string of that length */
550 	must_read(priv_fd, &ret_len, sizeof(int));
551 
552 	/* Check there was no error (indicated by a return of 0) */
553 	if (!ret_len)
554 		return 0;
555 
556 	/* Check we don't overflow the passed in buffer */
557 	if (res_len < ret_len)
558 		errx(1, "%s: overflow attempt in return", __func__);
559 
560 	/* Read the resolved hostname */
561 	must_read(priv_fd, res, ret_len);
562 	return ret_len;
563 }
564 
565 /* If priv parent gets a TERM or HUP, pass it through to child instead */
566 static void
567 sig_pass_to_chld(int sig)
568 {
569 	kill(child_pid, sig);
570 }
571 
572 /* When child dies, move into the shutdown state */
573 static void
574 sig_got_chld(int sig)
575 {
576 	if (cur_state < STATE_QUIT)
577 		cur_state = STATE_QUIT;
578 }
579 
580 /* Read data with the assertion that it all must come through, or
581  * else abort the process.  Based on atomicio() from openssh. */
582 static void
583 must_read(int fd, void *buf, size_t n)
584 {
585 	char *s = buf;
586 	ssize_t res, pos = 0;
587 
588 	while (n > pos) {
589 		res = read(fd, s + pos, n - pos);
590 		switch (res) {
591 		case -1:
592 			if (errno == EINTR || errno == EAGAIN)
593 				continue;
594 		case 0:
595 			_exit(0);
596 		default:
597 			pos += res;
598 		}
599 	}
600 }
601 
602 /* Write data with the assertion that it all has to be written, or
603  * else abort the process.  Based on atomicio() from openssh. */
604 static void
605 must_write(int fd, void *buf, size_t n)
606 {
607 	char *s = buf;
608 	ssize_t res, pos = 0;
609 
610 	while (n > pos) {
611 		res = write(fd, s + pos, n - pos);
612 		switch (res) {
613 		case -1:
614 			if (errno == EINTR || errno == EAGAIN)
615 				continue;
616 		case 0:
617 			_exit(0);
618 		default:
619 			pos += res;
620 		}
621 	}
622 }
623