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