Import dhcpcd-10.0.2 with the following changes:
[dragonfly.git] / contrib / dhcpcd / src / privsep.c
blobb11c0351c25e2189ff042bc7b5b5a923cc754e3c
1 /* SPDX-License-Identifier: BSD-2-Clause */
2 /*
3 * Privilege Separation for dhcpcd
4 * Copyright (c) 2006-2023 Roy Marples <roy@marples.name>
5 * All rights reserved
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.
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.
30 * The current design is this:
31 * Spawn a priv process to carry out privileged actions and
32 * spawning unpriv process to initate network connections such as BPF
33 * or address specific listener.
34 * Spawn an unpriv process to send/receive common network data.
35 * Then drop all privs and start running.
36 * Every process aside from the privileged proxy is chrooted.
37 * All privsep processes ignore signals - only the manager process accepts them.
39 * dhcpcd will maintain the config file in the chroot, no need to handle
40 * this in a script or something.
43 #include <sys/resource.h>
44 #include <sys/socket.h>
45 #include <sys/stat.h>
46 #include <sys/types.h>
47 #include <sys/wait.h>
49 #ifdef AF_LINK
50 #include <net/if_dl.h>
51 #endif
53 #include <assert.h>
54 #include <errno.h>
55 #include <fcntl.h>
56 #include <grp.h>
57 #include <paths.h>
58 #include <pwd.h>
59 #include <stddef.h> /* For offsetof, struct padding debug */
60 #include <signal.h>
61 #include <stdlib.h>
62 #include <string.h>
63 #include <unistd.h>
65 #include "arp.h"
66 #include "common.h"
67 #include "control.h"
68 #include "dev.h"
69 #include "dhcp.h"
70 #include "dhcp6.h"
71 #include "eloop.h"
72 #include "ipv6nd.h"
73 #include "logerr.h"
74 #include "privsep.h"
76 #ifdef HAVE_CAPSICUM
77 #include <sys/capsicum.h>
78 #include <sys/procdesc.h>
79 #include <capsicum_helpers.h>
80 #endif
81 #ifdef HAVE_UTIL_H
82 #include <util.h>
83 #endif
85 /* CMSG_ALIGN is a Linux extension */
86 #ifndef CMSG_ALIGN
87 #define CMSG_ALIGN(n) (CMSG_SPACE((n)) - CMSG_SPACE(0))
88 #endif
90 /* Calculate number of padding bytes to achieve 'struct cmsghdr' alignment */
91 #define CALC_CMSG_PADLEN(has_cmsg, pos) \
92 ((has_cmsg) ? (socklen_t)(CMSG_ALIGN((pos)) - (pos)) : 0)
94 int
95 ps_init(struct dhcpcd_ctx *ctx)
97 struct passwd *pw;
98 struct stat st;
100 errno = 0;
101 if ((ctx->ps_user = pw = getpwnam(PRIVSEP_USER)) == NULL) {
102 ctx->options &= ~DHCPCD_PRIVSEP;
103 if (errno == 0) {
104 logerrx("no such user %s", PRIVSEP_USER);
105 /* Just incase logerrx caused an error... */
106 errno = 0;
107 } else
108 logerr("getpwnam");
109 return -1;
112 if (stat(pw->pw_dir, &st) == -1 || !S_ISDIR(st.st_mode)) {
113 ctx->options &= ~DHCPCD_PRIVSEP;
114 logerrx("refusing chroot: %s: %s",
115 PRIVSEP_USER, pw->pw_dir);
116 errno = 0;
117 return -1;
120 ctx->options |= DHCPCD_PRIVSEP;
121 return 0;
124 static int
125 ps_dropprivs(struct dhcpcd_ctx *ctx)
127 struct passwd *pw = ctx->ps_user;
129 if (ctx->options & DHCPCD_LAUNCHER)
130 logdebugx("chrooting as %s to %s", pw->pw_name, pw->pw_dir);
131 if (chroot(pw->pw_dir) == -1 &&
132 (errno != EPERM || ctx->options & DHCPCD_FORKED))
133 logerr("%s: chroot: %s", __func__, pw->pw_dir);
134 if (chdir("/") == -1)
135 logerr("%s: chdir: /", __func__);
137 if ((setgroups(1, &pw->pw_gid) == -1 ||
138 setgid(pw->pw_gid) == -1 ||
139 setuid(pw->pw_uid) == -1) &&
140 (errno != EPERM || ctx->options & DHCPCD_FORKED))
142 logerr("failed to drop privileges");
143 return -1;
146 struct rlimit rzero = { .rlim_cur = 0, .rlim_max = 0 };
148 /* Prohibit new files, sockets, etc */
150 * If poll(2) is called with nfds>RLIMIT_NOFILE then it returns EINVAL.
151 * We don't know the final value of nfds at this point *easily*.
152 * Sadly, this is a POSIX limitation and most platforms adhere to it.
153 * However, some are not that strict and are whitelisted below.
154 * Also, if we're not using poll then we can be restrictive.
156 * For the non whitelisted platforms there should be a sandbox to
157 * fallback to where we don't allow new files, etc:
158 * Linux:seccomp, FreeBSD:capsicum, OpenBSD:pledge
159 * Solaris users are sadly out of luck on both counts.
161 #if defined(__NetBSD__) || defined(__DragonFly__) || \
162 defined(HAVE_KQUEUE) || defined(HAVE_EPOLL)
163 /* The control proxy *does* need to create new fd's via accept(2). */
164 if (ctx->ps_ctl == NULL || ctx->ps_ctl->psp_pid != getpid()) {
165 if (setrlimit(RLIMIT_NOFILE, &rzero) == -1)
166 logerr("setrlimit RLIMIT_NOFILE");
168 #endif
170 #define DHC_NOCHKIO (DHCPCD_STARTED | DHCPCD_DAEMONISE)
171 /* Prohibit writing to files.
172 * Obviously this won't work if we are using a logfile
173 * or redirecting stderr to a file. */
174 if ((ctx->options & DHC_NOCHKIO) == DHC_NOCHKIO ||
175 (ctx->logfile == NULL &&
176 (!ctx->stderr_valid || isatty(STDERR_FILENO) == 1)))
178 if (setrlimit(RLIMIT_FSIZE, &rzero) == -1)
179 logerr("setrlimit RLIMIT_FSIZE");
182 #ifdef RLIMIT_NPROC
183 /* Prohibit forks */
184 if (setrlimit(RLIMIT_NPROC, &rzero) == -1)
185 logerr("setrlimit RLIMIT_NPROC");
186 #endif
188 return 0;
191 static int
192 ps_setbuf0(int fd, int ctl, int minlen)
194 int len;
195 socklen_t slen;
197 slen = sizeof(len);
198 if (getsockopt(fd, SOL_SOCKET, ctl, &len, &slen) == -1)
199 return -1;
201 #ifdef __linux__
202 len /= 2;
203 #endif
204 if (len >= minlen)
205 return 0;
207 return setsockopt(fd, SOL_SOCKET, ctl, &minlen, sizeof(minlen));
210 static int
211 ps_setbuf(int fd)
213 /* Ensure we can receive a fully sized privsep message.
214 * Double the send buffer. */
215 int minlen = (int)sizeof(struct ps_msg);
217 if (ps_setbuf0(fd, SO_RCVBUF, minlen) == -1 ||
218 ps_setbuf0(fd, SO_SNDBUF, minlen * 2) == -1)
220 logerr(__func__);
221 return -1;
223 return 0;
227 ps_setbuf_fdpair(int fd[])
230 if (ps_setbuf(fd[0]) == -1 || ps_setbuf(fd[1]) == -1)
231 return -1;
232 return 0;
235 #ifdef PRIVSEP_RIGHTS
237 ps_rights_limit_ioctl(int fd)
239 cap_rights_t rights;
241 cap_rights_init(&rights, CAP_IOCTL);
242 if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
243 return -1;
244 return 0;
248 ps_rights_limit_fd_fctnl(int fd)
250 cap_rights_t rights;
252 cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT,
253 CAP_ACCEPT, CAP_FCNTL);
254 if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
255 return -1;
256 return 0;
260 ps_rights_limit_fd(int fd)
262 cap_rights_t rights;
264 cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT, CAP_SHUTDOWN);
265 if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
266 return -1;
267 return 0;
271 ps_rights_limit_fd_sockopt(int fd)
273 cap_rights_t rights;
275 cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT,
276 CAP_GETSOCKOPT, CAP_SETSOCKOPT);
277 if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
278 return -1;
279 return 0;
283 ps_rights_limit_fd_rdonly(int fd)
285 cap_rights_t rights;
287 cap_rights_init(&rights, CAP_READ, CAP_EVENT);
288 if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
289 return -1;
290 return 0;
294 ps_rights_limit_fdpair(int fd[])
297 if (ps_rights_limit_fd(fd[0]) == -1 || ps_rights_limit_fd(fd[1]) == -1)
298 return -1;
299 return 0;
302 static int
303 ps_rights_limit_stdio(struct dhcpcd_ctx *ctx)
305 const int iebadf = CAPH_IGNORE_EBADF;
306 int error = 0;
308 if (ctx->stdin_valid &&
309 caph_limit_stream(STDIN_FILENO, CAPH_READ | iebadf) == -1)
310 error = -1;
311 if (ctx->stdout_valid &&
312 caph_limit_stream(STDOUT_FILENO, CAPH_WRITE | iebadf) == -1)
313 error = -1;
314 if (ctx->stderr_valid &&
315 caph_limit_stream(STDERR_FILENO, CAPH_WRITE | iebadf) == -1)
316 error = -1;
318 return error;
320 #endif
322 #ifdef HAVE_CAPSICUM
323 static void
324 ps_processhangup(void *arg, unsigned short events)
326 struct ps_process *psp = arg;
327 struct dhcpcd_ctx *ctx = psp->psp_ctx;
329 if (!(events & ELE_HANGUP))
330 logerrx("%s: unexpected event 0x%04x", __func__, events);
332 logdebugx("%s%s%s exited from PID %d",
333 psp->psp_ifname, psp->psp_ifname[0] != '\0' ? ": " : "",
334 psp->psp_name, psp->psp_pid);
336 ps_freeprocess(psp);
338 if (!(ctx->options & DHCPCD_EXITING))
339 return;
340 if (!(ps_waitforprocs(ctx)))
341 eloop_exit(ctx->ps_eloop, EXIT_SUCCESS);
343 #endif
345 pid_t
346 ps_startprocess(struct ps_process *psp,
347 void (*recv_msg)(void *, unsigned short),
348 void (*recv_unpriv_msg)(void *, unsigned short),
349 int (*callback)(struct ps_process *), void (*signal_cb)(int, void *),
350 unsigned int flags)
352 struct dhcpcd_ctx *ctx = psp->psp_ctx;
353 int fd[2];
354 pid_t pid;
356 if (xsocketpair(AF_UNIX, SOCK_SEQPACKET | SOCK_CXNB, 0, fd) == -1) {
357 logerr("%s: socketpair", __func__);
358 return -1;
360 if (ps_setbuf_fdpair(fd) == -1) {
361 logerr("%s: ps_setbuf_fdpair", __func__);
362 return -1;
364 #ifdef PRIVSEP_RIGHTS
365 if (ps_rights_limit_fdpair(fd) == -1) {
366 logerr("%s: ps_rights_limit_fdpair", __func__);
367 return -1;
369 #endif
371 #ifdef HAVE_CAPSICUM
372 pid = pdfork(&psp->psp_pfd, PD_CLOEXEC);
373 #else
374 pid = fork();
375 #endif
376 switch (pid) {
377 case -1:
378 #ifdef HAVE_CAPSICUM
379 logerr("pdfork");
380 #else
381 logerr("fork");
382 #endif
383 return -1;
384 case 0:
385 psp->psp_pid = getpid();
386 psp->psp_fd = fd[1];
387 close(fd[0]);
388 break;
389 default:
390 psp->psp_pid = pid;
391 psp->psp_fd = fd[0];
392 close(fd[1]);
393 if (recv_unpriv_msg == NULL)
395 else if (eloop_event_add(ctx->eloop, psp->psp_fd, ELE_READ,
396 recv_unpriv_msg, psp) == -1)
398 logerr("%s: eloop_event_add fd %d",
399 __func__, psp->psp_fd);
400 return -1;
402 #ifdef HAVE_CAPSICUM
403 if (eloop_event_add(ctx->eloop, psp->psp_pfd, ELE_HANGUP,
404 ps_processhangup, psp) == -1)
406 logerr("%s: eloop_event_add pfd %d",
407 __func__, psp->psp_pfd);
408 return -1;
410 #endif
411 psp->psp_started = true;
412 return pid;
416 #ifdef PLUGIN_DEV
417 /* If we are not the root process, stop listening to devices. */
418 if (ctx->ps_root != psp)
419 dev_stop(ctx);
420 #endif
422 ctx->options |= DHCPCD_FORKED;
423 if (ctx->ps_log_fd != -1)
424 logsetfd(ctx->ps_log_fd);
425 eloop_clear(ctx->eloop, -1);
426 eloop_forked(ctx->eloop);
427 eloop_signal_set_cb(ctx->eloop,
428 dhcpcd_signals, dhcpcd_signals_len, signal_cb, ctx);
429 /* ctx->sigset aready has the initial sigmask set in main() */
430 if (eloop_signal_mask(ctx->eloop, NULL) == -1) {
431 logerr("%s: eloop_signal_mask", __func__);
432 goto errexit;
435 if (ctx->fork_fd != -1) {
436 /* Already removed from eloop thanks to above clear. */
437 close(ctx->fork_fd);
438 ctx->fork_fd = -1;
441 /* This process has no need of the blocking inner eloop. */
442 if (!(flags & PSF_ELOOP)) {
443 eloop_free(ctx->ps_eloop);
444 ctx->ps_eloop = NULL;
445 } else
446 eloop_forked(ctx->ps_eloop);
448 pidfile_clean();
449 ps_freeprocesses(ctx, psp);
451 if (ctx->ps_root != psp) {
452 ctx->options &= ~DHCPCD_PRIVSEPROOT;
453 ctx->ps_root = NULL;
454 if (ctx->ps_log_root_fd != -1) {
455 /* Already removed from eloop thanks to above clear. */
456 close(ctx->ps_log_root_fd);
457 ctx->ps_log_root_fd = -1;
459 #ifdef PRIVSEP_RIGHTS
460 if (ps_rights_limit_stdio(ctx) == -1) {
461 logerr("ps_rights_limit_stdio");
462 goto errexit;
464 #endif
467 if (ctx->ps_inet != psp)
468 ctx->ps_inet = NULL;
469 if (ctx->ps_ctl != psp)
470 ctx->ps_ctl = NULL;
472 #if 0
473 char buf[1024];
474 errno = 0;
475 ssize_t xx = recv(psp->psp_fd, buf, sizeof(buf), MSG_PEEK);
476 logerr("pid %d test fd %d recv peek %zd", getpid(), psp->psp_fd, xx);
477 #endif
479 if (eloop_event_add(ctx->eloop, psp->psp_fd, ELE_READ,
480 recv_msg, psp) == -1)
482 logerr("%d %s: eloop_event_add XX fd %d", getpid(), __func__, psp->psp_fd);
483 goto errexit;
486 if (callback(psp) == -1)
487 goto errexit;
489 if (flags & PSF_DROPPRIVS)
490 ps_dropprivs(ctx);
492 psp->psp_started = true;
493 return 0;
495 errexit:
496 if (psp->psp_fd != -1) {
497 close(psp->psp_fd);
498 psp->psp_fd = -1;
500 eloop_exit(ctx->eloop, EXIT_FAILURE);
501 return -1;
504 void
505 ps_process_timeout(void *arg)
507 struct dhcpcd_ctx *ctx = arg;
509 logerrx("%s: timed out", __func__);
510 eloop_exit(ctx->eloop, EXIT_FAILURE);
514 ps_stopprocess(struct ps_process *psp)
516 int err = 0;
518 if (psp == NULL)
519 return 0;
521 psp->psp_started = false;
523 #ifdef PRIVSEP_DEBUG
524 logdebugx("%s: me=%d pid=%d fd=%d %s", __func__,
525 getpid(), psp->psp_pid, psp->psp_fd, psp->psp_name);
526 #endif
528 if (psp->psp_fd != -1) {
529 eloop_event_delete(psp->psp_ctx->eloop, psp->psp_fd);
530 #if 0
531 if (ps_sendcmd(psp->psp_ctx, psp->psp_fd, PS_STOP, 0,
532 NULL, 0) == -1)
534 logerr("%d %d %s %s", getpid(), psp->psp_pid, psp->psp_name, __func__);
535 err = -1;
537 shutdown(psp->psp_fd, SHUT_WR);
538 #else
539 if (shutdown(psp->psp_fd, SHUT_WR) == -1) {
540 logerr(__func__);
541 err = -1;
543 #endif
544 psp->psp_fd = -1;
547 /* Don't wait for the process as it may not respond to the shutdown
548 * request. We'll reap the process on receipt of SIGCHLD. */
549 return err;
553 ps_start(struct dhcpcd_ctx *ctx)
555 pid_t pid;
557 TAILQ_INIT(&ctx->ps_processes);
559 /* We need an inner eloop to block with. */
560 if ((ctx->ps_eloop = eloop_new()) == NULL)
561 return -1;
562 eloop_signal_set_cb(ctx->ps_eloop,
563 dhcpcd_signals, dhcpcd_signals_len,
564 dhcpcd_signal_cb, ctx);
566 switch (pid = ps_root_start(ctx)) {
567 case -1:
568 logerr("ps_root_start");
569 return -1;
570 case 0:
571 return 0;
572 default:
573 logdebugx("spawned privileged proxy on PID %d", pid);
576 /* No point in spawning the generic network listener if we're
577 * not going to use it. */
578 if (!ps_inet_canstart(ctx))
579 goto started_net;
581 switch (pid = ps_inet_start(ctx)) {
582 case -1:
583 return -1;
584 case 0:
585 return 0;
586 default:
587 logdebugx("spawned network proxy on PID %d", pid);
590 started_net:
591 if (!(ctx->options & DHCPCD_TEST)) {
592 switch (pid = ps_ctl_start(ctx)) {
593 case -1:
594 return -1;
595 case 0:
596 return 0;
597 default:
598 logdebugx("spawned controller proxy on PID %d", pid);
602 #ifdef ARC4RANDOM_H
603 /* Seed the random number generator early incase it needs /dev/urandom
604 * which won't be available in the chroot. */
605 arc4random();
606 #endif
608 return 1;
612 ps_entersandbox(const char *_pledge, const char **sandbox)
615 #if !defined(HAVE_PLEDGE)
616 UNUSED(_pledge);
617 #endif
619 #if defined(HAVE_CAPSICUM)
620 if (sandbox != NULL)
621 *sandbox = "capsicum";
622 return cap_enter();
623 #elif defined(HAVE_PLEDGE)
624 if (sandbox != NULL)
625 *sandbox = "pledge";
626 return pledge(_pledge, NULL);
627 #elif defined(HAVE_SECCOMP)
628 if (sandbox != NULL)
629 *sandbox = "seccomp";
630 return ps_seccomp_enter();
631 #else
632 if (sandbox != NULL)
633 *sandbox = "posix resource limited";
634 return 0;
635 #endif
639 ps_managersandbox(struct dhcpcd_ctx *ctx, const char *_pledge)
641 const char *sandbox = NULL;
642 bool forked;
643 int dropped;
645 forked = ctx->options & DHCPCD_FORKED;
646 ctx->options &= ~DHCPCD_FORKED;
647 dropped = ps_dropprivs(ctx);
648 if (forked)
649 ctx->options |= DHCPCD_FORKED;
652 * If we don't have a root process, we cannot use syslog.
653 * If it cannot be opened before chrooting then syslog(3) will fail.
654 * openlog(3) does not return an error which doubly sucks.
656 if (ctx->ps_root == NULL) {
657 unsigned int logopts = loggetopts();
659 logopts &= ~LOGERR_LOG;
660 logsetopts(logopts);
663 if (dropped == -1) {
664 logerr("%s: ps_dropprivs", __func__);
665 return -1;
668 #ifdef PRIVSEP_RIGHTS
669 if ((ctx->pf_inet_fd != -1 &&
670 ps_rights_limit_ioctl(ctx->pf_inet_fd) == -1) ||
671 ps_rights_limit_stdio(ctx) == -1)
673 logerr("%s: cap_rights_limit", __func__);
674 return -1;
676 #endif
678 if (_pledge == NULL)
679 _pledge = "stdio";
680 if (ps_entersandbox(_pledge, &sandbox) == -1) {
681 if (errno == ENOSYS) {
682 if (sandbox != NULL)
683 logwarnx("sandbox unavailable: %s", sandbox);
684 return 0;
686 logerr("%s: %s", __func__, sandbox);
687 return -1;
688 } else if (ctx->options & DHCPCD_LAUNCHER ||
689 ((!(ctx->options & DHCPCD_DAEMONISE)) &&
690 ctx->options & DHCPCD_MANAGER))
691 logdebugx("sandbox: %s", sandbox);
692 return 0;
696 ps_stop(struct dhcpcd_ctx *ctx)
698 int r, ret = 0;
700 if (!(ctx->options & DHCPCD_PRIVSEP) ||
701 ctx->options & DHCPCD_FORKED ||
702 ctx->eloop == NULL)
703 return 0;
705 if (ctx->ps_ctl != NULL) {
706 r = ps_ctl_stop(ctx);
707 if (r != 0)
708 ret = r;
711 if (ctx->ps_inet != NULL) {
712 r = ps_inet_stop(ctx);
713 if (r != 0)
714 ret = r;
717 if (ctx->ps_root != NULL) {
718 if (ps_root_stopprocesses(ctx) == -1)
719 ret = -1;
722 return ret;
725 bool
726 ps_waitforprocs(struct dhcpcd_ctx *ctx)
728 struct ps_process *psp = TAILQ_FIRST(&ctx->ps_processes);
730 if (psp == NULL)
731 return false;
733 /* Different processes */
734 if (psp != TAILQ_LAST(&ctx->ps_processes, ps_process_head))
735 return true;
737 return !psp->psp_started;
741 ps_stopwait(struct dhcpcd_ctx *ctx)
743 int error = EXIT_SUCCESS;
745 if (ctx->ps_eloop == NULL || !ps_waitforprocs(ctx))
746 return 0;
748 ctx->options |= DHCPCD_EXITING;
749 if (eloop_timeout_add_sec(ctx->ps_eloop, PS_PROCESS_TIMEOUT,
750 ps_process_timeout, ctx) == -1)
751 logerr("%s: eloop_timeout_add_sec", __func__);
752 eloop_enter(ctx->ps_eloop);
754 #ifdef HAVE_CAPSICUM
755 struct ps_process *psp;
757 TAILQ_FOREACH(psp, &ctx->ps_processes, next) {
758 if (psp->psp_pfd == -1)
759 continue;
760 if (eloop_event_add(ctx->ps_eloop, psp->psp_pfd,
761 ELE_HANGUP, ps_processhangup, psp) == -1)
762 logerr("%s: eloop_event_add pfd %d",
763 __func__, psp->psp_pfd);
765 #endif
767 error = eloop_start(ctx->ps_eloop, &ctx->sigset);
768 if (error != EXIT_SUCCESS)
769 logerr("%s: eloop_start", __func__);
771 eloop_timeout_delete(ctx->ps_eloop, ps_process_timeout, ctx);
773 return error;
776 void
777 ps_freeprocess(struct ps_process *psp)
779 struct dhcpcd_ctx *ctx = psp->psp_ctx;
781 TAILQ_REMOVE(&ctx->ps_processes, psp, next);
783 if (psp->psp_fd != -1) {
784 eloop_event_delete(ctx->eloop, psp->psp_fd);
785 close(psp->psp_fd);
787 if (psp->psp_work_fd != -1) {
788 eloop_event_delete(ctx->eloop, psp->psp_work_fd);
789 close(psp->psp_work_fd);
791 #ifdef HAVE_CAPSICUM
792 if (psp->psp_pfd != -1) {
793 eloop_event_delete(ctx->eloop, psp->psp_pfd);
794 if (ctx->ps_eloop != NULL)
795 eloop_event_delete(ctx->ps_eloop, psp->psp_pfd);
796 close(psp->psp_pfd);
798 #endif
799 if (ctx->ps_root == psp)
800 ctx->ps_root = NULL;
801 if (ctx->ps_inet == psp)
802 ctx->ps_inet = NULL;
803 if (ctx->ps_ctl == psp)
804 ctx->ps_ctl = NULL;
805 #ifdef INET
806 if (psp->psp_bpf != NULL)
807 bpf_close(psp->psp_bpf);
808 #endif
809 free(psp);
812 static void
813 ps_free(struct dhcpcd_ctx *ctx)
815 struct ps_process *ppsp, *psp;
816 bool stop;
818 if (ctx->ps_root != NULL)
819 ppsp = ctx->ps_root;
820 else if (ctx->ps_ctl != NULL)
821 ppsp = ctx->ps_ctl;
822 else
823 ppsp = NULL;
824 if (ppsp != NULL)
825 stop = ppsp->psp_pid == getpid();
826 else
827 stop = false;
829 while ((psp = TAILQ_FIRST(&ctx->ps_processes)) != NULL) {
830 if (stop && psp != ppsp)
831 ps_stopprocess(psp);
832 ps_freeprocess(psp);
837 ps_unrollmsg(struct msghdr *msg, struct ps_msghdr *psm,
838 const void *data, size_t len)
840 uint8_t *datap, *namep, *controlp;
841 socklen_t cmsg_padlen =
842 CALC_CMSG_PADLEN(psm->ps_controllen, psm->ps_namelen);
844 namep = UNCONST(data);
845 controlp = namep + psm->ps_namelen + cmsg_padlen;
846 datap = controlp + psm->ps_controllen;
848 if (psm->ps_namelen != 0) {
849 if (psm->ps_namelen > len) {
850 errno = EINVAL;
851 return -1;
853 msg->msg_name = namep;
854 len -= psm->ps_namelen;
855 } else
856 msg->msg_name = NULL;
857 msg->msg_namelen = psm->ps_namelen;
859 if (psm->ps_controllen != 0) {
860 if (psm->ps_controllen > len) {
861 errno = EINVAL;
862 return -1;
864 msg->msg_control = controlp;
865 len -= psm->ps_controllen + cmsg_padlen;
866 } else
867 msg->msg_control = NULL;
868 msg->msg_controllen = psm->ps_controllen;
870 if (len != 0) {
871 msg->msg_iovlen = 1;
872 msg->msg_iov[0].iov_base = datap;
873 msg->msg_iov[0].iov_len = len;
874 } else {
875 msg->msg_iovlen = 0;
876 msg->msg_iov[0].iov_base = NULL;
877 msg->msg_iov[0].iov_len = 0;
879 return 0;
882 ssize_t
883 ps_sendpsmmsg(struct dhcpcd_ctx *ctx, int fd,
884 struct ps_msghdr *psm, const struct msghdr *msg)
886 long padding[1] = { 0 };
887 struct iovec iov[] = {
888 { .iov_base = UNCONST(psm), .iov_len = sizeof(*psm) },
889 { .iov_base = NULL, }, /* name */
890 { .iov_base = NULL, }, /* control padding */
891 { .iov_base = NULL, }, /* control */
892 { .iov_base = NULL, }, /* payload 1 */
893 { .iov_base = NULL, }, /* payload 2 */
894 { .iov_base = NULL, }, /* payload 3 */
896 int iovlen;
897 ssize_t len;
899 if (msg != NULL) {
900 struct iovec *iovp = &iov[1];
901 int i;
902 socklen_t cmsg_padlen;
904 psm->ps_namelen = msg->msg_namelen;
905 psm->ps_controllen = (socklen_t)msg->msg_controllen;
907 iovp->iov_base = msg->msg_name;
908 iovp->iov_len = msg->msg_namelen;
909 iovp++;
911 cmsg_padlen =
912 CALC_CMSG_PADLEN(msg->msg_controllen, msg->msg_namelen);
913 assert(cmsg_padlen <= sizeof(padding));
914 iovp->iov_len = cmsg_padlen;
915 iovp->iov_base = cmsg_padlen != 0 ? padding : NULL;
916 iovp++;
918 iovp->iov_base = msg->msg_control;
919 iovp->iov_len = msg->msg_controllen;
920 iovlen = 4;
922 for (i = 0; i < (int)msg->msg_iovlen; i++) {
923 if ((size_t)(iovlen + i) > __arraycount(iov)) {
924 errno = ENOBUFS;
925 return -1;
927 iovp++;
928 iovp->iov_base = msg->msg_iov[i].iov_base;
929 iovp->iov_len = msg->msg_iov[i].iov_len;
931 iovlen += i;
932 } else
933 iovlen = 1;
935 len = writev(fd, iov, iovlen);
936 if (len == -1) {
937 if (ctx->options & DHCPCD_FORKED &&
938 !(ctx->options & DHCPCD_PRIVSEPROOT))
939 eloop_exit(ctx->eloop, EXIT_FAILURE);
941 return len;
944 ssize_t
945 ps_sendpsmdata(struct dhcpcd_ctx *ctx, int fd,
946 struct ps_msghdr *psm, const void *data, size_t len)
948 struct iovec iov[] = {
949 { .iov_base = UNCONST(data), .iov_len = len },
951 struct msghdr msg = {
952 .msg_iov = iov, .msg_iovlen = 1,
955 return ps_sendpsmmsg(ctx, fd, psm, &msg);
959 ssize_t
960 ps_sendmsg(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags,
961 const struct msghdr *msg)
963 struct ps_msghdr psm = {
964 .ps_cmd = cmd,
965 .ps_flags = flags,
966 .ps_namelen = msg->msg_namelen,
967 .ps_controllen = (socklen_t)msg->msg_controllen,
969 size_t i;
971 for (i = 0; i < (size_t)msg->msg_iovlen; i++)
972 psm.ps_datalen += msg->msg_iov[i].iov_len;
974 #if 0 /* For debugging structure padding. */
975 logerrx("psa.family %lu %zu", offsetof(struct ps_addr, psa_family), sizeof(psm.ps_id.psi_addr.psa_family));
976 logerrx("psa.pad %lu %zu", offsetof(struct ps_addr, psa_pad), sizeof(psm.ps_id.psi_addr.psa_pad));
977 logerrx("psa.psa_u %lu %zu", offsetof(struct ps_addr, psa_u), sizeof(psm.ps_id.psi_addr.psa_u));
978 logerrx("psa %zu", sizeof(psm.ps_id.psi_addr));
980 logerrx("psi.addr %lu %zu", offsetof(struct ps_id, psi_addr), sizeof(psm.ps_id.psi_addr));
981 logerrx("psi.index %lu %zu", offsetof(struct ps_id, psi_ifindex), sizeof(psm.ps_id.psi_ifindex));
982 logerrx("psi.cmd %lu %zu", offsetof(struct ps_id, psi_cmd), sizeof(psm.ps_id.psi_cmd));
983 logerrx("psi.pad %lu %zu", offsetof(struct ps_id, psi_pad), sizeof(psm.ps_id.psi_pad));
984 logerrx("psi %zu", sizeof(struct ps_id));
986 logerrx("ps_cmd %lu", offsetof(struct ps_msghdr, ps_cmd));
987 logerrx("ps_pad %lu %zu", offsetof(struct ps_msghdr, ps_pad), sizeof(psm.ps_pad));
988 logerrx("ps_flags %lu %zu", offsetof(struct ps_msghdr, ps_flags), sizeof(psm.ps_flags));
990 logerrx("ps_id %lu %zu", offsetof(struct ps_msghdr, ps_id), sizeof(psm.ps_id));
992 logerrx("ps_namelen %lu %zu", offsetof(struct ps_msghdr, ps_namelen), sizeof(psm.ps_namelen));
993 logerrx("ps_controllen %lu %zu", offsetof(struct ps_msghdr, ps_controllen), sizeof(psm.ps_controllen));
994 logerrx("ps_pad2 %lu %zu", offsetof(struct ps_msghdr, ps_pad2), sizeof(psm.ps_pad2));
995 logerrx("ps_datalen %lu %zu", offsetof(struct ps_msghdr, ps_datalen), sizeof(psm.ps_datalen));
996 logerrx("psm %zu", sizeof(psm));
997 #endif
999 return ps_sendpsmmsg(ctx, fd, &psm, msg);
1002 ssize_t
1003 ps_sendcmd(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags,
1004 const void *data, size_t len)
1006 struct ps_msghdr psm = {
1007 .ps_cmd = cmd,
1008 .ps_flags = flags,
1010 struct iovec iov[] = {
1011 { .iov_base = UNCONST(data), .iov_len = len }
1013 struct msghdr msg = {
1014 .msg_iov = iov, .msg_iovlen = 1,
1017 return ps_sendpsmmsg(ctx, fd, &psm, &msg);
1020 static ssize_t
1021 ps_sendcmdmsg(int fd, uint16_t cmd, const struct msghdr *msg)
1023 struct ps_msghdr psm = { .ps_cmd = cmd };
1024 uint8_t data[PS_BUFLEN], *p = data;
1025 struct iovec iov[] = {
1026 { .iov_base = &psm, .iov_len = sizeof(psm) },
1027 { .iov_base = data, .iov_len = 0 },
1029 size_t dl = sizeof(data);
1030 socklen_t cmsg_padlen =
1031 CALC_CMSG_PADLEN(msg->msg_controllen, msg->msg_namelen);
1033 if (msg->msg_namelen != 0) {
1034 if (msg->msg_namelen > dl)
1035 goto nobufs;
1036 psm.ps_namelen = msg->msg_namelen;
1037 memcpy(p, msg->msg_name, msg->msg_namelen);
1038 p += msg->msg_namelen;
1039 dl -= msg->msg_namelen;
1042 if (msg->msg_controllen != 0) {
1043 if (msg->msg_controllen + cmsg_padlen > dl)
1044 goto nobufs;
1045 if (cmsg_padlen != 0) {
1046 memset(p, 0, cmsg_padlen);
1047 p += cmsg_padlen;
1048 dl -= cmsg_padlen;
1050 psm.ps_controllen = (socklen_t)msg->msg_controllen;
1051 memcpy(p, msg->msg_control, msg->msg_controllen);
1052 p += msg->msg_controllen;
1053 dl -= msg->msg_controllen;
1056 psm.ps_datalen = msg->msg_iov[0].iov_len;
1057 if (psm.ps_datalen > dl)
1058 goto nobufs;
1060 iov[1].iov_len =
1061 psm.ps_namelen + psm.ps_controllen + psm.ps_datalen + cmsg_padlen;
1062 if (psm.ps_datalen != 0)
1063 memcpy(p, msg->msg_iov[0].iov_base, psm.ps_datalen);
1064 return writev(fd, iov, __arraycount(iov));
1066 nobufs:
1067 errno = ENOBUFS;
1068 return -1;
1071 ssize_t
1072 ps_recvmsg(int rfd, unsigned short events, uint16_t cmd, int wfd)
1074 struct sockaddr_storage ss = { .ss_family = AF_UNSPEC };
1075 uint8_t controlbuf[sizeof(struct sockaddr_storage)] = { 0 };
1076 uint8_t databuf[64 * 1024];
1077 struct iovec iov[] = {
1078 { .iov_base = databuf, .iov_len = sizeof(databuf) }
1080 struct msghdr msg = {
1081 .msg_name = &ss, .msg_namelen = sizeof(ss),
1082 .msg_control = controlbuf, .msg_controllen = sizeof(controlbuf),
1083 .msg_iov = iov, .msg_iovlen = 1,
1085 ssize_t len;
1087 if (!(events & ELE_READ))
1088 logerrx("%s: unexpected event 0x%04x", __func__, events);
1090 len = recvmsg(rfd, &msg, 0);
1091 if (len == -1) {
1092 logerr("%s: recvmsg", __func__);
1093 return len;
1096 iov[0].iov_len = (size_t)len;
1097 len = ps_sendcmdmsg(wfd, cmd, &msg);
1098 if (len == -1)
1099 logerr("%s: ps_sendcmdmsg", __func__);
1100 return len;
1103 ssize_t
1104 ps_recvpsmsg(struct dhcpcd_ctx *ctx, int fd, unsigned short events,
1105 ssize_t (*callback)(void *, struct ps_msghdr *, struct msghdr *),
1106 void *cbctx)
1108 struct ps_msg psm;
1109 ssize_t len;
1110 size_t dlen;
1111 struct iovec iov[1];
1112 struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 1 };
1113 bool stop = false;
1115 if (!(events & ELE_READ))
1116 logerrx("%s: unexpected event 0x%04x", __func__, events);
1118 len = read(fd, &psm, sizeof(psm));
1119 #ifdef PRIVSEP_DEBUG
1120 logdebugx("%s: %zd", __func__, len);
1121 #endif
1123 if (len == -1 || len == 0)
1124 stop = true;
1125 else {
1126 dlen = (size_t)len;
1127 if (dlen < sizeof(psm.psm_hdr)) {
1128 errno = EINVAL;
1129 return -1;
1132 if (psm.psm_hdr.ps_cmd == PS_STOP) {
1133 stop = true;
1134 len = 0;
1138 if (stop) {
1139 ctx->options |= DHCPCD_EXITING;
1140 #ifdef PRIVSEP_DEBUG
1141 logdebugx("process %d stopping", getpid());
1142 #endif
1143 ps_free(ctx);
1144 eloop_exit(ctx->eloop, len != -1 ? EXIT_SUCCESS : EXIT_FAILURE);
1145 return len;
1147 dlen -= sizeof(psm.psm_hdr);
1149 if (ps_unrollmsg(&msg, &psm.psm_hdr, psm.psm_data, dlen) == -1)
1150 return -1;
1152 if (callback == NULL)
1153 return 0;
1155 errno = 0;
1156 return callback(cbctx, &psm.psm_hdr, &msg);
1159 struct ps_process *
1160 ps_findprocess(struct dhcpcd_ctx *ctx, struct ps_id *psid)
1162 struct ps_process *psp;
1164 TAILQ_FOREACH(psp, &ctx->ps_processes, next) {
1165 if (!(psp->psp_started))
1166 continue;
1167 if (memcmp(&psp->psp_id, psid, sizeof(psp->psp_id)) == 0)
1168 return psp;
1170 errno = ESRCH;
1171 return NULL;
1174 struct ps_process *
1175 ps_findprocesspid(struct dhcpcd_ctx *ctx, pid_t pid)
1177 struct ps_process *psp;
1179 TAILQ_FOREACH(psp, &ctx->ps_processes, next) {
1180 if (psp->psp_pid == pid)
1181 return psp;
1183 errno = ESRCH;
1184 return NULL;
1187 struct ps_process *
1188 ps_newprocess(struct dhcpcd_ctx *ctx, struct ps_id *psid)
1190 struct ps_process *psp;
1192 psp = calloc(1, sizeof(*psp));
1193 if (psp == NULL)
1194 return NULL;
1195 psp->psp_ctx = ctx;
1196 memcpy(&psp->psp_id, psid, sizeof(psp->psp_id));
1197 psp->psp_work_fd = -1;
1198 #ifdef HAVE_CAPSICUM
1199 psp->psp_pfd = -1;
1200 #endif
1202 if (!(ctx->options & DHCPCD_MANAGER))
1203 strlcpy(psp->psp_ifname, ctx->ifv[0], sizeof(psp->psp_name));
1204 TAILQ_INSERT_TAIL(&ctx->ps_processes, psp, next);
1205 return psp;
1208 void
1209 ps_freeprocesses(struct dhcpcd_ctx *ctx, struct ps_process *notthis)
1211 struct ps_process *psp, *psn;
1213 TAILQ_FOREACH_SAFE(psp, &ctx->ps_processes, next, psn) {
1214 if (psp == notthis)
1215 continue;
1216 ps_freeprocess(psp);