changeset 5328:ea68407e5ac8 draft

privsep: Implement a resource limited sandbox For systems without Capsicum or Pledge we can create a resource limited sandbox provided that either ppoll(2) or works with RLIMIT_NOFILES set to zero. As far as dhcpcd is concerned, that means Linux and Solaris won't work with this, but NetBSD and DragonFlyBSD will. To achieve this, a special control proxy process will be spawned just to accept new connections over the control socket because this *cannot* be limited by RLIMIT_NOFILES.
author Roy Marples <roy@marples.name>
date Tue, 09 Jun 2020 18:25:18 +0100
parents 1e2041e4dfe7
children cc6b3545c52c
files configure src/Makefile src/control.c src/control.h src/dhcpcd.c src/dhcpcd.h src/privsep-control.c src/privsep-control.h src/privsep.c src/privsep.h src/script.c
diffstat 11 files changed, 780 insertions(+), 263 deletions(-) [+]
line wrap: on
line diff
--- a/configure	Tue Jun 09 17:56:03 2020 +0100
+++ b/configure	Tue Jun 09 18:25:18 2020 +0100
@@ -580,16 +580,18 @@
 	echo "#ifndef PRIVSEP_USER" >>$CONFIG_H
 	echo "#define PRIVSEP_USER		 \"$PRIVSEP_USER\"" >>$CONFIG_H
 	echo "#endif" >>$CONFIG_H
-	echo "DHCPCD_SRCS+=	privsep.c privsep-root.c privsep-inet.c" \
+	echo "PRIVSEP_SRCS=	privsep.c privsep-root.c privsep-inet.c" \
 		>>$CONFIG_MK
 	if [ -z "$INET" ] || [ "$INET" = yes ]; then
-		echo "DHCPCD_SRCS+=	privsep-bpf.c" >>$CONFIG_MK
+		echo "PRIVSEP_SRCS+=	privsep-bpf.c" >>$CONFIG_MK
 	fi
 	case "$OS" in
-	linux*)		 echo "DHCPCD_SRCS+=	privsep-linux.c" >>$CONFIG_MK;;
-	solaris*|sunos*) echo "DHCPCD_SRCS+=	privsep-sun.c" >>$CONFIG_MK;;
-	*)		 echo "DHCPCD_SRCS+=	privsep-bsd.c" >>$CONFIG_MK;;
+	linux*)		 echo "PRIVSEP_SRCS+=	privsep-linux.c" >>$CONFIG_MK;;
+	solaris*|sunos*) echo "PRIVSEP_SRCS+=	privsep-sun.c" >>$CONFIG_MK;;
+	*)		 echo "PRIVSEP_SRCS+=	privsep-bsd.c" >>$CONFIG_MK;;
 	esac
+else
+	echo "PRIVSEP_SRCS=" >>$CONFIG_MK
 fi
 
 echo "Using compiler .. $CC"
@@ -622,16 +624,8 @@
 [ "$CC" != cc ] && echo "CC=		$CC" >>$CONFIG_MK
 $CC --version | $SED -e '1!d'
 
-if [ -z "$EMBEDDED" -o "$EMBEDDED" = yes ]; then
-	echo "$DHCPCD_DEFS will be embedded in dhcpcd itself"
-	echo "DHCPCD_SRCS+=	dhcpcd-embedded.c" >>$CONFIG_MK
-else
-	echo "$DHCPCD_DEFS will be installed to $LIBEXECDIR"
-	echo "CPPFLAGS+=	-DEMBEDDED_CONFIG=\\\"$LIBEXECDIR/dhcpcd-definitions.conf\\\"" >>$CONFIG_MK
-	echo "EMBEDDEDINSTALL=	_embeddedinstall" >>$CONFIG_MK
-fi
-
 if [ "$PRIVSEP" = yes ]; then
+	PRIVSEP_CONTROLLER=true
 	printf "Testing for capsicum ... "
 	cat <<EOF >_capsicum.c
 #include <sys/capsicum.h>
@@ -642,6 +636,7 @@
 	if $XCC _capsicum.c -o _capsicum 2>&3; then
 		echo "yes"
 		echo "#define	HAVE_CAPSICUM" >>$CONFIG_H
+		PRIVSEP_CONTROLLER=false
 	else
 		echo "no"
 	fi
@@ -657,10 +652,25 @@
         if $XCC _pledge.c -o _pledge 2>&3; then
                 echo "yes"
                 echo "#define   HAVE_PLEDGE" >>$CONFIG_H
+		PRIVSEP_CONTROLLER=false
         else
                 echo "no"
         fi
         rm -f _pledge.c _pledge
+
+	if $PRIVSEP_CONTROLLER; then
+		echo "#define	PRIVSEP_CONTROLLER" >>$CONFIG_H
+		echo "PRIVSEP_SRCS+=	privsep-control.c" >>$CONFIG_MK
+	fi
+fi
+
+if [ -z "$EMBEDDED" -o "$EMBEDDED" = yes ]; then
+	echo "$DHCPCD_DEFS will be embedded in dhcpcd itself"
+	echo "DHCPCD_SRCS+=	dhcpcd-embedded.c" >>$CONFIG_MK
+else
+	echo "$DHCPCD_DEFS will be installed to $LIBEXECDIR"
+	echo "CPPFLAGS+=	-DEMBEDDED_CONFIG=\\\"$LIBEXECDIR/dhcpcd-definitions.conf\\\"" >>$CONFIG_MK
+	echo "EMBEDDEDINSTALL=	_embeddedinstall" >>$CONFIG_MK
 fi
 
 if [ "$OS" = linux ]; then
--- a/src/Makefile	Tue Jun 09 17:56:03 2020 +0100
+++ b/src/Makefile	Tue Jun 09 18:25:18 2020 +0100
@@ -15,7 +15,7 @@
 CFLAGS+=	-std=${CSTD}
 CPPFLAGS+=	-I${TOP} -I${TOP}/src -I./crypt
 
-SRCS+=		${DHCPCD_SRCS}
+SRCS+=		${DHCPCD_SRCS} ${PRIVSEP_SRCS}
 DHCPCD_DEF?=	dhcpcd-definitions.conf
 DHCPCD_DEFS=	dhcpcd-definitions.conf dhcpcd-definitions-small.conf
 
