1 /* $OpenBSD: privsep.c,v 1.9 2003/10/26 18:21:49 avsm 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 }; 61 62 enum cmd_types { 63 PRIV_OPEN_TTY, /* open terminal or console device */ 64 PRIV_OPEN_LOG, /* open logfile for appending */ 65 PRIV_OPEN_UTMP, /* open utmp for reading only */ 66 PRIV_OPEN_CONFIG, /* open config file for reading only */ 67 PRIV_CONFIG_MODIFIED, /* check if config file has been modified */ 68 PRIV_GETHOSTBYNAME, /* resolve hostname into numerical address */ 69 PRIV_GETHOSTBYADDR, /* resolve numeric address into hostname */ 70 PRIV_DONE_CONFIG_PARSE /* signal that the initial config parse is done */ 71 }; 72 73 static int priv_fd = -1; 74 static pid_t child_pid = -1; 75 static char config_file[MAXPATHLEN]; 76 static struct stat cf_info; 77 static int allow_gethostbyaddr = 0; 78 static volatile sig_atomic_t cur_state = STATE_INIT; 79 80 /* Queue for the allowed logfiles */ 81 struct logname { 82 char path[MAXPATHLEN]; 83 TAILQ_ENTRY(logname) next; 84 }; 85 static TAILQ_HEAD(, logname) lognames; 86 87 static void check_log_name(char *, size_t); 88 static void check_tty_name(char *, size_t); 89 static void increase_state(int); 90 static void sig_pass_to_chld(int); 91 static void sig_got_chld(int); 92 static void must_read(int, void *, size_t); 93 static void must_write(int, void *, size_t); 94 static int may_read(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, restart; 100 size_t path_len, hostname_len; 101 char path[MAXPATHLEN], hostname[MAXHOSTNAMELEN]; 102 struct stat cf_stat; 103 struct hostent *hp; 104 struct passwd *pw; 105 106 /* Create sockets */ 107 if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1) 108 err(1, "socketpair() failed"); 109 110 pw = getpwnam("_syslogd"); 111 if (pw == NULL) 112 errx(1, "unknown user _syslogd"); 113 114 child_pid = fork(); 115 if (child_pid < 0) 116 err(1, "fork() failed"); 117 118 if (!child_pid) { 119 gid_t gidset[1]; 120 121 /* Child - drop privileges and return */ 122 if (chroot(pw->pw_dir) != 0) 123 err(1, "unable to chroot"); 124 chdir("/"); 125 126 gidset[0] = pw->pw_gid; 127 if (setgroups(1, gidset) == -1) 128 err(1, "setgroups() failed"); 129 if (setegid(pw->pw_gid) == -1) 130 err(1, "setegid() failed"); 131 if (setgid(pw->pw_gid) == -1) 132 err(1, "setgid() failed"); 133 if (seteuid(pw->pw_uid) == -1) 134 err(1, "seteuid() failed"); 135 if (setuid(pw->pw_uid) == -1) 136 err(1, "setuid() failed"); 137 close(socks[0]); 138 priv_fd = socks[1]; 139 return 0; 140 } 141 142 close(lockfd); 143 if (!Debug) { 144 dup2(nullfd, STDIN_FILENO); 145 dup2(nullfd, STDOUT_FILENO); 146 dup2(nullfd, STDERR_FILENO); 147 } 148 149 if (nullfd > 2) 150 close(nullfd); 151 152 /* Father */ 153 for (i = 1; i <= _NSIG; i++) 154 signal(i, SIG_DFL); 155 156 /* Pass TERM/HUP through to child, and accept CHLD */ 157 signal(SIGTERM, sig_pass_to_chld); 158 signal(SIGHUP, sig_pass_to_chld); 159 signal(SIGCHLD, sig_got_chld); 160 161 setproctitle("[priv]"); 162 close(socks[1]); 163 164 /* Close descriptors that only the unpriv child needs */ 165 for (i = 0; i < nfunix; i++) 166 if (funix[i] != -1) 167 close(funix[i]); 168 if (finet != -1) 169 close(finet); 170 if (fklog != -1) 171 close(fklog); 172 173 /* Save the config file specified by the child process */ 174 if (strlcpy(config_file, conf, sizeof config_file) >= sizeof(config_file)) 175 errx(1, "config_file truncation"); 176 177 if (stat(config_file, &cf_info) < 0) 178 err(1, "stat config file failed"); 179 180 /* Save whether or not the child can have access to gethostbyaddr(3) */ 181 if (numeric > 0) 182 allow_gethostbyaddr = 0; 183 else 184 allow_gethostbyaddr = 1; 185 186 TAILQ_INIT(&lognames); 187 increase_state(STATE_CONFIG); 188 restart = 0; 189 190 while (cur_state < STATE_QUIT) { 191 if (may_read(socks[0], &cmd, sizeof(int))) 192 break; 193 switch (cmd) { 194 case PRIV_OPEN_TTY: 195 dprintf("[priv]: msg PRIV_OPEN_TTY received\n"); 196 /* Expecting: length, path */ 197 must_read(socks[0], &path_len, sizeof(size_t)); 198 if (path_len == 0 || path_len > sizeof(path)) 199 _exit(0); 200 must_read(socks[0], &path, path_len); 201 path[path_len - 1] = '\0'; 202 check_tty_name(path, path_len); 203 fd = open(path, O_WRONLY|O_NONBLOCK, 0); 204 if (fd < 0) 205 warnx("priv_open_tty failed"); 206 send_fd(socks[0], fd); 207 close(fd); 208 break; 209 210 case PRIV_OPEN_LOG: 211 dprintf("[priv]: msg PRIV_OPEN_LOG received\n"); 212 /* Expecting: length, path */ 213 must_read(socks[0], &path_len, sizeof(size_t)); 214 if (path_len == 0 || path_len > sizeof(path)) 215 _exit(0); 216 must_read(socks[0], &path, path_len); 217 path[path_len - 1] = '\0'; 218 check_log_name(path, path_len); 219 fd = open(path, O_WRONLY|O_APPEND|O_NONBLOCK, 0); 220 if (fd < 0) 221 warnx("priv_open_log failed"); 222 send_fd(socks[0], fd); 223 close(fd); 224 break; 225 226 case PRIV_OPEN_UTMP: 227 dprintf("[priv]: msg PRIV_OPEN_UTMP received\n"); 228 fd = open(_PATH_UTMP, O_RDONLY|O_NONBLOCK, 0); 229 if (fd < 0) 230 warnx("priv_open_utmp failed"); 231 send_fd(socks[0], fd); 232 close(fd); 233 break; 234 235 case PRIV_OPEN_CONFIG: 236 dprintf("[priv]: msg PRIV_OPEN_CONFIG received\n"); 237 stat(config_file, &cf_info); 238 fd = open(config_file, O_RDONLY|O_NONBLOCK, 0); 239 if (fd < 0) 240 warnx("priv_open_config failed"); 241 send_fd(socks[0], fd); 242 close(fd); 243 break; 244 245 case PRIV_CONFIG_MODIFIED: 246 dprintf("[priv]: msg PRIV_CONFIG_MODIFIED received\n"); 247 if (stat(config_file, &cf_stat) < 0 || 248 timespeccmp(&cf_info.st_mtimespec, 249 &cf_stat.st_mtimespec, <) || 250 cf_info.st_size != cf_stat.st_size) { 251 dprintf("config file modified: restarting\n"); 252 restart = result = 1; 253 must_write(socks[0], &result, sizeof(int)); 254 } else { 255 result = 0; 256 must_write(socks[0], &result, sizeof(int)); 257 } 258 break; 259 260 case PRIV_DONE_CONFIG_PARSE: 261 dprintf("[priv]: msg PRIV_DONE_CONFIG_PARSE received\n"); 262 increase_state(STATE_RUNNING); 263 break; 264 265 case PRIV_GETHOSTBYNAME: 266 dprintf("[priv]: msg PRIV_GETHOSTBYNAME received\n"); 267 /* Expecting: length, hostname */ 268 must_read(socks[0], &hostname_len, sizeof(size_t)); 269 if (hostname_len == 0 || hostname_len > sizeof(hostname)) 270 _exit(0); 271 must_read(socks[0], &hostname, hostname_len); 272 hostname[hostname_len - 1] = '\0'; 273 hp = gethostbyname(hostname); 274 if (hp == NULL) { 275 addr_len = 0; 276 must_write(socks[0], &addr_len, sizeof(int)); 277 } else { 278 must_write(socks[0], &hp->h_length, sizeof(int)); 279 must_write(socks[0], hp->h_addr, hp->h_length); 280 } 281 break; 282 283 case PRIV_GETHOSTBYADDR: 284 dprintf("[priv]: msg PRIV_GETHOSTBYADDR received\n"); 285 if (!allow_gethostbyaddr) 286 errx(1, "rejected attempt to gethostbyaddr"); 287 /* Expecting: length, address, address family */ 288 must_read(socks[0], &addr_len, sizeof(int)); 289 if (addr_len <= 0 || addr_len > sizeof(hostname)) 290 _exit(0); 291 must_read(socks[0], hostname, addr_len); 292 must_read(socks[0], &addr_af, sizeof(int)); 293 hp = gethostbyaddr(hostname, addr_len, addr_af); 294 if (hp == NULL) { 295 addr_len = 0; 296 must_write(socks[0], &addr_len, sizeof(int)); 297 } else { 298 addr_len = strlen(hp->h_name) + 1; 299 must_write(socks[0], &addr_len, sizeof(int)); 300 must_write(socks[0], hp->h_name, addr_len); 301 } 302 break; 303 default: 304 errx(1, "unknown command %d", cmd); 305 break; 306 } 307 } 308 309 /* Unlink any domain sockets that have been opened */ 310 for (i = 0; i < nfunix; i++) 311 if (funixn[i] && funix[i] != -1) 312 (void)unlink(funixn[i]); 313 314 if (restart) { 315 int r; 316 317 wait(&r); 318 execvp(argv[0], argv); 319 } 320 _exit(1); 321 } 322 323 /* Check that the terminal device is ok, and if not, rewrite to /dev/null. 324 * Either /dev/console or /dev/tty* are allowed. 325 */ 326 static void 327 check_tty_name(char *tty, size_t ttylen) 328 { 329 const char ttypre[] = "/dev/tty"; 330 char *p; 331 332 /* Any path containing '..' is invalid. */ 333 for (p = tty; *p && (p - tty) < ttylen; p++) 334 if (*p == '.' && *(p + 1) == '.') 335 goto bad_path; 336 337 if (strcmp(_PATH_CONSOLE, tty) && strncmp(tty, ttypre, strlen(ttypre))) 338 goto bad_path; 339 return; 340 341 bad_path: 342 warnx ("%s: invalid attempt to open %s: rewriting to /dev/null", 343 __func__, tty); 344 strlcpy(tty, "/dev/null", ttylen); 345 } 346 347 /* If we are in the initial configuration state, accept a logname and add 348 * it to the list of acceptable logfiles. Otherwise, check against this list 349 * and rewrite to /dev/null if it's a bad path. 350 */ 351 static void 352 check_log_name(char *log, size_t loglen) 353 { 354 struct logname *lg; 355 char *p; 356 357 /* Any path containing '..' is invalid. */ 358 for (p = log; *p && (p - log) < loglen; p++) 359 if (*p == '.' && *(p + 1) == '.') 360 goto bad_path; 361 362 switch (cur_state) { 363 case STATE_CONFIG: 364 lg = malloc(sizeof(struct logname)); 365 if (!lg) 366 err(1, "check_log_name() malloc"); 367 strlcpy(lg->path, log, MAXPATHLEN); 368 TAILQ_INSERT_TAIL(&lognames, lg, next); 369 break; 370 case STATE_RUNNING: 371 TAILQ_FOREACH(lg, &lognames, next) 372 if (!strcmp(lg->path, log)) 373 return; 374 goto bad_path; 375 break; 376 default: 377 /* Any other state should just refuse the request */ 378 goto bad_path; 379 break; 380 } 381 return; 382 383 bad_path: 384 warnx("%s: invalid attempt to open %s: rewriting to /dev/null", 385 __func__, log); 386 strlcpy(log, "/dev/null", loglen); 387 } 388 389 /* Crank our state into less permissive modes */ 390 static void 391 increase_state(int state) 392 { 393 if (state <= cur_state) 394 errx(1, "attempt to decrease or match current state"); 395 if (state < STATE_INIT || state > STATE_QUIT) 396 errx(1, "attempt to switch to invalid state"); 397 cur_state = state; 398 } 399 400 /* Open console or a terminal device for writing */ 401 int 402 priv_open_tty(const char *tty) 403 { 404 char path[MAXPATHLEN]; 405 int cmd, fd; 406 size_t path_len; 407 408 if (priv_fd < 0) 409 errx(1, "%s: called from privileged portion", __func__); 410 411 if (strlcpy(path, tty, sizeof path) >= sizeof(path)) 412 return -1; 413 path_len = strlen(path) + 1; 414 415 cmd = PRIV_OPEN_TTY; 416 must_write(priv_fd, &cmd, sizeof(int)); 417 must_write(priv_fd, &path_len, sizeof(size_t)); 418 must_write(priv_fd, path, path_len); 419 fd = receive_fd(priv_fd); 420 return fd; 421 } 422 423 /* Open log-file */ 424 int 425 priv_open_log(const char *log) 426 { 427 char path[MAXPATHLEN]; 428 int cmd, fd; 429 size_t path_len; 430 431 if (priv_fd < 0) 432 errx(1, "%s: called from privileged child", __func__); 433 434 if (strlcpy(path, log, sizeof path) >= sizeof(path)) 435 return -1; 436 path_len = strlen(path) + 1; 437 438 cmd = PRIV_OPEN_LOG; 439 must_write(priv_fd, &cmd, sizeof(int)); 440 must_write(priv_fd, &path_len, sizeof(size_t)); 441 must_write(priv_fd, path, path_len); 442 fd = receive_fd(priv_fd); 443 return fd; 444 } 445 446 /* Open utmp for reading */ 447 FILE * 448 priv_open_utmp(void) 449 { 450 int cmd, fd; 451 FILE *fp; 452 453 if (priv_fd < 0) 454 errx(1, "%s: called from privileged portion", __func__); 455 456 cmd = PRIV_OPEN_UTMP; 457 must_write(priv_fd, &cmd, sizeof(int)); 458 fd = receive_fd(priv_fd); 459 if (fd < 0) 460 return NULL; 461 462 fp = fdopen(fd, "r"); 463 if (!fp) { 464 warn("priv_open_utmp: fdopen() failed"); 465 close(fd); 466 return NULL; 467 } 468 469 return fp; 470 } 471 472 /* Open syslog config file for reading */ 473 FILE * 474 priv_open_config(void) 475 { 476 int cmd, fd; 477 FILE *fp; 478 479 if (priv_fd < 0) 480 errx(1, "%s: called from privileged portion", __func__); 481 482 cmd = PRIV_OPEN_CONFIG; 483 must_write(priv_fd, &cmd, sizeof(int)); 484 fd = receive_fd(priv_fd); 485 if (fd < 0) 486 return NULL; 487 488 fp = fdopen(fd, "r"); 489 if (!fp) { 490 warn("priv_open_config: fdopen() failed"); 491 close(fd); 492 return NULL; 493 } 494 495 return fp; 496 } 497 498 /* Ask if config file has been modified since last attempt to read it */ 499 int 500 priv_config_modified() 501 { 502 int cmd, res; 503 504 if (priv_fd < 0) 505 errx(1, "%s: called from privileged portion", __func__); 506 507 cmd = PRIV_CONFIG_MODIFIED; 508 must_write(priv_fd, &cmd, sizeof(int)); 509 510 /* Expect back integer signalling 1 for modification */ 511 must_read(priv_fd, &res, sizeof(int)); 512 return res; 513 } 514 515 /* Child can signal that its initial parsing is done, so that parent 516 * can revoke further logfile permissions. This call only works once. */ 517 void 518 priv_config_parse_done(void) 519 { 520 int cmd; 521 522 if (priv_fd < 0) 523 errx(1, "%s: called from privileged portion", __func__); 524 525 cmd = PRIV_DONE_CONFIG_PARSE; 526 must_write(priv_fd, &cmd, sizeof(int)); 527 } 528 529 /* Resolve hostname into address. Response is placed into addr, and 530 * the length is returned (zero on error) */ 531 int 532 priv_gethostbyname(char *host, char *addr, size_t addr_len) 533 { 534 char hostcpy[MAXHOSTNAMELEN]; 535 int cmd, ret_len; 536 size_t hostname_len; 537 538 if (priv_fd < 0) 539 errx(1, "%s: called from privileged portion", __func__); 540 541 if (strlcpy(hostcpy, host, sizeof hostcpy) >= sizeof(hostcpy)) 542 errx(1, "%s: overflow attempt in hostname", __func__); 543 hostname_len = strlen(hostcpy) + 1; 544 545 cmd = PRIV_GETHOSTBYNAME; 546 must_write(priv_fd, &cmd, sizeof(int)); 547 must_write(priv_fd, &hostname_len, sizeof(size_t)); 548 must_write(priv_fd, hostcpy, hostname_len); 549 550 /* Expect back an integer size, and then a string of that length */ 551 must_read(priv_fd, &ret_len, sizeof(int)); 552 553 /* Check there was no error (indicated by a return of 0) */ 554 if (!ret_len) 555 return 0; 556 557 /* Make sure we aren't overflowing the passed in buffer */ 558 if (addr_len < ret_len) 559 errx(1, "%s: overflow attempt in return", __func__); 560 561 /* Read the resolved address and make sure we got all of it */ 562 must_read(priv_fd, addr, ret_len); 563 return ret_len; 564 } 565 566 /* Reverse address resolution; response is placed into res, and length of 567 * response is returned (zero on error) */ 568 int 569 priv_gethostbyaddr(char *addr, int addr_len, int af, char *res, size_t res_len) 570 { 571 int cmd, ret_len; 572 573 if (priv_fd < 0) 574 errx(1, "%s called from privileged portion", __func__); 575 576 cmd = PRIV_GETHOSTBYADDR; 577 must_write(priv_fd, &cmd, sizeof(int)); 578 must_write(priv_fd, &addr_len, sizeof(int)); 579 must_write(priv_fd, addr, addr_len); 580 must_write(priv_fd, &af, sizeof(int)); 581 582 /* Expect back an integer size, and then a string of that length */ 583 must_read(priv_fd, &ret_len, sizeof(int)); 584 585 /* Check there was no error (indicated by a return of 0) */ 586 if (!ret_len) 587 return 0; 588 589 /* Check we don't overflow the passed in buffer */ 590 if (res_len < ret_len) 591 errx(1, "%s: overflow attempt in return", __func__); 592 593 /* Read the resolved hostname */ 594 must_read(priv_fd, res, ret_len); 595 return ret_len; 596 } 597 598 /* If priv parent gets a TERM or HUP, pass it through to child instead */ 599 static void 600 sig_pass_to_chld(int sig) 601 { 602 if (child_pid != -1) 603 kill(child_pid, sig); 604 } 605 606 /* When child dies, move into the shutdown state */ 607 static void 608 sig_got_chld(int sig) 609 { 610 if (cur_state < STATE_QUIT) 611 cur_state = STATE_QUIT; 612 } 613 614 /* Read all data or return 1 for error. */ 615 static int 616 may_read(int fd, void *buf, size_t n) 617 { 618 char *s = buf; 619 ssize_t res, pos = 0; 620 621 while (n > pos) { 622 res = read(fd, s + pos, n - pos); 623 switch (res) { 624 case -1: 625 if (errno == EINTR || errno == EAGAIN) 626 continue; 627 case 0: 628 return (1); 629 default: 630 pos += res; 631 } 632 } 633 return (0); 634 } 635 636 /* Read data with the assertion that it all must come through, or 637 * else abort the process. Based on atomicio() from openssh. */ 638 static void 639 must_read(int fd, void *buf, size_t n) 640 { 641 char *s = buf; 642 ssize_t res, pos = 0; 643 644 while (n > pos) { 645 res = read(fd, s + pos, n - pos); 646 switch (res) { 647 case -1: 648 if (errno == EINTR || errno == EAGAIN) 649 continue; 650 case 0: 651 _exit(0); 652 default: 653 pos += res; 654 } 655 } 656 } 657 658 /* Write data with the assertion that it all has to be written, or 659 * else abort the process. Based on atomicio() from openssh. */ 660 static void 661 must_write(int fd, void *buf, size_t n) 662 { 663 char *s = buf; 664 ssize_t res, pos = 0; 665 666 while (n > pos) { 667 res = write(fd, s + pos, n - pos); 668 switch (res) { 669 case -1: 670 if (errno == EINTR || errno == EAGAIN) 671 continue; 672 case 0: 673 _exit(0); 674 default: 675 pos += res; 676 } 677 } 678 } 679