xref: /dragonfly/contrib/dhcpcd/src/privsep.c (revision 9189eb5e)
1 /* SPDX-License-Identifier: BSD-2-Clause */
2 /*
3  * Privilege Separation for dhcpcd
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 /*
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 actioneer is chrooted.
37  * All privsep processes ignore signals - only the master process accepts them.
38  *
39  * dhcpcd will maintain the config file in the chroot, no need to handle
40  * this in a script or something.
41  */
42 
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>
48 
49 #ifdef AF_LINK
50 #include <net/if_dl.h>
51 #endif
52 
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>
64 
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"
75 
76 #ifdef HAVE_CAPSICUM
77 #include <sys/capsicum.h>
78 #include <capsicum_helpers.h>
79 #endif
80 #ifdef HAVE_UTIL_H
81 #include <util.h>
82 #endif
83 
84 int
85 ps_init(struct dhcpcd_ctx *ctx)
86 {
87 	struct passwd *pw;
88 	struct stat st;
89 
90 	errno = 0;
91 	if ((ctx->ps_user = pw = getpwnam(PRIVSEP_USER)) == NULL) {
92 		ctx->options &= ~DHCPCD_PRIVSEP;
93 		if (errno == 0) {
94 			logerrx("no such user %s", PRIVSEP_USER);
95 			/* Just incase logerrx caused an error... */
96 			errno = 0;
97 		} else
98 			logerr("getpwnam");
99 		return -1;
100 	}
101 
102 	if (stat(pw->pw_dir, &st) == -1 || !S_ISDIR(st.st_mode)) {
103 		ctx->options &= ~DHCPCD_PRIVSEP;
104 		logerrx("refusing chroot: %s: %s",
105 		    PRIVSEP_USER, pw->pw_dir);
106 		errno = 0;
107 		return -1;
108 	}
109 
110 	ctx->options |= DHCPCD_PRIVSEP;
111 	return 0;
112 }
113 
114 static int
115 ps_dropprivs(struct dhcpcd_ctx *ctx)
116 {
117 	struct passwd *pw = ctx->ps_user;
118 
119 	if (ctx->options & DHCPCD_LAUNCHER)
120 		logdebugx("chrooting as %s to %s", pw->pw_name, pw->pw_dir);
121 	if (chroot(pw->pw_dir) == -1 &&
122 	    (errno != EPERM || ctx->options & DHCPCD_FORKED))
123 		logerr("%s: chroot: %s", __func__, pw->pw_dir);
124 	if (chdir("/") == -1)
125 		logerr("%s: chdir: /", __func__);
126 
127 	if ((setgroups(1, &pw->pw_gid) == -1 ||
128 	     setgid(pw->pw_gid) == -1 ||
129 	     setuid(pw->pw_uid) == -1) &&
130 	     (errno != EPERM || ctx->options & DHCPCD_FORKED))
131 	{
132 		logerr("failed to drop privileges");
133 		return -1;
134 	}
135 
136 	struct rlimit rzero = { .rlim_cur = 0, .rlim_max = 0 };
137 
138 	if (ctx->ps_control_pid != getpid()) {
139 		/* Prohibit new files, sockets, etc */
140 #if defined(__linux__) || defined(__sun) || defined(__OpenBSD__)
141 		/*
142 		 * If poll(2) is called with nfds > RLIMIT_NOFILE
143 		 * then it returns EINVAL.
144 		 * This blows.
145 		 * Do the best we can and limit to what we need.
146 		 * An attacker could potentially close a file and
147 		 * open a new one still, but that cannot be helped.
148 		 */
149 		unsigned long maxfd;
150 		maxfd = (unsigned long)eloop_event_count(ctx->eloop);
151 		if (IN_PRIVSEP_SE(ctx))
152 			maxfd++; /* XXX why? */
153 
154 		struct rlimit rmaxfd = {
155 		    .rlim_cur = maxfd,
156 		    .rlim_max = maxfd
157 		};
158 		if (setrlimit(RLIMIT_NOFILE, &rmaxfd) == -1)
159 			logerr("setrlimit RLIMIT_NOFILE");
160 #else
161 		if (setrlimit(RLIMIT_NOFILE, &rzero) == -1)
162 			logerr("setrlimit RLIMIT_NOFILE");
163 #endif
164 	}
165 
166 	/* Prohibit writing to files.
167 	 * Obviously this won't work if we are using a logfile
168 	 * or redirecting stderr to a file. */
169 	if (ctx->logfile == NULL &&
170 	    (ctx->options & DHCPCD_STARTED ||
171 	     !ctx->stderr_valid || isatty(STDERR_FILENO) == 1))
172 	{
173 		if (setrlimit(RLIMIT_FSIZE, &rzero) == -1)
174 			logerr("setrlimit RLIMIT_FSIZE");
175 	}
176 
177 #ifdef RLIMIT_NPROC
178 	/* Prohibit forks */
179 	if (setrlimit(RLIMIT_NPROC, &rzero) == -1)
180 		logerr("setrlimit RLIMIT_NPROC");
181 #endif
182 
183 	return 0;
184 }
185 
186 static int
187 ps_setbuf0(int fd, int ctl, int minlen)
188 {
189 	int len;
190 	socklen_t slen;
191 
192 	slen = sizeof(len);
193 	if (getsockopt(fd, SOL_SOCKET, ctl, &len, &slen) == -1)
194 		return -1;
195 
196 #ifdef __linux__
197 	len /= 2;
198 #endif
199 	if (len >= minlen)
200 		return 0;
201 
202 	return setsockopt(fd, SOL_SOCKET, ctl, &minlen, sizeof(minlen));
203 }
204 
205 static int
206 ps_setbuf(int fd)
207 {
208 	/* Ensure we can receive a fully sized privsep message.
209 	 * Double the send buffer. */
210 	int minlen = (int)sizeof(struct ps_msg);
211 
212 	if (ps_setbuf0(fd, SO_RCVBUF, minlen) == -1 ||
213 	    ps_setbuf0(fd, SO_SNDBUF, minlen * 2) == -1)
214 	{
215 		logerr(__func__);
216 		return -1;
217 	}
218 	return 0;
219 }
220 
221 int
222 ps_setbuf_fdpair(int fd[])
223 {
224 
225 	if (ps_setbuf(fd[0]) == -1 || ps_setbuf(fd[1]) == -1)
226 		return -1;
227 	return 0;
228 }
229 
230 #ifdef PRIVSEP_RIGHTS
231 int
232 ps_rights_limit_ioctl(int fd)
233 {
234 	cap_rights_t rights;
235 
236 	cap_rights_init(&rights, CAP_IOCTL);
237 	if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
238 		return -1;
239 	return 0;
240 }
241 
242 int
243 ps_rights_limit_fd_fctnl(int fd)
244 {
245 	cap_rights_t rights;
246 
247 	cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT,
248 	    CAP_ACCEPT, CAP_FCNTL);
249 	if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
250 		return -1;
251 	return 0;
252 }
253 
254 int
255 ps_rights_limit_fd(int fd)
256 {
257 	cap_rights_t rights;
258 
259 	cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT, CAP_SHUTDOWN);
260 	if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
261 		return -1;
262 	return 0;
263 }
264 
265 int
266 ps_rights_limit_fd_sockopt(int fd)
267 {
268 	cap_rights_t rights;
269 
270 	cap_rights_init(&rights, CAP_READ, CAP_WRITE, CAP_EVENT,
271 	    CAP_GETSOCKOPT, CAP_SETSOCKOPT);
272 	if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
273 		return -1;
274 	return 0;
275 }
276 
277 int
278 ps_rights_limit_fd_rdonly(int fd)
279 {
280 	cap_rights_t rights;
281 
282 	cap_rights_init(&rights, CAP_READ, CAP_EVENT);
283 	if (cap_rights_limit(fd, &rights) == -1 && errno != ENOSYS)
284 		return -1;
285 	return 0;
286 }
287 
288 int
289 ps_rights_limit_fdpair(int fd[])
290 {
291 
292 	if (ps_rights_limit_fd(fd[0]) == -1 || ps_rights_limit_fd(fd[1]) == -1)
293 		return -1;
294 	return 0;
295 }
296 
297 static int
298 ps_rights_limit_stdio(struct dhcpcd_ctx *ctx)
299 {
300 	const int iebadf = CAPH_IGNORE_EBADF;
301 	int error = 0;
302 
303 	if (ctx->stdin_valid &&
304 	    caph_limit_stream(STDIN_FILENO, CAPH_READ | iebadf) == -1)
305 		error = -1;
306 	if (ctx->stdout_valid &&
307 	    caph_limit_stream(STDOUT_FILENO, CAPH_WRITE | iebadf) == -1)
308 		error = -1;
309 	if (ctx->stderr_valid &&
310 	    caph_limit_stream(STDERR_FILENO, CAPH_WRITE | iebadf) == -1)
311 		error = -1;
312 
313 	return error;
314 }
315 #endif
316 
317 pid_t
318 ps_dostart(struct dhcpcd_ctx *ctx,
319     pid_t *priv_pid, int *priv_fd,
320     void (*recv_msg)(void *), void (*recv_unpriv_msg),
321     void *recv_ctx, int (*callback)(void *), void (*signal_cb)(int, void *),
322     unsigned int flags)
323 {
324 	int fd[2];
325 	pid_t pid;
326 
327 	if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, fd) == -1) {
328 		logerr("%s: socketpair", __func__);
329 		return -1;
330 	}
331 	if (ps_setbuf_fdpair(fd) == -1) {
332 		logerr("%s: ps_setbuf_fdpair", __func__);
333 		return -1;
334 	}
335 #ifdef PRIVSEP_RIGHTS
336 	if (ps_rights_limit_fdpair(fd) == -1) {
337 		logerr("%s: ps_rights_limit_fdpair", __func__);
338 		return -1;
339 	}
340 #endif
341 
342 	switch (pid = fork()) {
343 	case -1:
344 		logerr("fork");
345 		return -1;
346 	case 0:
347 		*priv_fd = fd[1];
348 		close(fd[0]);
349 		break;
350 	default:
351 		*priv_pid = pid;
352 		*priv_fd = fd[0];
353 		close(fd[1]);
354 		if (recv_unpriv_msg == NULL)
355 			;
356 		else if (eloop_event_add(ctx->eloop, *priv_fd,
357 		    recv_unpriv_msg, recv_ctx) == -1)
358 		{
359 			logerr("%s: eloop_event_add", __func__);
360 			return -1;
361 		}
362 		return pid;
363 	}
364 
365 	ctx->options |= DHCPCD_UNPRIV | DHCPCD_FORKED;
366 	if (ctx->fork_fd != -1) {
367 		close(ctx->fork_fd);
368 		ctx->fork_fd = -1;
369 	}
370 	pidfile_clean();
371 	eloop_clear(ctx->eloop);
372 
373 	/* We are not root */
374 	if (priv_fd != &ctx->ps_root_fd) {
375 		ps_freeprocesses(ctx, recv_ctx);
376 		if (ctx->ps_root_fd != -1) {
377 			close(ctx->ps_root_fd);
378 			ctx->ps_root_fd = -1;
379 		}
380 
381 #ifdef PRIVSEP_RIGHTS
382 		/* We cannot limit the root process in any way. */
383 		if (ps_rights_limit_stdio(ctx) == -1) {
384 			logerr("ps_rights_limit_stdio");
385 			goto errexit;
386 		}
387 #endif
388 	}
389 
390 	if (priv_fd != &ctx->ps_inet_fd && ctx->ps_inet_fd != -1) {
391 		close(ctx->ps_inet_fd);
392 		ctx->ps_inet_fd = -1;
393 	}
394 
395 	eloop_signal_set_cb(ctx->eloop,
396 	    dhcpcd_signals, dhcpcd_signals_len, signal_cb, ctx);
397 
398 	/* ctx->sigset aready has the initial sigmask set in main() */
399 	if (eloop_signal_mask(ctx->eloop, NULL) == -1) {
400 		logerr("%s: eloop_signal_mask", __func__);
401 		goto errexit;
402 	}
403 
404 	if (eloop_event_add(ctx->eloop, *priv_fd, recv_msg, recv_ctx) == -1)
405 	{
406 		logerr("%s: eloop_event_add", __func__);
407 		goto errexit;
408 	}
409 
410 	if (callback(recv_ctx) == -1)
411 		goto errexit;
412 
413 	if (flags & PSF_DROPPRIVS)
414 		ps_dropprivs(ctx);
415 
416 	return 0;
417 
418 errexit:
419 	/* Failure to start root or inet processes is fatal. */
420 	if (priv_fd == &ctx->ps_root_fd || priv_fd == &ctx->ps_inet_fd)
421 		(void)ps_sendcmd(ctx, *priv_fd, PS_STOP, 0, NULL, 0);
422 	shutdown(*priv_fd, SHUT_RDWR);
423 	*priv_fd = -1;
424 	eloop_exit(ctx->eloop, EXIT_FAILURE);
425 	return -1;
426 }
427 
428 int
429 ps_dostop(struct dhcpcd_ctx *ctx, pid_t *pid, int *fd)
430 {
431 	int err = 0;
432 
433 #ifdef PRIVSEP_DEBUG
434 	logdebugx("%s: pid=%d fd=%d", __func__, *pid, *fd);
435 #endif
436 
437 	if (*fd != -1) {
438 		eloop_event_delete(ctx->eloop, *fd);
439 		if (ps_sendcmd(ctx, *fd, PS_STOP, 0, NULL, 0) == -1) {
440 			logerr(__func__);
441 			err = -1;
442 		}
443 		(void)shutdown(*fd, SHUT_RDWR);
444 		close(*fd);
445 		*fd = -1;
446 	}
447 
448 	/* Don't wait for the process as it may not respond to the shutdown
449 	 * request. We'll reap the process on receipt of SIGCHLD. */
450 	*pid = 0;
451 	return err;
452 }
453 
454 int
455 ps_start(struct dhcpcd_ctx *ctx)
456 {
457 	pid_t pid;
458 
459 	TAILQ_INIT(&ctx->ps_processes);
460 
461 	switch (pid = ps_root_start(ctx)) {
462 	case -1:
463 		logerr("ps_root_start");
464 		return -1;
465 	case 0:
466 		return 0;
467 	default:
468 		logdebugx("spawned privileged actioneer on PID %d", pid);
469 	}
470 
471 	/* No point in spawning the generic network listener if we're
472 	 * not going to use it. */
473 	if (!ps_inet_canstart(ctx))
474 		goto started_net;
475 
476 	switch (pid = ps_inet_start(ctx)) {
477 	case -1:
478 		return -1;
479 	case 0:
480 		return 0;
481 	default:
482 		logdebugx("spawned network proxy on PID %d", pid);
483 	}
484 
485 started_net:
486 	if (!(ctx->options & DHCPCD_TEST)) {
487 		switch (pid = ps_ctl_start(ctx)) {
488 		case -1:
489 			return -1;
490 		case 0:
491 			return 0;
492 		default:
493 			logdebugx("spawned controller proxy on PID %d", pid);
494 		}
495 	}
496 
497 #ifdef ARC4RANDOM_H
498 	/* Seed the random number generator early incase it needs /dev/urandom
499 	 * which won't be available in the chroot. */
500 	arc4random();
501 #endif
502 
503 	return 1;
504 }
505 
506 int
507 ps_entersandbox(const char *_pledge, const char **sandbox)
508 {
509 
510 #if !defined(HAVE_PLEDGE)
511 	UNUSED(_pledge);
512 #endif
513 
514 #if defined(HAVE_CAPSICUM)
515 	if (sandbox != NULL)
516 		*sandbox = "capsicum";
517 	return cap_enter();
518 #elif defined(HAVE_PLEDGE)
519 	if (sandbox != NULL)
520 		*sandbox = "pledge";
521 	return pledge(_pledge, NULL);
522 #elif defined(HAVE_SECCOMP)
523 	if (sandbox != NULL)
524 		*sandbox = "seccomp";
525 	return ps_seccomp_enter();
526 #else
527 	if (sandbox != NULL)
528 		*sandbox = "posix resource limited";
529 	return 0;
530 #endif
531 }
532 
533 int
534 ps_mastersandbox(struct dhcpcd_ctx *ctx, const char *_pledge)
535 {
536 	const char *sandbox = NULL;
537 	bool forked;
538 	int dropped;
539 
540 	forked = ctx->options & DHCPCD_FORKED;
541 	ctx->options &= ~DHCPCD_FORKED;
542 	dropped = ps_dropprivs(ctx);
543 	if (forked)
544 		ctx->options |= DHCPCD_FORKED;
545 
546 	/*
547 	 * If we don't have a root process, we cannot use syslog.
548 	 * If it cannot be opened before chrooting then syslog(3) will fail.
549 	 * openlog(3) does not return an error which doubly sucks.
550 	 */
551 	if (ctx->ps_root_fd == -1) {
552 		unsigned int logopts = loggetopts();
553 
554 		logopts &= ~LOGERR_LOG;
555 		logsetopts(logopts);
556 	}
557 
558 	if (dropped == -1) {
559 		logerr("%s: ps_dropprivs", __func__);
560 		return -1;
561 	}
562 
563 #ifdef PRIVSEP_RIGHTS
564 	if ((ctx->pf_inet_fd != -1 &&
565 	    ps_rights_limit_ioctl(ctx->pf_inet_fd) == -1) ||
566 	     ps_rights_limit_stdio(ctx) == -1)
567 	{
568 		logerr("%s: cap_rights_limit", __func__);
569 		return -1;
570 	}
571 #endif
572 
573 	if (_pledge == NULL)
574 		_pledge = "stdio";
575 	if (ps_entersandbox(_pledge, &sandbox) == -1) {
576 		if (errno == ENOSYS) {
577 			if (sandbox != NULL)
578 				logwarnx("sandbox unavailable: %s", sandbox);
579 			return 0;
580 		}
581 		logerr("%s: %s", __func__, sandbox);
582 		return -1;
583 	} else if (ctx->options & DHCPCD_LAUNCHER)
584 		logdebugx("sandbox: %s", sandbox);
585 	return 0;
586 }
587 
588 int
589 ps_stop(struct dhcpcd_ctx *ctx)
590 {
591 	int r, ret = 0;
592 
593 	if (!(ctx->options & DHCPCD_PRIVSEP) ||
594 	    ctx->options & DHCPCD_FORKED ||
595 	    ctx->eloop == NULL)
596 		return 0;
597 
598 	r = ps_ctl_stop(ctx);
599 	if (r != 0)
600 		ret = r;
601 
602 	r = ps_inet_stop(ctx);
603 	if (r != 0)
604 		ret = r;
605 
606 	/* We've been chrooted, so we need to tell the
607 	 * privileged actioneer to remove the pidfile. */
608 	ps_root_unlink(ctx, ctx->pidfile);
609 
610 	r = ps_root_stop(ctx);
611 	if (r != 0)
612 		ret = r;
613 
614 	ctx->options &= ~DHCPCD_PRIVSEP;
615 	return ret;
616 }
617 
618 void
619 ps_freeprocess(struct ps_process *psp)
620 {
621 
622 	TAILQ_REMOVE(&psp->psp_ctx->ps_processes, psp, next);
623 	if (psp->psp_fd != -1) {
624 		eloop_event_delete(psp->psp_ctx->eloop, psp->psp_fd);
625 		close(psp->psp_fd);
626 	}
627 	if (psp->psp_work_fd != -1) {
628 		eloop_event_delete(psp->psp_ctx->eloop, psp->psp_work_fd);
629 		close(psp->psp_work_fd);
630 	}
631 #ifdef INET
632 	if (psp->psp_bpf != NULL)
633 		bpf_close(psp->psp_bpf);
634 #endif
635 	free(psp);
636 }
637 
638 static void
639 ps_free(struct dhcpcd_ctx *ctx)
640 {
641 	struct ps_process *psp;
642 	bool stop = ctx->ps_root_pid == getpid();
643 
644 	while ((psp = TAILQ_FIRST(&ctx->ps_processes)) != NULL) {
645 		if (stop)
646 			ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd);
647 		ps_freeprocess(psp);
648 	}
649 }
650 
651 int
652 ps_unrollmsg(struct msghdr *msg, struct ps_msghdr *psm,
653     const void *data, size_t len)
654 {
655 	uint8_t *datap, *namep, *controlp;
656 
657 	namep = UNCONST(data);
658 	controlp = namep + psm->ps_namelen;
659 	datap = controlp + psm->ps_controllen;
660 
661 	if (psm->ps_namelen != 0) {
662 		if (psm->ps_namelen > len) {
663 			errno = EINVAL;
664 			return -1;
665 		}
666 		msg->msg_name = namep;
667 		len -= psm->ps_namelen;
668 	} else
669 		msg->msg_name = NULL;
670 	msg->msg_namelen = psm->ps_namelen;
671 
672 	if (psm->ps_controllen != 0) {
673 		if (psm->ps_controllen > len) {
674 			errno = EINVAL;
675 			return -1;
676 		}
677 		msg->msg_control = controlp;
678 		len -= psm->ps_controllen;
679 	} else
680 		msg->msg_control = NULL;
681 	msg->msg_controllen = psm->ps_controllen;
682 
683 	if (len != 0) {
684 		msg->msg_iovlen = 1;
685 		msg->msg_iov[0].iov_base = datap;
686 		msg->msg_iov[0].iov_len = len;
687 	} else {
688 		msg->msg_iovlen = 0;
689 		msg->msg_iov[0].iov_base = NULL;
690 		msg->msg_iov[0].iov_len = 0;
691 	}
692 	return 0;
693 }
694 
695 ssize_t
696 ps_sendpsmmsg(struct dhcpcd_ctx *ctx, int fd,
697     struct ps_msghdr *psm, const struct msghdr *msg)
698 {
699 	struct iovec iov[] = {
700 		{ .iov_base = UNCONST(psm), .iov_len = sizeof(*psm) },
701 		{ .iov_base = NULL, },	/* name */
702 		{ .iov_base = NULL, },	/* control */
703 		{ .iov_base = NULL, },	/* payload 1 */
704 		{ .iov_base = NULL, },	/* payload 2 */
705 		{ .iov_base = NULL, },	/* payload 3 */
706 	};
707 	int iovlen;
708 	ssize_t len;
709 
710 	if (msg != NULL) {
711 		struct iovec *iovp = &iov[1];
712 		int i;
713 
714 		psm->ps_namelen = msg->msg_namelen;
715 		psm->ps_controllen = (socklen_t)msg->msg_controllen;
716 
717 		iovp->iov_base = msg->msg_name;
718 		iovp->iov_len = msg->msg_namelen;
719 		iovp++;
720 		iovp->iov_base = msg->msg_control;
721 		iovp->iov_len = msg->msg_controllen;
722 		iovlen = 3;
723 
724 		for (i = 0; i < (int)msg->msg_iovlen; i++) {
725 			if ((size_t)(iovlen + i) > __arraycount(iov)) {
726 				errno =	ENOBUFS;
727 				return -1;
728 			}
729 			iovp++;
730 			iovp->iov_base = msg->msg_iov[i].iov_base;
731 			iovp->iov_len = msg->msg_iov[i].iov_len;
732 		}
733 		iovlen += i;
734 	} else
735 		iovlen = 1;
736 
737 	len = writev(fd, iov, iovlen);
738 	if (len == -1) {
739 		logerr(__func__);
740 		if (ctx->options & DHCPCD_FORKED &&
741 		    !(ctx->options & DHCPCD_PRIVSEPROOT))
742 			eloop_exit(ctx->eloop, EXIT_FAILURE);
743 	}
744 	return len;
745 }
746 
747 ssize_t
748 ps_sendpsmdata(struct dhcpcd_ctx *ctx, int fd,
749     struct ps_msghdr *psm, const void *data, size_t len)
750 {
751 	struct iovec iov[] = {
752 		{ .iov_base = UNCONST(data), .iov_len = len },
753 	};
754 	struct msghdr msg = {
755 		.msg_iov = iov, .msg_iovlen = 1,
756 	};
757 
758 	return ps_sendpsmmsg(ctx, fd, psm, &msg);
759 }
760 
761 
762 ssize_t
763 ps_sendmsg(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags,
764     const struct msghdr *msg)
765 {
766 	struct ps_msghdr psm = {
767 		.ps_cmd = cmd,
768 		.ps_flags = flags,
769 		.ps_namelen = msg->msg_namelen,
770 		.ps_controllen = (socklen_t)msg->msg_controllen,
771 	};
772 	size_t i;
773 
774 	for (i = 0; i < (size_t)msg->msg_iovlen; i++)
775 		psm.ps_datalen += msg->msg_iov[i].iov_len;
776 
777 #if 0	/* For debugging structure padding. */
778 	logerrx("psa.family %lu %zu", offsetof(struct ps_addr, psa_family), sizeof(psm.ps_id.psi_addr.psa_family));
779 	logerrx("psa.pad %lu %zu", offsetof(struct ps_addr, psa_pad), sizeof(psm.ps_id.psi_addr.psa_pad));
780 	logerrx("psa.psa_u %lu %zu", offsetof(struct ps_addr, psa_u), sizeof(psm.ps_id.psi_addr.psa_u));
781 	logerrx("psa %zu", sizeof(psm.ps_id.psi_addr));
782 
783 	logerrx("psi.addr %lu %zu", offsetof(struct ps_id, psi_addr), sizeof(psm.ps_id.psi_addr));
784 	logerrx("psi.index %lu %zu", offsetof(struct ps_id, psi_ifindex), sizeof(psm.ps_id.psi_ifindex));
785 	logerrx("psi.cmd %lu %zu", offsetof(struct ps_id, psi_cmd), sizeof(psm.ps_id.psi_cmd));
786 	logerrx("psi.pad %lu %zu", offsetof(struct ps_id, psi_pad), sizeof(psm.ps_id.psi_pad));
787 	logerrx("psi %zu", sizeof(struct ps_id));
788 
789 	logerrx("ps_cmd %lu", offsetof(struct ps_msghdr, ps_cmd));
790 	logerrx("ps_pad %lu %zu", offsetof(struct ps_msghdr, ps_pad), sizeof(psm.ps_pad));
791 	logerrx("ps_flags %lu %zu", offsetof(struct ps_msghdr, ps_flags), sizeof(psm.ps_flags));
792 
793 	logerrx("ps_id %lu %zu", offsetof(struct ps_msghdr, ps_id), sizeof(psm.ps_id));
794 
795 	logerrx("ps_namelen %lu %zu", offsetof(struct ps_msghdr, ps_namelen), sizeof(psm.ps_namelen));
796 	logerrx("ps_controllen %lu %zu", offsetof(struct ps_msghdr, ps_controllen), sizeof(psm.ps_controllen));
797 	logerrx("ps_pad2 %lu %zu", offsetof(struct ps_msghdr, ps_pad2), sizeof(psm.ps_pad2));
798 	logerrx("ps_datalen %lu %zu", offsetof(struct ps_msghdr, ps_datalen), sizeof(psm.ps_datalen));
799 	logerrx("psm %zu", sizeof(psm));
800 #endif
801 
802 	return ps_sendpsmmsg(ctx, fd, &psm, msg);
803 }
804 
805 ssize_t
806 ps_sendcmd(struct dhcpcd_ctx *ctx, int fd, uint16_t cmd, unsigned long flags,
807     const void *data, size_t len)
808 {
809 	struct ps_msghdr psm = {
810 		.ps_cmd = cmd,
811 		.ps_flags = flags,
812 	};
813 	struct iovec iov[] = {
814 		{ .iov_base = UNCONST(data), .iov_len = len }
815 	};
816 	struct msghdr msg = {
817 		.msg_iov = iov, .msg_iovlen = 1,
818 	};
819 
820 	return ps_sendpsmmsg(ctx, fd, &psm, &msg);
821 }
822 
823 static ssize_t
824 ps_sendcmdmsg(int fd, uint16_t cmd, const struct msghdr *msg)
825 {
826 	struct ps_msghdr psm = { .ps_cmd = cmd };
827 	uint8_t data[PS_BUFLEN], *p = data;
828 	struct iovec iov[] = {
829 		{ .iov_base = &psm, .iov_len = sizeof(psm) },
830 		{ .iov_base = data, .iov_len = 0 },
831 	};
832 	size_t dl = sizeof(data);
833 
834 	if (msg->msg_namelen != 0) {
835 		if (msg->msg_namelen > dl)
836 			goto nobufs;
837 		psm.ps_namelen = msg->msg_namelen;
838 		memcpy(p, msg->msg_name, msg->msg_namelen);
839 		p += msg->msg_namelen;
840 		dl -= msg->msg_namelen;
841 	}
842 
843 	if (msg->msg_controllen != 0) {
844 		if (msg->msg_controllen > dl)
845 			goto nobufs;
846 		psm.ps_controllen = (socklen_t)msg->msg_controllen;
847 		memcpy(p, msg->msg_control, msg->msg_controllen);
848 		p += msg->msg_controllen;
849 		dl -= msg->msg_controllen;
850 	}
851 
852 	psm.ps_datalen = msg->msg_iov[0].iov_len;
853 	if (psm.ps_datalen > dl)
854 		goto nobufs;
855 
856 	iov[1].iov_len = psm.ps_namelen + psm.ps_controllen + psm.ps_datalen;
857 	if (psm.ps_datalen != 0)
858 		memcpy(p, msg->msg_iov[0].iov_base, psm.ps_datalen);
859 	return writev(fd, iov, __arraycount(iov));
860 
861 nobufs:
862 	errno = ENOBUFS;
863 	return -1;
864 }
865 
866 ssize_t
867 ps_recvmsg(struct dhcpcd_ctx *ctx, int rfd, uint16_t cmd, int wfd)
868 {
869 	struct sockaddr_storage ss = { .ss_family = AF_UNSPEC };
870 	uint8_t controlbuf[sizeof(struct sockaddr_storage)] = { 0 };
871 	uint8_t databuf[64 * 1024];
872 	struct iovec iov[] = {
873 	    { .iov_base = databuf, .iov_len = sizeof(databuf) }
874 	};
875 	struct msghdr msg = {
876 		.msg_name = &ss, .msg_namelen = sizeof(ss),
877 		.msg_control = controlbuf, .msg_controllen = sizeof(controlbuf),
878 		.msg_iov = iov, .msg_iovlen = 1,
879 	};
880 
881 	ssize_t len = recvmsg(rfd, &msg, 0);
882 
883 	if (len == -1)
884 		logerr("%s: recvmsg", __func__);
885 	if (len == -1 || len == 0) {
886 		if (ctx->options & DHCPCD_FORKED &&
887 		    !(ctx->options & DHCPCD_PRIVSEPROOT))
888 			eloop_exit(ctx->eloop,
889 			    len == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
890 		return len;
891 	}
892 
893 	iov[0].iov_len = (size_t)len;
894 	len = ps_sendcmdmsg(wfd, cmd, &msg);
895 	if (len == -1) {
896 		logerr("ps_sendcmdmsg");
897 		if (ctx->options & DHCPCD_FORKED &&
898 		    !(ctx->options & DHCPCD_PRIVSEPROOT))
899 			eloop_exit(ctx->eloop, EXIT_FAILURE);
900 	}
901 	return len;
902 }
903 
904 ssize_t
905 ps_recvpsmsg(struct dhcpcd_ctx *ctx, int fd,
906     ssize_t (*callback)(void *, struct ps_msghdr *, struct msghdr *),
907     void *cbctx)
908 {
909 	struct ps_msg psm;
910 	ssize_t len;
911 	size_t dlen;
912 	struct iovec iov[1];
913 	struct msghdr msg = { .msg_iov = iov, .msg_iovlen = 1 };
914 	bool stop = false;
915 
916 	len = read(fd, &psm, sizeof(psm));
917 #ifdef PRIVSEP_DEBUG
918 	logdebugx("%s: %zd", __func__, len);
919 #endif
920 
921 	if (len == -1 || len == 0)
922 		stop = true;
923 	else {
924 		dlen = (size_t)len;
925 		if (dlen < sizeof(psm.psm_hdr)) {
926 			errno = EINVAL;
927 			return -1;
928 		}
929 
930 		if (psm.psm_hdr.ps_cmd == PS_STOP) {
931 			stop = true;
932 			len = 0;
933 		}
934 	}
935 
936 	if (stop) {
937 #ifdef PRIVSEP_DEBUG
938 		logdebugx("process %d stopping", getpid());
939 #endif
940 		ps_free(ctx);
941 #ifdef PLUGIN_DEV
942 		dev_stop(ctx);
943 #endif
944 		eloop_exit(ctx->eloop, len != -1 ? EXIT_SUCCESS : EXIT_FAILURE);
945 		return len;
946 	}
947 	dlen -= sizeof(psm.psm_hdr);
948 
949 	if (ps_unrollmsg(&msg, &psm.psm_hdr, psm.psm_data, dlen) == -1)
950 		return -1;
951 
952 	if (callback == NULL)
953 		return 0;
954 
955 	errno = 0;
956 	return callback(cbctx, &psm.psm_hdr, &msg);
957 }
958 
959 struct ps_process *
960 ps_findprocess(struct dhcpcd_ctx *ctx, struct ps_id *psid)
961 {
962 	struct ps_process *psp;
963 
964 	TAILQ_FOREACH(psp, &ctx->ps_processes, next) {
965 		if (memcmp(&psp->psp_id, psid, sizeof(psp->psp_id)) == 0)
966 			return psp;
967 	}
968 	errno = ESRCH;
969 	return NULL;
970 }
971 
972 struct ps_process *
973 ps_newprocess(struct dhcpcd_ctx *ctx, struct ps_id *psid)
974 {
975 	struct ps_process *psp;
976 
977 	psp = calloc(1, sizeof(*psp));
978 	if (psp == NULL)
979 		return NULL;
980 	psp->psp_ctx = ctx;
981 	memcpy(&psp->psp_id, psid, sizeof(psp->psp_id));
982 	psp->psp_work_fd = -1;
983 	TAILQ_INSERT_TAIL(&ctx->ps_processes, psp, next);
984 	return psp;
985 }
986 
987 void
988 ps_freeprocesses(struct dhcpcd_ctx *ctx, struct ps_process *notthis)
989 {
990 	struct ps_process *psp, *psn;
991 
992 	TAILQ_FOREACH_SAFE(psp, &ctx->ps_processes, next, psn) {
993 		if (psp == notthis)
994 			continue;
995 		ps_freeprocess(psp);
996 	}
997 }
998