--- a/src/control.c	Tue Jun 09 17:56:03 2020 +0100
+++ b/src/control.c	Tue Jun 09 18:25:18 2020 +0100
@@ -64,7 +64,6 @@
 			free(fdp->data);
 		free(fdp);
 	}
-	fd->queue_len = 0;
 
 #ifdef CTL_FREE_LIST
 	while ((fdp = TAILQ_FIRST(&fd->free_queue))) {
@@ -76,56 +75,118 @@
 #endif
 }
 
-static void
+void
+control_free(struct fd_list *fd)
+{
+
+#ifdef PRIVSEP_CONTROLLER
+	if (fd->ctx->ps_control_client == fd)
+		fd->ctx->ps_control_client = NULL;
+#endif
+
+	eloop_event_remove_writecb(fd->ctx->eloop, fd->fd);
+	TAILQ_REMOVE(&fd->ctx->control_fds, fd, next);
+	control_queue_free(fd);
+	free(fd);
+}
+
+void
 control_delete(struct fd_list *fd)
 {
 
-	TAILQ_REMOVE(&fd->ctx->control_fds, fd, next);
+#ifdef PRIVSEP_CONTROLLER
+	if (IN_PRIVSEP_SE(fd->ctx))
+		return;
+#endif
+
 	eloop_event_delete(fd->ctx->eloop, fd->fd);
 	close(fd->fd);
-	control_queue_free(fd);
-	free(fd);
+	control_free(fd);
 }
 
 static void
 control_handle_data(void *arg)
 {
 	struct fd_list *fd = arg;
-	char buffer[1024], *e, *p, *argvp[255], **ap, *a;
+	char buffer[1024];
 	ssize_t bytes;
-	size_t len;
-	int argc;
 
 	bytes = read(fd->fd, buffer, sizeof(buffer) - 1);
+
 	if (bytes == -1 || bytes == 0) {
 		/* Control was closed or there was an error.
 		 * Remove it from our list. */
 		control_delete(fd);
 		return;
 	}
-	buffer[bytes] = '\0';
-	p = buffer;
-	e = buffer + bytes;
+
+#ifdef PRIVSEP_CONTROLLER
+	if (IN_PRIVSEP(fd->ctx)) {
+		ssize_t err;
+
+		fd->flags |= FD_SENDLEN;
+		err = ps_ctl_handleargs(fd, buffer, (size_t)bytes);
+		fd->flags &= ~FD_SENDLEN;
+		if (err == -1) {
+			logerr(__func__);
+			return;
+		}
+		if (err == 1 &&
+		    ps_ctl_sendargs(fd, buffer, (size_t)bytes) == -1) {
+			logerr(__func__);
+			control_delete(fd);
+		}
+		return;
+	}
+#endif
+
+	control_recvdata(fd, buffer, (size_t)bytes);
+}
+
+void
+control_recvdata(struct fd_list *fd, char *data, size_t len)
+{
+	char *p = data, *e;
+	char *argvp[255], **ap;
+	int argc;
 
 	/* Each command is \n terminated
 	 * Each argument is NULL separated */
-	while (p < e) {
+	while (len != 0) {
 		argc = 0;
 		ap = argvp;
-		while (p < e) {
-			argc++;
+		while (len != 0) {
+			if (*p == '\0') {
+				p++;
+				len--;
+				continue;
+			}
+			e = memchr(p, '\0', len);
+			if (e == NULL) {
+				errno = EINVAL;
+				logerrx("%s: no terminator", __func__);
+				return;
+			}
 			if ((size_t)argc >= sizeof(argvp) / sizeof(argvp[0])) {
 				errno = ENOBUFS;
+				logerrx("%s: no arg buffer", __func__);
 				return;
 			}
-			a = *ap++ = p;
-			len = strlen(p);
-			p += len + 1;
-			if (len && a[len - 1] == '\n') {
-				a[len - 1] = '\0';
+			*ap++ = p;
+			argc++;
+			e++;
+			len -= (size_t)(e - p);
+			p = e;
+			e--;
+			if (*(--e) == '\n') {
+				*e = '\0';
 				break;
 			}
 		}
+		if (argc == 0) {
+			logerrx("%s: no args", __func__);
+			continue;
+		}
 		*ap = NULL;
 		if (dhcpcd_handleargs(fd->ctx, fd, argc, argvp) == -1) {
 			logerr(__func__);
@@ -137,6 +198,26 @@
 	}
 }
 
+struct fd_list *
+control_new(struct dhcpcd_ctx *ctx, int fd, unsigned int flags)
+{
+	struct fd_list *l;
+
+	l = malloc(sizeof(*l));
+	if (l == NULL)
+		return NULL;
+
+	l->ctx = ctx;
+	l->fd = fd;
+	l->flags = flags;
+	TAILQ_INIT(&l->queue);
+#ifdef CTL_FREE_LIST
+	TAILQ_INIT(&l->free_queue);
+#endif
+	TAILQ_INSERT_TAIL(&ctx->control_fds, l, next);
+	return l;
+}
+
 static void
 control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags)
 {
@@ -155,20 +236,19 @@
 	    fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
 		goto error;
 
-	l = malloc(sizeof(*l));
+#ifdef PRIVSEP_CONTROLLER
+	if (IN_PRIVSEP(ctx) && !IN_PRIVSEP_SE(ctx))
+		;
+	else
+#endif
+	fd_flags |= FD_SENDLEN;
+
+	l = control_new(ctx, fd, fd_flags);
 	if (l == NULL)
 		goto error;
 
-	l->ctx = ctx;
-	l->fd = fd;
-	l->flags = fd_flags;
-	TAILQ_INIT(&l->queue);
-	l->queue_len = 0;
-#ifdef CTL_FREE_LIST
-	TAILQ_INIT(&l->free_queue);
-#endif
-	TAILQ_INSERT_TAIL(&ctx->control_fds, l, next);
-	eloop_event_add(ctx->eloop, l->fd, control_handle_data, l);
+	if (eloop_event_add(ctx->eloop, l->fd, control_handle_data, l) == -1)
+		logerr(__func__);
 	return;
 
 error:
@@ -194,6 +274,26 @@
 }
 
 static int
+make_path(char *path, size_t len, const char *ifname, sa_family_t family)
+{
+	const char *per;
+
+	switch(family) {
+	case AF_INET:
+		per = "-4";
+		break;
+	case AF_INET6:
+		per = "-6";
+		break;
+	default:
+		per = "";
+		break;
+	}
+	return snprintf(path, len, CONTROLSOCKET,
+	    ifname ? ifname : "", ifname ? per : "", ifname ? "." : "");
+}
+
+static int
 make_sock(struct sockaddr_un *sa, const char *ifname, sa_family_t family,
     bool unpriv)
 {
@@ -205,23 +305,8 @@
 	sa->sun_family = AF_UNIX;
 	if (unpriv)
 		strlcpy(sa->sun_path, UNPRIVSOCKET, sizeof(sa->sun_path));
-	else {
-		const char *per;
-
-		switch(family) {
-		case AF_INET:
-			per = "-4";
-			break;
-		case AF_INET6:
-			per = "-6";
-			break;
-		default:
-			per = "";
-			break;
-		}
-		snprintf(sa->sun_path, sizeof(sa->sun_path), CONTROLSOCKET,
-		    ifname ? ifname : "", ifname ? per : "", ifname ? "." : "");
-	}
+	else
+		make_path(sa->sun_path, sizeof(sa->sun_path), ifname, family);
 	return fd;
 }
 
@@ -272,6 +357,14 @@
 {
 	int fd;
 
+#ifdef PRIVSEP_CONTROLLER
+	if (IN_PRIVSEP_SE(ctx)) {
+		make_path(ctx->control_sock, sizeof(ctx->control_sock),
+		    ifname, family);
+		return 0;
+	}
+#endif
+
 	if ((fd = control_start1(ctx, ifname, family, S_PRIV)) == -1)
 		return -1;
 
@@ -313,8 +406,21 @@
 	int retval = 0;
 	struct fd_list *l;
 
-	if (ctx->options & DHCPCD_FORKED)
-		return 0;
+	while ((l = TAILQ_FIRST(&ctx->control_fds)) != NULL) {
+		control_free(l);
+	}
+
+#ifdef PRIVSEP_CONTROLLER
+	if (IN_PRIVSEP_SE(ctx)) {
+		if (ps_root_unlink(ctx, ctx->control_sock) == -1)
+			retval = -1;
+		if (ctx->options & DHCPCD_MASTER &&
+		    control_unlink(ctx, UNPRIVSOCKET) == -1)
+			retval = -1;
+		return retval;
+	} else if (ctx->options & DHCPCD_FORKED)
+		return retval;
+#endif
 
 	if (ctx->control_fd != -1) {
 		eloop_event_delete(ctx->eloop, ctx->control_fd);
@@ -332,14 +438,6 @@
 			retval = -1;
 	}
 
-	while ((l = TAILQ_FIRST(&ctx->control_fds))) {
-		TAILQ_REMOVE(&ctx->control_fds, l, next);
-		eloop_event_delete(ctx->eloop, l->fd);
-		close(l->fd);
-		control_queue_free(l);
-		free(l);
-	}
-
 	return retval;
 }
 
@@ -390,23 +488,31 @@
 {
 	struct fd_list *fd;
 	struct iovec iov[2];
+	int iov_len;
 	struct fd_data *data;
 
 	fd = arg;
 	data = TAILQ_FIRST(&fd->queue);
-	iov[0].iov_base = &data->data_len;
-	iov[0].iov_len = sizeof(size_t);
-	iov[1].iov_base = data->data;
-	iov[1].iov_len = data->data_len;
-	if (writev(fd->fd, iov, 2) == -1) {
-		logerr(__func__);
-		if (errno != EINTR && errno != EAGAIN)
-			control_delete(fd);
+
+	if (data->data_flags & FD_SENDLEN) {
+		iov[0].iov_base = &data->data_len;
+		iov[0].iov_len = sizeof(size_t);
+		iov[1].iov_base = data->data;
+		iov[1].iov_len = data->data_len;
+		iov_len = 2;
+	} else {
+		iov[0].iov_base = data->data;
+		iov[0].iov_len = data->data_len;
+		iov_len = 1;
+	}
+
+	if (writev(fd->fd, iov, iov_len) == -1) {
+		logerr("%s: write", __func__);
+		control_delete(fd);
 		return;
 	}
 
 	TAILQ_REMOVE(&fd->queue, data, next);
-	fd->queue_len--;
 #ifdef CTL_FREE_LIST
 	TAILQ_INSERT_TAIL(&fd->free_queue, data, next);
 #else
@@ -415,30 +521,34 @@
 	free(data);
 #endif
 
-	if (TAILQ_FIRST(&fd->queue) == NULL)
-		eloop_event_remove_writecb(fd->ctx->eloop, fd->fd);
+	if (TAILQ_FIRST(&fd->queue) != NULL)
+		return;
+
+	eloop_event_remove_writecb(fd->ctx->eloop, fd->fd);
+#ifdef PRIVSEP_CONTROLLER
+	if (IN_PRIVSEP_SE(fd->ctx) && !(fd->flags & FD_LISTEN)) {
+		if (ps_ctl_sendeof(fd) == -1)
+			logerr(__func__);
+		control_free(fd);
+	}
+#endif
 }
 
 int
-control_queue(struct fd_list *fd, void *data, size_t data_len, bool fit)
+control_queue(struct fd_list *fd, void *data, size_t data_len)
 {
 	struct fd_data *d;
 
-	if (data_len == 0)
-		return 0;
+	if (data_len == 0) {
+		errno = EINVAL;
+		return -1;
+	}
 
 #ifdef CTL_FREE_LIST
 	struct fd_data *df;
 
 	d = NULL;
 	TAILQ_FOREACH(df, &fd->free_queue, next) {
-		if (!fit) {
-			if (df->data_size == 0) {
-				d = df;
-				break;
-			}
-			continue;
-		}
 		if (d == NULL || d->data_size < df->data_size) {
 			d = df;
 			if (d->data_size <= data_len)
@@ -450,28 +560,11 @@
 	else
 #endif
 	{
-		if (fd->queue_len == CONTROL_QUEUE_MAX) {
-			errno = ENOBUFS;
-			return -1;
-		}
-		fd->queue_len++;
 		d = calloc(1, sizeof(*d));
 		if (d == NULL)
 			return -1;
 	}
 
-	if (!fit) {
-#ifdef CTL_FREE_LIST
-		if (d->data_size != 0) {
-			free(d->data);
-			d->data_size = 0;
-		}
-#endif
-		d->data = data;
-		d->data_len = data_len;
-		goto queue;
-	}
-
 	if (d->data_size == 0)
 		d->data = NULL;
 	if (d->data_size < data_len) {
@@ -486,8 +579,8 @@
 	}
 	memcpy(d->data, data, data_len);
 	d->data_len = data_len;
+	d->data_flags = fd->flags & FD_SENDLEN;
 
-queue:
 	TAILQ_INSERT_TAIL(&fd->queue, d, next);
 	eloop_event_add_w(fd->ctx->eloop, fd->fd, control_writeone, fd);
 	return 0;
--- a/src/control.h	Tue Jun 09 17:56:03 2020 +0100
+++ b/src/control.h	Tue Jun 09 18:25:18 2020 +0100
@@ -47,6 +47,7 @@
 	void *data;
 	size_t data_size;
 	size_t data_len;
+	unsigned int data_flags;
 };
 TAILQ_HEAD(fd_data_head, fd_data);
 
@@ -56,20 +57,23 @@
 	int fd;
 	unsigned int flags;
 	struct fd_data_head queue;
-	size_t queue_len;
 #ifdef CTL_FREE_LIST
 	struct fd_data_head free_queue;
 #endif
 };
 TAILQ_HEAD(fd_list_head, fd_list);
 
-#define FD_LISTEN	(1<<0)
-#define FD_UNPRIV	(1<<1)
+#define	FD_LISTEN	0x01U
+#define	FD_UNPRIV	0x02U
+#define	FD_SENDLEN	0x04U
 
 int control_start(struct dhcpcd_ctx *, const char *, sa_family_t);
 int control_stop(struct dhcpcd_ctx *);
 int control_open(const char *, sa_family_t, bool);
 ssize_t control_send(struct dhcpcd_ctx *, int, char * const *);
-int control_queue(struct fd_list *, void *, size_t, bool);
-
+struct fd_list *control_new(struct dhcpcd_ctx *, int, unsigned int);
+void control_free(struct fd_list *);
+void control_delete(struct fd_list *);
+int control_queue(struct fd_list *, void *, size_t);
+void control_recvdata(struct fd_list *fd, char *, size_t);
 #endif
--- a/src/dhcpcd.c	Tue Jun 09 17:56:03 2020 +0100
+++ b/src/dhcpcd.c	Tue Jun 09 18:25:18 2020 +0100
@@ -1400,6 +1400,11 @@
 	unsigned long long opts;
 	int exit_code;
 
+	if (ctx->options & DHCPCD_DUMPLEASE) {
+		eloop_exit(ctx->eloop, EXIT_FAILURE);
+		return;
+	}
+
 	if (sig != SIGCHLD && ctx->options & DHCPCD_FORKED) {
 		pid_t pid = pidfile_read(ctx->pidfile);
 		if (pid == -1) {
@@ -1462,48 +1467,6 @@
 }
 #endif
 
-static void
-dhcpcd_getinterfaces(void *arg)
-{
-	struct fd_list *fd = arg;
-	struct interface *ifp;
-	size_t len;
-
-	len = 0;
-	TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) {
-		if (!ifp->active)
-			continue;
-		len++;
-#ifdef INET
-		if (D_STATE_RUNNING(ifp))
-			len++;
-#endif
-#ifdef IPV4LL
-		if (IPV4LL_STATE_RUNNING(ifp))
-			len++;
-#endif
-#ifdef INET6
-		if (IPV6_STATE_RUNNING(ifp))
-			len++;
-		if (RS_STATE_RUNNING(ifp))
-			len++;
-#endif
-#ifdef DHCP6
-		if (D6_STATE_RUNNING(ifp))
-			len++;
-#endif
-	}
-	if (write(fd->fd, &len, sizeof(len)) != sizeof(len))
-		return;
-	eloop_event_remove_writecb(fd->ctx->eloop, fd->fd);
-	TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) {
-		if (!ifp->active)
-			continue;
-		if (send_interface(fd, ifp, AF_UNSPEC) == -1)
-			logerr(__func__);
-	}
-}
-
 int
 dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd,
     int argc, char **argv)
@@ -1511,23 +1474,23 @@
 	struct interface *ifp;
 	unsigned long long opts;
 	int opt, oi, do_reboot, do_renew, af = AF_UNSPEC;
-	size_t len, l;
+	size_t len, l, nifaces;
 	char *tmp, *p;
 
 	/* Special commands for our control socket
 	 * as the other end should be blocking until it gets the
 	 * expected reply we should be safely able just to change the
 	 * write callback on the fd */
+	/* Make any change here in privsep-control.c as well. */
 	if (strcmp(*argv, "--version") == 0) {
 		return control_queue(fd, UNCONST(VERSION),
-		    strlen(VERSION) + 1, false);
+		    strlen(VERSION) + 1);
 	} else if (strcmp(*argv, "--getconfigfile") == 0) {
 		return control_queue(fd, UNCONST(fd->ctx->cffile),
-		    strlen(fd->ctx->cffile) + 1, false);
+		    strlen(fd->ctx->cffile) + 1);
 	} else if (strcmp(*argv, "--getinterfaces") == 0) {
-		eloop_event_add_w(fd->ctx->eloop, fd->fd,
-		    dhcpcd_getinterfaces, fd);
-		return 0;
+		optind = argc = 0;
+		goto dumplease;
 	} else if (strcmp(*argv, "--listen") == 0) {
 		fd->flags |= FD_LISTEN;
 		return 0;
@@ -1591,8 +1554,8 @@
 
 	if (opts & DHCPCD_DUMPLEASE) {
 		ctx->options |= DHCPCD_DUMPLEASE;
-		size_t nifaces = 0;
-
+dumplease:
+		nifaces = 0;
 		TAILQ_FOREACH(ifp, ctx->ifaces, next) {
 			if (!ifp->active)
 				continue;
@@ -1672,68 +1635,113 @@
 	return 0;
 }
 
+static void dhcpcd_readdump1(void *);
+
+static void
+dhcpcd_readdump2(void *arg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+	ssize_t len;
+	int exit_code = EXIT_FAILURE;
+
+	len = read(ctx->control_fd, ctx->ctl_buf + ctx->ctl_bufpos,
+	    ctx->ctl_buflen - ctx->ctl_bufpos);
+	if (len == -1) {
+		logerr(__func__);
+		goto finished;
+	} else if (len == 0)
+		goto finished;
+	if ((size_t)len + ctx->ctl_bufpos != ctx->ctl_buflen) {
+		ctx->ctl_bufpos += (size_t)len;
+		return;
+	}
+
+	script_dump(ctx->ctl_buf, ctx->ctl_buflen);
+	fflush(stdout);
+	if (--ctx->ctl_extra != 0) {
+		putchar('\n');
+		eloop_event_add(ctx->eloop, ctx->control_fd,
+		    dhcpcd_readdump1, ctx);
+		return;
+	}
+	exit_code = EXIT_SUCCESS;
+
+finished:
+	shutdown(ctx->control_fd, SHUT_RDWR);
+	eloop_exit(ctx->eloop, exit_code);
+}
+
+static void
+dhcpcd_readdump1(void *arg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+	ssize_t len;
+
+	len = read(ctx->control_fd, &ctx->ctl_buflen, sizeof(ctx->ctl_buflen));
+	if (len != sizeof(ctx->ctl_buflen)) {
+		if (len != -1)
+			errno = EINVAL;
+		goto err;
+	}
+
+	free(ctx->ctl_buf);
+	ctx->ctl_buf = malloc(ctx->ctl_buflen);
+	if (ctx->ctl_buf == NULL)
+		goto err;
+
+	ctx->ctl_bufpos = 0;
+	eloop_event_add(ctx->eloop, ctx->control_fd,
+	    dhcpcd_readdump2, ctx);
+	return;
+
+err:
+	logerr(__func__);
+	eloop_exit(ctx->eloop, EXIT_FAILURE);
+}
+
+static void
+dhcpcd_readdump0(void *arg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+	ssize_t len;
+
+	len = read(ctx->control_fd, &ctx->ctl_extra, sizeof(ctx->ctl_extra));
+	if (len != sizeof(ctx->ctl_extra)) {
+		if (len != -1)
+			errno = EINVAL;
+		logerr(__func__);
+		eloop_exit(ctx->eloop, EXIT_FAILURE);
+		return;
+	}
+
+	if (ctx->ctl_extra == 0) {
+		eloop_exit(ctx->eloop, EXIT_SUCCESS);
+		return;
+	}
+
+	eloop_event_add(ctx->eloop, ctx->control_fd,
+	    dhcpcd_readdump1, ctx);
+}
+
+static void
+dhcpcd_readdumptimeout(void *arg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+
+	logerrx(__func__);
+	eloop_exit(ctx->eloop, EXIT_FAILURE);
+}
+
 static int
 dhcpcd_readdump(struct dhcpcd_ctx *ctx)
 {
-	int error = 0;
-	size_t nifaces, buflen = 0, dlen;
-	ssize_t len;
-	char *buf = NULL;
 
-again1:
-	len = read(ctx->control_fd, &nifaces, sizeof(nifaces));
-	if (len == -1) {
-		if (errno == EAGAIN)
-			goto again1;
-		return -1;
-	}
-	if (len != sizeof(nifaces)) {
-		errno = EINVAL;
+	ctx->options |=	DHCPCD_FORKED;
+	if (eloop_timeout_add_sec(ctx->eloop, 5,
+	    dhcpcd_readdumptimeout, ctx) == -1)
 		return -1;
-	}
-	for (; nifaces > 0; nifaces--) {
-again2:
-		len = read(ctx->control_fd, &dlen, sizeof(dlen));
-		if (len == -1) {
-			if (errno == EAGAIN)
-				goto again2;
-			error = -1;
-			goto out;
-		}
-		if (len != sizeof(dlen)) {
-			errno = EINVAL;
-			goto out;
-		}
-		if (dlen > buflen) {
-			char *nbuf = realloc(buf, dlen);
-			if (nbuf == NULL) {
-				error = -1;
-				goto out;
-			}
-			buf = nbuf;
-			buflen = dlen;
-		}
-		if (dlen == 0) {
-			errno = EINVAL;
-			error = -1;
-			goto out;
-		}
-again3:
-		if (read(ctx->control_fd, buf, dlen) != (ssize_t)dlen) {
-			if (errno == EAGAIN)
-				goto again3;
-			error = -1;
-			goto out;
-		}
-		script_dump(buf, dlen);
-		fflush(stdout);
-		if (nifaces != 1)
-			putchar('\n');
-	}
-
-out:
-	free(buf);
-	return error;
+	return eloop_event_add(ctx->eloop, ctx->control_fd,
+	    dhcpcd_readdump0, ctx);
 }
 
 static void
@@ -1844,6 +1852,9 @@
 #endif
 #ifdef PRIVSEP
 	ctx.ps_root_fd = ctx.ps_data_fd = -1;
+#ifdef PRIVSEP_COMTROLLER
+	ctx.ps_ctl_fd = -1;
+#endif
 	TAILQ_INIT(&ctx.ps_processes);
 #endif
 	rt_init(&ctx);
@@ -2156,6 +2167,7 @@
 					logerr("%s: dhcpcd_readdump", __func__);
 					goto exit_failure;
 				}
+				goto run_loop;
 			}
 			goto exit_success;
 		} else {
@@ -2166,6 +2178,8 @@
 					logerrx("dhcpcd is not running");
 				goto exit_failure;
 			}
