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