1 /* SPDX-License-Identifier: BSD-2-Clause */ 2 /* 3 * Privilege Separation for dhcpcd, privileged actioneer 4 * Copyright (c) 2006-2020 Roy Marples <roy@marples.name> 5 * All rights reserved 6 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <sys/ioctl.h> 30 #include <sys/socket.h> 31 #include <sys/stat.h> 32 #include <sys/time.h> 33 #include <sys/types.h> 34 #include <sys/wait.h> 35 36 #include <assert.h> 37 #include <errno.h> 38 #include <fcntl.h> 39 #include <pwd.h> 40 #include <signal.h> 41 #include <stdlib.h> 42 #include <string.h> 43 #include <unistd.h> 44 45 #include "common.h" 46 #include "dhcpcd.h" 47 #include "eloop.h" 48 #include "if.h" 49 #include "logerr.h" 50 #include "privsep.h" 51 #include "script.h" 52 53 __CTASSERT(sizeof(ioctl_request_t) <= sizeof(unsigned long)); 54 55 struct psr_error 56 { 57 ssize_t psr_result; 58 int psr_errno; 59 char psr_pad[sizeof(ssize_t) - sizeof(int)]; 60 }; 61 62 struct psr_ctx { 63 struct dhcpcd_ctx *psr_ctx; 64 struct psr_error psr_error; 65 }; 66 67 static void 68 ps_root_readerrorsig(__unused int sig, void *arg) 69 { 70 struct dhcpcd_ctx *ctx = arg; 71 72 eloop_exit(ctx->ps_eloop, EXIT_FAILURE); 73 } 74 75 static void 76 ps_root_readerrorcb(void *arg) 77 { 78 struct psr_ctx *psr_ctx = arg; 79 struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx; 80 struct psr_error *psr_error = &psr_ctx->psr_error; 81 ssize_t len; 82 int exit_code = EXIT_FAILURE; 83 84 len = read(ctx->ps_root_fd, psr_error, sizeof(*psr_error)); 85 if (len == 0 || len == -1) { 86 logerr(__func__); 87 psr_error->psr_result = -1; 88 psr_error->psr_errno = errno; 89 } else if ((size_t)len < sizeof(*psr_error)) { 90 logerrx("%s: psr_error truncated", __func__); 91 psr_error->psr_result = -1; 92 psr_error->psr_errno = EINVAL; 93 } else 94 exit_code = EXIT_SUCCESS; 95 96 eloop_exit(ctx->ps_eloop, exit_code); 97 } 98 99 ssize_t 100 ps_root_readerror(struct dhcpcd_ctx *ctx) 101 { 102 struct psr_ctx psr_ctx = { .psr_ctx = ctx }; 103 104 if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd, 105 ps_root_readerrorcb, &psr_ctx) == -1) 106 { 107 logerr(__func__); 108 return -1; 109 } 110 111 eloop_start(ctx->ps_eloop, &ctx->sigset); 112 113 errno = psr_ctx.psr_error.psr_errno; 114 return psr_ctx.psr_error.psr_result; 115 } 116 117 static ssize_t 118 ps_root_writeerror(struct dhcpcd_ctx *ctx, ssize_t result) 119 { 120 struct psr_error psr = { 121 .psr_result = result, 122 .psr_errno = errno, 123 }; 124 125 #ifdef PRIVSEP_DEBUG 126 logdebugx("%s: result %zd errno %d", __func__, result, errno); 127 #endif 128 129 return write(ctx->ps_root_fd, &psr, sizeof(psr)); 130 } 131 132 static ssize_t 133 ps_root_doioctl(unsigned long req, void *data, size_t len) 134 { 135 int s, err; 136 137 s = socket(PF_INET, SOCK_DGRAM, 0); 138 if (s != -1) 139 #ifdef IOCTL_REQUEST_TYPE 140 { 141 ioctl_request_t reqt; 142 143 memcpy(&reqt, &req, sizeof(reqt)); 144 err = ioctl(s, reqt, data, len); 145 } 146 #else 147 err = ioctl(s, req, data, len); 148 #endif 149 else 150 err = -1; 151 if (s != -1) 152 close(s); 153 return err; 154 } 155 156 static ssize_t 157 ps_root_run_script(struct dhcpcd_ctx *ctx, const void *data, size_t len) 158 { 159 const char *envbuf = data; 160 char * const argv[] = { UNCONST(data), NULL }; 161 pid_t pid; 162 int status; 163 164 #ifdef PRIVSEP_DEBUG 165 logdebugx("%s: IN %zu", __func__, len); 166 #endif 167 168 if (len == 0) 169 return 0; 170 171 /* Script is the first one, find the environment buffer. */ 172 while (*envbuf != '\0') { 173 if (len == 0) 174 return EINVAL; 175 envbuf++; 176 len--; 177 } 178 179 if (len != 0) { 180 envbuf++; 181 len--; 182 } 183 184 #ifdef PRIVSEP_DEBUG 185 logdebugx("%s: run script: %s", __func__, argv[0]); 186 #endif 187 188 if (script_buftoenv(ctx, UNCONST(envbuf), len) == NULL) 189 return -1; 190 191 pid = script_exec(argv, ctx->script_env); 192 if (pid == -1) 193 return -1; 194 /* Wait for the script to finish */ 195 while (waitpid(pid, &status, 0) == -1) { 196 if (errno != EINTR) { 197 logerr(__func__); 198 status = 0; 199 break; 200 } 201 } 202 return status; 203 } 204 205 #if defined(__linux__) && !defined(st_mtimespec) 206 #define st_atimespec st_atim 207 #define st_mtimespec st_mtim 208 #endif 209 ssize_t 210 ps_root_docopychroot(struct dhcpcd_ctx *ctx, const char *file) 211 { 212 213 char path[PATH_MAX], buf[BUFSIZ], *slash; 214 struct stat from_sb, to_sb; 215 int from_fd, to_fd; 216 ssize_t rcount, wcount, total; 217 #if defined(BSD) || defined(__linux__) 218 struct timespec ts[2]; 219 #else 220 struct timeval tv[2]; 221 #endif 222 223 if (snprintf(path, sizeof(path), "%s/%s", 224 ctx->ps_user->pw_dir, file) == -1) 225 return -1; 226 if (stat(file, &from_sb) == -1) 227 return -1; 228 if (stat(path, &to_sb) == 0) { 229 #if defined(BSD) || defined(__linux__) 230 if (from_sb.st_mtimespec.tv_sec == to_sb.st_mtimespec.tv_sec && 231 from_sb.st_mtimespec.tv_nsec == to_sb.st_mtimespec.tv_nsec) 232 return 0; 233 #else 234 if (from_sb.st_mtime == to_sb.st_mtime) 235 return 0; 236 #endif 237 } else { 238 /* Ensure directory exists */ 239 slash = strrchr(path, '/'); 240 if (slash != NULL) { 241 *slash = '\0'; 242 ps_mkdir(path); 243 *slash = '/'; 244 } 245 } 246 247 if (unlink(path) == -1 && errno != ENOENT) 248 return -1; 249 if ((from_fd = open(file, O_RDONLY, 0)) == -1) 250 return -1; 251 if ((to_fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0555)) == -1) 252 return -1; 253 254 total = 0; 255 while ((rcount = read(from_fd, buf, sizeof(buf))) > 0) { 256 wcount = write(to_fd, buf, (size_t)rcount); 257 if (wcount != rcount) { 258 total = -1; 259 break; 260 } 261 total += wcount; 262 } 263 264 #if defined(BSD) || defined(__linux__) 265 ts[0] = from_sb.st_atimespec; 266 ts[1] = from_sb.st_mtimespec; 267 if (futimens(to_fd, ts) == -1) 268 total = -1; 269 #else 270 tv[0].tv_sec = from_sb.st_atime; 271 tv[0].tv_usec = 0; 272 tv[1].tv_sec = from_sb.st_mtime; 273 tv[1].tv_usec = 0; 274 if (futimes(to_fd, tv) == -1) 275 total = -1; 276 #endif 277 close(from_fd); 278 close(to_fd); 279 280 return total; 281 } 282 283 static ssize_t 284 ps_root_dofileop(struct dhcpcd_ctx *ctx, void *data, size_t len, uint8_t op) 285 { 286 char *path = data; 287 size_t plen; 288 289 if (len < sizeof(plen)) { 290 errno = EINVAL; 291 return -1; 292 } 293 294 memcpy(&plen, path, sizeof(plen)); 295 path += sizeof(plen); 296 if (sizeof(plen) + plen > len) { 297 errno = EINVAL; 298 return -1; 299 } 300 301 switch(op) { 302 case PS_COPY: 303 return ps_root_docopychroot(ctx, path); 304 case PS_UNLINK: 305 return (ssize_t)unlink(path); 306 default: 307 errno = ENOTSUP; 308 return -1; 309 } 310 } 311 312 static ssize_t 313 ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) 314 { 315 struct dhcpcd_ctx *ctx = arg; 316 uint8_t cmd; 317 struct ps_process *psp; 318 struct iovec *iov = msg->msg_iov; 319 void *data = iov->iov_base; 320 size_t len = iov->iov_len; 321 ssize_t err; 322 323 cmd = (uint8_t)(psm->ps_cmd & ~(PS_START | PS_STOP | PS_DELETE)); 324 psp = ps_findprocess(ctx, &psm->ps_id); 325 326 #ifdef PRIVSEP_DEBUG 327 logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp); 328 #endif 329 330 if ((!(psm->ps_cmd & PS_START) || cmd == PS_BPF_ARP_ADDR) && 331 psp != NULL) 332 { 333 if (psm->ps_cmd & PS_STOP) { 334 int ret = ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd); 335 336 ps_freeprocess(psp); 337 return ret; 338 } 339 return ps_sendpsmmsg(ctx, psp->psp_fd, psm, msg); 340 } 341 342 if (psm->ps_cmd & (PS_STOP | PS_DELETE) && psp == NULL) 343 return 0; 344 345 /* All these should just be PS_START */ 346 switch (cmd) { 347 #ifdef INET 348 #ifdef ARP 349 case PS_BPF_ARP: /* FALLTHROUGH */ 350 #endif 351 case PS_BPF_BOOTP: 352 return ps_bpf_cmd(ctx, psm, msg); 353 #endif 354 #ifdef INET 355 case PS_BOOTP: 356 return ps_inet_cmd(ctx, psm, msg); 357 #endif 358 #ifdef INET6 359 #ifdef DHCP6 360 case PS_DHCP6: /* FALLTHROUGH */ 361 #endif 362 case PS_ND: 363 return ps_inet_cmd(ctx, psm, msg); 364 #endif 365 default: 366 break; 367 } 368 369 assert(msg->msg_iovlen == 1); 370 371 /* Reset errno */ 372 errno = 0; 373 374 switch (psm->ps_cmd) { 375 case PS_IOCTL: 376 err = ps_root_doioctl(psm->ps_flags, data, len); 377 break; 378 case PS_SCRIPT: 379 err = ps_root_run_script(ctx, data, len); 380 break; 381 case PS_COPY: /* FALLTHROUGH */ 382 case PS_UNLINK: 383 err = ps_root_dofileop(ctx, data, len, psm->ps_cmd); 384 break; 385 default: 386 err = ps_root_os(psm, msg); 387 break; 388 } 389 390 return ps_root_writeerror(ctx, err); 391 } 392 393 /* Receive from state engine, do an action. */ 394 static void 395 ps_root_recvmsg(void *arg) 396 { 397 struct dhcpcd_ctx *ctx = arg; 398 399 if (ps_recvpsmsg(ctx, ctx->ps_root_fd, ps_root_recvmsgcb, ctx) == -1 && 400 errno != ECONNRESET) 401 logerr(__func__); 402 } 403 404 static int 405 ps_root_startcb(void *arg) 406 { 407 struct dhcpcd_ctx *ctx = arg; 408 409 setproctitle("[privileged actioneer]"); 410 ctx->ps_root_pid = getpid(); 411 return 0; 412 } 413 414 static void 415 ps_root_signalcb(int sig, void *arg) 416 { 417 struct dhcpcd_ctx *ctx = arg; 418 419 /* Ignore SIGINT, respect PS_STOP command or SIGTERM. */ 420 if (sig == SIGINT) 421 return; 422 423 logerrx("process %d unexpectedly terminating on signal %d", 424 getpid(), sig); 425 if (ctx->ps_root_pid == getpid()) { 426 shutdown(ctx->ps_root_fd, SHUT_RDWR); 427 shutdown(ctx->ps_data_fd, SHUT_RDWR); 428 } 429 eloop_exit(ctx->eloop, sig == SIGTERM ? EXIT_SUCCESS : EXIT_FAILURE); 430 } 431 432 static ssize_t 433 ps_root_dispatchcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) 434 { 435 struct dhcpcd_ctx *ctx = arg; 436 ssize_t err; 437 438 #ifdef INET 439 err = ps_bpf_dispatch(ctx, psm, msg); 440 if (err == -1 && errno == ENOTSUP) 441 #endif 442 err = ps_inet_dispatch(ctx, psm, msg); 443 return err; 444 } 445 446 static void 447 ps_root_dispatch(void *arg) 448 { 449 struct dhcpcd_ctx *ctx = arg; 450 451 if (ps_recvpsmsg(ctx, ctx->ps_data_fd, ps_root_dispatchcb, ctx) == -1) 452 logerr(__func__); 453 } 454 455 pid_t 456 ps_root_start(struct dhcpcd_ctx *ctx) 457 { 458 int fd[2]; 459 pid_t pid; 460 461 #define SOCK_CXNB SOCK_CLOEXEC | SOCK_NONBLOCK 462 if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fd) == -1) 463 return -1; 464 465 pid = ps_dostart(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd, 466 ps_root_recvmsg, NULL, ctx, 467 ps_root_startcb, ps_root_signalcb, 0); 468 469 if (pid == 0) { 470 ctx->ps_data_fd = fd[1]; 471 close(fd[0]); 472 return 0; 473 } else if (pid == -1) 474 return -1; 475 476 ctx->ps_data_fd = fd[0]; 477 close(fd[1]); 478 if (eloop_event_add(ctx->eloop, ctx->ps_data_fd, 479 ps_root_dispatch, ctx) == -1) 480 logerr(__func__); 481 482 if ((ctx->ps_eloop = eloop_new()) == NULL) { 483 logerr(__func__); 484 return -1; 485 } 486 487 if (eloop_signal_set_cb(ctx->ps_eloop, 488 dhcpcd_signals, dhcpcd_signals_len, 489 ps_root_readerrorsig, ctx) == -1) 490 { 491 logerr(__func__); 492 return -1; 493 } 494 return pid; 495 } 496 497 int 498 ps_root_stop(struct dhcpcd_ctx *ctx) 499 { 500 501 return ps_dostop(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd); 502 } 503 504 ssize_t 505 ps_root_script(const struct interface *ifp, const void *data, size_t len) 506 { 507 char buf[PS_BUFLEN], *p = buf; 508 size_t blen = PS_BUFLEN, slen = strlen(ifp->options->script) + 1; 509 510 #ifdef PRIVSEP_DEBUG 511 logdebugx("%s: sending script: %zu %s len %zu", 512 __func__, slen, ifp->options->script, len); 513 #endif 514 515 if (slen > blen) { 516 errno = ENOBUFS; 517 return -1; 518 } 519 memcpy(p, ifp->options->script, slen); 520 p += slen; 521 blen -= slen; 522 523 if (len > blen) { 524 errno = ENOBUFS; 525 return -1; 526 } 527 memcpy(p, data, len); 528 529 #ifdef PRIVSEP_DEBUG 530 logdebugx("%s: sending script data: %zu", __func__, slen + len); 531 #endif 532 533 if (ps_sendcmd(ifp->ctx, ifp->ctx->ps_root_fd, PS_SCRIPT, 0, 534 buf, slen + len) == -1) 535 return -1; 536 537 return ps_root_readerror(ifp->ctx); 538 } 539 540 ssize_t 541 ps_root_ioctl(struct dhcpcd_ctx *ctx, ioctl_request_t req, void *data, 542 size_t len) 543 { 544 #ifdef IOCTL_REQUEST_TYPE 545 unsigned long ulreq = 0; 546 547 memcpy(&ulreq, &req, sizeof(req)); 548 if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, ulreq, data, len) == -1) 549 return -1; 550 #else 551 if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, req, data, len) == -1) 552 return -1; 553 #endif 554 return ps_root_readerror(ctx); 555 } 556 557 static ssize_t 558 ps_root_fileop(struct dhcpcd_ctx *ctx, const char *path, uint8_t op) 559 { 560 char buf[PATH_MAX], *p = buf; 561 size_t plen = strlen(path) + 1; 562 size_t len = sizeof(plen) + plen; 563 564 if (len > sizeof(buf)) { 565 errno = ENOBUFS; 566 return -1; 567 } 568 569 memcpy(p, &plen, sizeof(plen)); 570 p += sizeof(plen); 571 memcpy(p, path, plen); 572 573 if (ps_sendcmd(ctx, ctx->ps_root_fd, op, 0, buf, len) == -1) 574 return -1; 575 return ps_root_readerror(ctx); 576 } 577 578 579 ssize_t 580 ps_root_copychroot(struct dhcpcd_ctx *ctx, const char *path) 581 { 582 583 return ps_root_fileop(ctx, path, PS_COPY); 584 } 585 586 ssize_t 587 ps_root_unlink(struct dhcpcd_ctx *ctx, const char *path) 588 { 589 590 return ps_root_fileop(ctx, path, PS_UNLINK); 591 } 592