+			if (errno == EPERM || errno == EACCES)
+				goto exit_failure;
 		}
 		ctx.options &= ~DHCPCD_FORKED;
 	}
@@ -2260,7 +2274,7 @@
 	}
 
 #ifdef PRIVSEP
-	if (ctx.options & DHCPCD_PRIVSEP && ps_start(&ctx) == -1) {
+	if (IN_PRIVSEP(&ctx) && ps_start(&ctx) == -1) {
 		logerr("ps_start");
 		goto exit_failure;
 	}
@@ -2268,12 +2282,14 @@
 		goto run_loop;
 #endif
 
-	if (!(ctx.options & DHCPCD_TEST) &&
-	    control_start(&ctx,
-	    ctx.options & DHCPCD_MASTER ? NULL : argv[optind], family) == -1)
-	{
-		logerr("%s: control_start", __func__);
-		goto exit_failure;
+	if (!(ctx.options & DHCPCD_TEST)) {
+		if (control_start(&ctx,
+		    ctx.options & DHCPCD_MASTER ?
+		    NULL : argv[optind], family) == -1)
+		{
+			logerr("%s: control_start", __func__);
+			goto exit_failure;
+		}
 	}
 
 #ifdef PLUGIN_DEV
@@ -2479,6 +2495,7 @@
 		loginfox(PACKAGE " exited");
 	logclose();
 	free(ctx.logfile);
+	free(ctx.ctl_buf);
 #ifdef SETPROCTITLE_H
 	setproctitle_free();
 #endif
--- a/src/dhcpcd.h	Tue Jun 09 17:56:03 2020 +0100
+++ b/src/dhcpcd.h	Tue Jun 09 18:25:18 2020 +0100
@@ -144,6 +144,11 @@
 	size_t duid_len;
 	struct if_head *ifaces;
 
+	char *ctl_buf;
+	size_t ctl_buflen;
+	size_t ctl_bufpos;
+	size_t ctl_extra;
+
 	rb_tree_t routes;	/* our routes */
 #ifdef RT_FREE_ROUTE_TABLE
 	rb_tree_t froutes;	/* free routes for re-use */
@@ -204,6 +209,13 @@
 	struct ps_process_head ps_processes;	/* List of spawned processes */
 	pid_t ps_inet_pid;
 	int ps_inet_fd;		/* Network Proxy commands and data */
+#ifdef PRIVSEP_CONTROLLER
+	pid_t ps_control_pid;
+	int ps_control_fd;
+	int ps_control_data_fd;
+	struct fd_list *ps_control;
+	struct fd_list *ps_control_client;
+#endif
 #endif
 
 #ifdef INET
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/privsep-control.c	Tue Jun 09 18:25:18 2020 +0100
@@ -0,0 +1,339 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd, control proxy
+ * Copyright (c) 2006-2020 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/resource.h>
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "dhcpcd.h"
+#include "control.h"
+#include "eloop.h"
+#include "logerr.h"
+#include "privsep.h"
+
+int
+ps_ctl_limitresources(struct dhcpcd_ctx *ctx)
+{
+	struct rlimit rzero = { .rlim_cur = 0, .rlim_max = 0 };
+
+	if (ctx->ps_control_pid != getpid()) {
+		/* Prohibit new files, sockets, etc */
+		if (setrlimit(RLIMIT_NOFILE, &rzero) == -1) {
+			logerr("setrlimit RLIMIT_NOFILE");
+			return -1;
+		}
+	}
+
+	/* Prohibit large files */
+	if (setrlimit(RLIMIT_FSIZE, &rzero) == -1) {
+		logerr("setrlimit RLIMIT_FSIZE");
+		return -1;
+	}
+
+	/* Prohibit forks */
+	if (setrlimit(RLIMIT_NPROC, &rzero) == -1) {
+		logerr("setrlimit RLIMIT_NPROC");
+		return -1;
+	}
+
+	return 0;
+}
+
+static int
+ps_ctl_startcb(void *arg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+	sa_family_t af;
+
+	if (ctx->options & DHCPCD_MASTER) {
+		setproctitle("[control proxy]");
+		af = AF_UNSPEC;
+	} else {
+		setproctitle("[control proxy] %s%s%s",
+		    ctx->ifv[0],
+		    ctx->options & DHCPCD_IPV4 ? " [ip4]" : "",
+		    ctx->options & DHCPCD_IPV6 ? " [ip6]" : "");
+		if ((ctx->options &
+		    (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV4)
+			af = AF_INET;
+		else if ((ctx->options &
+		    (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV6)
+			af = AF_INET6;
+		else
+			af = AF_UNSPEC;
+	}
+
+	ctx->ps_control_pid = getpid();
+
+	return control_start(ctx,
+	    ctx->options & DHCPCD_MASTER ? NULL : *ctx->ifv, af);
+}
+
+static ssize_t
+ps_ctl_recvmsgcb(void *arg, struct ps_msghdr *psm, __unused struct msghdr *msg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+
+	if (psm->ps_cmd != PS_CTL_EOF) {
+		errno = ENOTSUP;
+		return -1;
+	}
+
+	if (ctx->ps_control_client != NULL)
+		ctx->ps_control_client = NULL;
+	return 0;
+}
+
+static void
+ps_ctl_recvmsg(void *arg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+
+	if (ps_recvpsmsg(ctx, ctx->ps_control_fd, ps_ctl_recvmsgcb, ctx) == -1)
+		logerr(__func__);
+}
+
+static void
+ps_ctl_signalcb(int sig, void *arg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+
+	/* Ignore SIGINT, respect PS_STOP command or SIGTERM. */
+	if (sig == SIGINT)
+		return;
+
+	shutdown(ctx->ps_control_fd, SHUT_RDWR);
+	eloop_exit(ctx->eloop, sig == SIGTERM ? EXIT_SUCCESS : EXIT_FAILURE);
+}
+
+ssize_t
+ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len)
+{
+
+	/* Make any change here in dhcpcd.c as well. */
+	if (strncmp(data, "--version",
+	    MIN(strlen("--version"), len)) == 0) {
+		return control_queue(fd, UNCONST(VERSION),
+		    strlen(VERSION) + 1);
+	} else if (strncmp(data, "--getconfigfile",
+	    MIN(strlen("--getconfigfile"), len)) == 0) {
+		return control_queue(fd, UNCONST(fd->ctx->cffile),
+		    strlen(fd->ctx->cffile) + 1);
+	} else if (strncmp(data, "--listen",
+	    MIN(strlen("--listen"), len)) == 0) {
+		fd->flags |= FD_LISTEN;
+		return 0;
+	}
+
+	if (fd->ctx->ps_control_client != NULL &&
+	    fd->ctx->ps_control_client != fd)
+	{
+		logerrx("%s: cannot handle another client", __func__);
+		return 0;
+	}
+	return 1;
+}
+
+static ssize_t
+ps_ctl_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+	struct iovec *iov = msg->msg_iov;
+	struct fd_list *fd;
+	unsigned int fd_flags = FD_SENDLEN;
+
+	switch (psm->ps_flags) {
+	case PS_CTL_PRIV:
+		break;
+	case PS_CTL_UNPRIV:
+		fd_flags |= FD_UNPRIV;
+		break;
+	}
+
+	switch (psm->ps_cmd) {
+	case PS_CTL:
+		if (msg->msg_iovlen != 1) {
+			errno = EINVAL;
+			return -1;
+		}
+		if (ctx->ps_control_client != NULL) {
+			logerrx("%s: cannot handle another client", __func__);
+			return 0;
+		}
+		fd = control_new(ctx, ctx->ps_control_data_fd, fd_flags);
+		if (fd == NULL)
+			return -1;
+		ctx->ps_control_client = fd;
+		control_recvdata(fd, iov->iov_base, iov->iov_len);
+		break;
+	case PS_CTL_EOF:
+		fd = ctx->ps_control_client;
+		control_free(ctx->ps_control_client);
+		break;
+	default:
+		errno = ENOTSUP;
+		return -1;
+	}
+	return 0;
+}
+
+static void
+ps_ctl_dodispatch(void *arg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+
+	if (ps_recvpsmsg(ctx, ctx->ps_control_fd, ps_ctl_dispatch, ctx) == -1)
+		logerr(__func__);
+}
+
+static void
+ps_ctl_recv(void *arg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+	char buf[BUFSIZ];
+	ssize_t len;
+
+	errno = 0;
+	len = read(ctx->ps_control_data_fd, buf, sizeof(buf));
+	if (len == -1 || len == 0) {
+		logerr("%s: read", __func__);
+		eloop_exit(ctx->eloop, EXIT_FAILURE);
+		return;
+	}
+	if (ctx->ps_control_client == NULL) /* client disconnected */
+		return;
+	errno = 0;
+	if (control_queue(ctx->ps_control_client, buf, (size_t)len) == -1)
+		logerr("%s: control_queue", __func__);
+}
+
+static void
+ps_ctl_listen(void *arg)
+{
+	struct dhcpcd_ctx *ctx = arg;
+	char buf[BUFSIZ];
+	ssize_t len;
+	struct fd_list *fd;
+
+	errno = 0;
+	len = read(ctx->ps_control->fd, buf, sizeof(buf));
+	if (len == -1 || len == 0) {
+		logerr("%s: read", __func__);
+		eloop_exit(ctx->eloop, EXIT_FAILURE);
+		return;
+	}
+
+	/* Send to our listeners */
+	TAILQ_FOREACH(fd, &ctx->control_fds, next) {
+		if (!(fd->flags & FD_LISTEN))
+			continue;
+		if (control_queue(fd, buf, (size_t)len)== -1)
+			logerr("%s: control_queue", __func__);
+	}
+}
+
+pid_t
+ps_ctl_start(struct dhcpcd_ctx *ctx)
+{
+	int data_fd[2], listen_fd[2];
+	pid_t pid;
+
+	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, data_fd) == -1)
+		return -1;
+	if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, listen_fd) == -1)
+		return -1;
+#ifdef PRIVSEP_RIGHTS
+	if (ps_rights_limit_fdpair(data_fd) == -1)
+		return -1;
+	if (ps_rights_limit_fdpair(listen_fd) == -1)
+		return -1;
+#endif
+
+	pid = ps_dostart(ctx, &ctx->ps_control_pid, &ctx->ps_control_fd,
+	    ps_ctl_recvmsg, ps_ctl_dodispatch, ctx,
+	    ps_ctl_startcb, ps_ctl_signalcb,
+	    PSF_DROPPRIVS);
+
+	if (pid != 0) {
+		ctx->ps_control_data_fd = data_fd[1];
+		close(data_fd[0]);
+		ctx->ps_control = control_new(ctx,
+		    listen_fd[1], FD_SENDLEN | FD_LISTEN);
+		if (ctx->ps_control == NULL)
+			return -1;
+		close(listen_fd[0]);
+		return pid;
+	} else if (pid == -1)
+		return -1;
+
+	ctx->ps_control_data_fd = data_fd[0];
+	close(data_fd[1]);
+	if (eloop_event_add(ctx->eloop, ctx->ps_control_data_fd,
+	    ps_ctl_recv, ctx) == -1)
+		return -1;
+
+	ctx->ps_control = control_new(ctx,
+	    listen_fd[0], 0);
+	close(listen_fd[1]);
+	if (ctx->ps_control == NULL)
+		return -1;
+	if (eloop_event_add(ctx->eloop, ctx->ps_control->fd,
+	    ps_ctl_listen, ctx) == -1)
+		return -1;
+	return 0;
+}
+
+int
+ps_ctl_stop(struct dhcpcd_ctx *ctx)
+{
+
+	return ps_dostop(ctx, &ctx->ps_control_pid, &ctx->ps_control_fd);
+}
+
+ssize_t
+ps_ctl_sendargs(struct fd_list *fd, void *data, size_t len)
+{
+	struct dhcpcd_ctx *ctx = fd->ctx;
+
+	if (ctx->ps_control_client != NULL && ctx->ps_control_client != fd)
+		logerrx("%s: cannot deal with another client", __func__);
+	ctx->ps_control_client = fd;
+	return ps_sendcmd(ctx, ctx->ps_control_fd, PS_CTL,
+	    fd->flags & FD_UNPRIV ? PS_CTL_UNPRIV : PS_CTL_PRIV,
+	    data, len);
+}
+
+ssize_t
+ps_ctl_sendeof(struct fd_list *fd)
+{
+	struct dhcpcd_ctx *ctx = fd->ctx;
+
+	return ps_sendcmd(ctx, ctx->ps_control_fd, PS_CTL_EOF, 0, NULL, 0);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/privsep-control.h	Tue Jun 09 18:25:18 2020 +0100
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: BSD-2-Clause */
+/*
+ * Privilege Separation for dhcpcd
+ * Copyright (c) 2006-2020 Roy Marples <roy@marples.name>
+ * All rights reserved
+
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef PRIVSEP_CTL_H
+#define PRIVSEP_CTL_H
+
+#define IN_PRIVSEP_CONTROLLER(ctx) \
+    (IN_PRIVSEP((ctx)) && (ctx)->ps_control_pid == getpid())
+
+int ps_ctl_limitresources(struct dhcpcd_ctx *);
+pid_t ps_ctl_start(struct dhcpcd_ctx *);
+int ps_ctl_stop(struct dhcpcd_ctx *);
+ssize_t ps_ctl_handleargs(struct fd_list *, char *, size_t);
+ssize_t ps_ctl_sendargs(struct fd_list *, void *, size_t);
+ssize_t ps_ctl_sendeof(struct fd_list *fd);
+
+#endif
--- a/src/privsep.c	Tue Jun 09 17:56:03 2020 +0100
+++ b/src/privsep.c	Tue Jun 09 18:25:18 2020 +0100
@@ -39,7 +39,6 @@
  * this in a script or something.
  */
 
-#include <sys/resource.h>
 #include <sys/socket.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -129,36 +128,11 @@
 		return -1;
 	}
 
-#if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE)
-	/* Resource limits are not needed for these sandboxes */
-#else
-	struct rlimit rzero = { .rlim_cur = 0, .rlim_max = 0 };
-
-	/* We can't use RLIMIT_NOFILE because that breaks our control socket.
-	 * XXX Offload to a new process? */
-#if 0
-#ifndef __linux__ /* breaks ppoll */
-	/* Prohibit new files, sockets, etc */
-	if (setrlimit(RLIMIT_NOFILE, &rzero) == -1) {
-		logerr("setrlimit RLIMIT_NOFILE");
+#ifdef PRIVSEP_CONTROLLER
+	if (ps_ctl_limitresources(ctx) == -1)
 		return -1;
-	}
-#endif
-#endif
-
-	/* Prohibit large files */
-	if (setrlimit(RLIMIT_FSIZE, &rzero) == -1) {
-		logerr("setrlimit RLIMIT_FSIZE");
-		return -1;
-	}
-
-#ifdef RLIMIT_NPROC
-	/* Prohibit forks */
-	if (setrlimit(RLIMIT_NPROC, &rzero) == -1) {
-		logerr("setrlimit RLIMIT_NPROC");
-		return -1;
-	}
-#endif
+#elif !defined(HAVE_CAPSIUM) && !defined(HAVE_PLEDGE)
+#warning No sandbox support
 #endif
 
 	return 0;
@@ -437,6 +411,18 @@
 	}
 
 started:
+#ifdef PRIVSEP_CONTROLLER
+	if (!(ctx->options & DHCPCD_TEST)) {
+		switch (pid = ps_ctl_start(ctx)) {
+		case -1:
+			return -1;
+		case 0:
+			return 0;
+		default:
+			logdebugx("spawned controller on PID %d", pid);
+		}
+	}
+#endif
 
 #ifdef ARC4RANDOM_H
 	/* Seed the random number generator early incase it needs /dev/urandom
@@ -491,6 +477,12 @@
 	    ctx->eloop == NULL)
 		return 0;
 
+#ifdef PRIVSEP_CONTROLLER
+	r = ps_ctl_stop(ctx);
+	if (r != 0)
+		ret = r;
+#endif
+
 	r = ps_inet_stop(ctx);
 	if (r != 0)
 		ret = r;
--- a/src/privsep.h	Tue Jun 09 17:56:03 2020 +0100
+++ b/src/privsep.h	Tue Jun 09 18:25:18 2020 +0100
@@ -50,6 +50,8 @@
 #define	PS_WRITEFILE		0x0015
 #define	PS_FILEMTIME		0x0016
 #define	PS_AUTH_MONORDM		0x0017
+#define	PS_CTL			0x0018
+#define	PS_CTL_EOF		0x0019
 
 /* BSD Commands */
 #define	PS_IOCTLLINK		0x0101
@@ -69,6 +71,10 @@
 #define	PS_DEV_IFREMOVED	0x0002
 #define	PS_DEV_IFUPDATED	0x0003
 
+/* Control Type (via flags) */
+#define	PS_CTL_PRIV		0x0301
+#define	PS_CTL_UNPRIV		0x0302
+
 /* Process commands */
 #define	PS_START		0x4000
 #define	PS_STOP			0x8000
@@ -157,6 +163,9 @@
 #ifdef INET
 #include "privsep-bpf.h"
 #endif
+#ifdef PRIVSEP_CONTROLLER
+#include "privsep-control.h"
+#endif
 
 int ps_init(struct dhcpcd_ctx *);
 int ps_dropprivs(struct dhcpcd_ctx *);
--- a/src/script.c	Tue Jun 09 17:56:03 2020 +0100
+++ b/src/script.c	Tue Jun 09 18:25:18 2020 +0100
@@ -560,7 +560,7 @@
 	len = make_env(ifp->ctx, ifp, reason);
 	if (len == -1)
 		return -1;
-	return control_queue(fd, ctx->script_buf, (size_t)len, 1);
+	return control_queue(fd, ctx->script_buf, (size_t)len);
 }
 
 int
@@ -752,8 +752,7 @@
 	TAILQ_FOREACH(fd, &ctx->control_fds, next) {
 		if (!(fd->flags & FD_LISTEN))
 			continue;
-		if (control_queue(fd, ctx->script_buf, ctx->script_buflen,
-		    true) == -1)
+		if (control_queue(fd, ctx->script_buf, ctx->script_buflen)== -1)
 			logerr("%s: control_queue", __func__);
 		else
 			status = 1;