changeset 5223:333f66ce84bd draft

privsep: Add a generic wrapper for getifaddrs(3) Although this is only for Capsicum, the getifaddrs interface is quite portable although not POSIX. With this final change, the Master process can now enter Capsicum Capabilites Mode and this completes the Capsicum integration.
author Roy Marples <roy@marples.name>
date Wed, 13 May 2020 20:52:24 +0100
parents 3d4511e7280c
children a71e4a05aa61
files src/dhcpcd.c src/if.c src/privsep-root.c src/privsep-root.h src/privsep.c src/privsep.h
diffstat 6 files changed, 280 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/src/dhcpcd.c	Wed May 13 20:50:45 2020 +0100
+++ b/src/dhcpcd.c	Wed May 13 20:52:24 2020 +0100
@@ -2263,14 +2263,7 @@
 
 #ifdef PRIVSEP
 	if (ctx.options & DHCPCD_PRIVSEP) {
-		/*
-		 * PSF_CAP_ENTER is not set because getifaddrs(3) won't
-		 * work in it. This is a huge challenge because it's the
-		 * only portable interface to work stuff out and it's
-		 * non trivial to IPC for privsep.
-		 * There could be more blockers, it's as far as I've got.
-		 */
-		if (ps_dropprivs(&ctx, PSF_PLEDGE) == -1) {
+		if (ps_dropprivs(&ctx, PSF_CAP_ENTER | PSF_PLEDGE) == -1) {
 			logerr("ps_dropprivs");
 			goto exit_failure;
 		}
--- a/src/if.c	Wed May 13 20:50:45 2020 +0100
+++ b/src/if.c	Wed May 13 20:52:24 2020 +0100
@@ -392,12 +392,22 @@
 		logerr(__func__);
 		return NULL;
 	}
+	TAILQ_INIT(ifs);
+
+#ifdef HAVE_CAPSICUM
+	if (ctx->options & DHCPCD_PRIVSEP) {
+		if (ps_root_getifaddrs(ctx, ifaddrs) == -1) {
+			logerr("ps_root_getifaddrs");
+			free(ifs);
+			return NULL;
+		}
+	} else
+#endif
 	if (getifaddrs(ifaddrs) == -1) {
 		logerr("getifaddrs");
 		free(ifs);
 		return NULL;
 	}
-	TAILQ_INIT(ifs);
 
 #ifdef IFLR_ACTIVE
 	link_fd = xsocket(PF_LINK, SOCK_DGRAM | SOCK_CLOEXEC, 0);
--- a/src/privsep-root.c	Wed May 13 20:50:45 2020 +0100
+++ b/src/privsep-root.c	Wed May 13 20:52:24 2020 +0100
@@ -48,6 +48,7 @@
 #include "if.h"
 #include "logerr.h"
 #include "privsep.h"
+#include "sa.h"
 #include "script.h"
 
 __CTASSERT(sizeof(ioctl_request_t) <= sizeof(unsigned long));
@@ -57,6 +58,7 @@
 	ssize_t psr_result;
 	int psr_errno;
 	char psr_pad[sizeof(ssize_t) - sizeof(int)];
+	size_t psr_datalen;
 };
 
 struct psr_ctx {
@@ -88,18 +90,21 @@
 	ssize_t len;
 	int exit_code = EXIT_FAILURE;
 
+#define PSR_ERROR(e)				\
+	do {					\
+		psr_error->psr_result = -1;	\
+		psr_error->psr_errno = (e);	\
+		goto out;			\
+	} while (0 /* CONSTCOND */)
+
 	len = readv(ctx->ps_root_fd, iov, __arraycount(iov));
-	if (len == 0 || len == -1) {
-		logerr(__func__);
-		psr_error->psr_result = -1;
-		psr_error->psr_errno = errno;
-	} else if ((size_t)len < sizeof(*psr_error)) {
-		logerrx("%s: psr_error truncated", __func__);
-		psr_error->psr_result = -1;
-		psr_error->psr_errno = EINVAL;
-	} else
-		exit_code = EXIT_SUCCESS;
+	if (len == -1)
+		PSR_ERROR(errno);
+	else if ((size_t)len < sizeof(*psr_error))
+		PSR_ERROR(EINVAL);
+	exit_code = EXIT_SUCCESS;
 
+out:
 	eloop_exit(ctx->ps_eloop, exit_code);
 }
 
@@ -113,10 +118,7 @@
 
 	if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd,
 	    ps_root_readerrorcb, &psr_ctx) == -1)
-	{
-		logerr(__func__);
 		return -1;
-	}
 
 	eloop_start(ctx->ps_eloop, &ctx->sigset);
 
@@ -124,6 +126,66 @@
 	return psr_ctx.psr_error.psr_result;
 }
 
+#ifdef HAVE_CAPSICUM
+static void
+ps_root_mreaderrorcb(void *arg)
+{
+	struct psr_ctx *psr_ctx = arg;
+	struct dhcpcd_ctx *ctx = psr_ctx->psr_ctx;
+	struct psr_error *psr_error = &psr_ctx->psr_error;
+	struct iovec iov[] = {
+		{ .iov_base = psr_error, .iov_len = sizeof(*psr_error) },
+		{ .iov_base = NULL, .iov_len = 0 },
+	};
+	ssize_t len;
+	int exit_code = EXIT_FAILURE;
+
+	len = recv(ctx->ps_root_fd, psr_error, sizeof(*psr_error), MSG_PEEK);
+	if (len == -1)
+		PSR_ERROR(errno);
+	else if ((size_t)len < sizeof(*psr_error))
+		PSR_ERROR(EINVAL);
+
+	if (psr_error->psr_datalen != 0) {
+		psr_ctx->psr_data = malloc(psr_error->psr_datalen);
+		if (psr_ctx->psr_data == NULL)
+			PSR_ERROR(errno);
+		psr_ctx->psr_datalen = psr_error->psr_datalen;
+		iov[1].iov_base = psr_ctx->psr_data;
+		iov[1].iov_len = psr_ctx->psr_datalen;
+	}
+
+	len = readv(ctx->ps_root_fd, iov, __arraycount(iov));
+	if (len == -1)
+		PSR_ERROR(errno);
+	else if ((size_t)len != sizeof(*psr_error) + psr_ctx->psr_datalen)
+		PSR_ERROR(EINVAL);
+	exit_code = EXIT_SUCCESS;
+
+out:
+	eloop_exit(ctx->ps_eloop, exit_code);
+}
+
+ssize_t
+ps_root_mreaderror(struct dhcpcd_ctx *ctx, void **data, size_t *len)
+{
+	struct psr_ctx psr_ctx = {
+	    .psr_ctx = ctx,
+	};
+
+	if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd,
+	    ps_root_mreaderrorcb, &psr_ctx) == -1)
+		return -1;
+
+	eloop_start(ctx->ps_eloop, &ctx->sigset);
+
+	errno = psr_ctx.psr_error.psr_errno;
+	*data = psr_ctx.psr_data;
+	*len = psr_ctx.psr_datalen;
+	return psr_ctx.psr_error.psr_result;
+}
+#endif
+
 static ssize_t
 ps_root_writeerror(struct dhcpcd_ctx *ctx, ssize_t result,
     void *data, size_t len)
@@ -131,6 +193,7 @@
 	struct psr_error psr = {
 		.psr_result = result,
 		.psr_errno = errno,
+		.psr_datalen = len,
 	};
 	struct iovec iov[] = {
 		{ .iov_base = &psr, .iov_len = sizeof(psr) },
@@ -231,6 +294,89 @@
 	return writefile(file, mode, nc, len - (size_t)(nc - file));
 }
 
+#ifdef HAVE_CAPSICUM
+#define	IFA_NADDRS	3
+static ssize_t
+ps_root_dogetifaddrs(void **rdata, size_t *rlen)
+{
+	struct ifaddrs *ifaddrs, *ifa;
+	size_t len;
+	uint8_t *buf, *sap;
+	void *ifdata;
+
+	if (getifaddrs(&ifaddrs) == -1)
+		return -1;
+	if (ifaddrs == NULL) {
+		*rdata = NULL;
+		*rlen = 0;
+		return 0;
+	}
+
+	/* Work out the buffer length required.
+	 * Ensure everything is aligned correctly, which does
+	 * create a larger buffer than what is needed to send,
+	 * but makes creating the same structure in the client
+	 * much easier. */
+	len = 0;
+	for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
+		len += ALIGN(sizeof(*ifa));
+		len += ALIGN(IFNAMSIZ);
+		len += ALIGN(sizeof(*sap) * IFA_NADDRS);
+		if (ifa->ifa_addr != NULL)
+			len += ALIGN(sa_len(ifa->ifa_addr));
+		if (ifa->ifa_netmask != NULL)
+			len += ALIGN(sa_len(ifa->ifa_netmask));
+		if (ifa->ifa_broadaddr != NULL)
+			len += ALIGN(sa_len(ifa->ifa_broadaddr));
+	}
+
+	/* Use calloc to set everything to zero.
+	 * This satisfies memory sanitizers because don't write
+	 * where we don't need to. */
+	buf = calloc(1, len);
+	if (buf == NULL) {
+		freeifaddrs(ifaddrs);
+		return -1;
+	}
+	*rdata = buf;
+	*rlen = len;
+
+	for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
+		/* Don't carry ifa_data. */
+		ifdata = ifa->ifa_data;
+		ifa->ifa_data = NULL;
+		memcpy(buf, ifa, sizeof(*ifa));
+		buf += ALIGN(sizeof(*ifa));
+		ifa->ifa_data = ifdata;
+
+		strlcpy((char *)buf, ifa->ifa_name, IFNAMSIZ);
+		buf += ALIGN(IFNAMSIZ);
+		sap = buf;
+		buf += ALIGN(sizeof(*sap) * IFA_NADDRS);
+
+#define	COPYINSA(addr)					\
+	do {						\
+		*sap = sa_len((addr));			\
+		if (*sap != 0) {			\
+			memcpy(buf, (addr), *sap);	\
+			buf += ALIGN(*sap);		\
+		}					\
+		sap++;					\
+	} while (0 /*CONSTCOND */)
+
+		if (ifa->ifa_addr != NULL)
+			COPYINSA(ifa->ifa_addr);
+		if (ifa->ifa_netmask != NULL)
+			COPYINSA(ifa->ifa_netmask);
+		if (ifa->ifa_broadaddr != NULL)
+			COPYINSA(ifa->ifa_broadaddr);
+	}
+
+	freeifaddrs(ifaddrs);
+	return 0;
+}
+#endif
+
 static ssize_t
 ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg)
 {
@@ -238,12 +384,12 @@
 	uint16_t cmd;
 	struct ps_process *psp;
 	struct iovec *iov = msg->msg_iov;
-	void *data = iov->iov_base;
-	size_t len = iov->iov_len;
+	void *data = iov->iov_base, *rdata = NULL;
+	size_t len = iov->iov_len, rlen = 0;
 	uint8_t buf[PS_BUFLEN];
 	time_t mtime;
 	ssize_t err;
-	bool retdata = false;
+	bool free_rdata= false;
 
 	cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP | PS_DELETE));
 	psp = ps_findprocess(ctx, &psm->ps_id);
@@ -299,7 +445,10 @@
 	switch (psm->ps_cmd) {
 	case PS_IOCTL:
 		err = ps_root_doioctl(psm->ps_flags, data, len);
-		retdata = true;
+		if (err != -1) {
+			rdata = data;
+			rlen = len;
+		}
 		break;
 	case PS_SCRIPT:
 		err = ps_root_run_script(ctx, data, len);
@@ -308,12 +457,10 @@
 		err = unlink(data);
 		break;
 	case PS_READFILE:
-		errno = 0;
 		err = readfile(data, buf, sizeof(buf));
 		if (err != -1) {
-			data = buf;
-			len = (size_t)err;
-			retdata = true;
+			rdata = buf;
+			rlen = (size_t)err;
 		}
 		break;
 	case PS_WRITEFILE:
@@ -322,17 +469,26 @@
 	case PS_FILEMTIME:
 		err = filemtime(data, &mtime);
 		if (err != -1) {
-			data = &mtime;
-			len = sizeof(mtime);
-			retdata = true;
+			rdata = &mtime;
+			rlen = sizeof(mtime);
 		}
 		break;
+#ifdef HAVE_CAPSICUM
+	case PS_GETIFADDRS:
+		err = ps_root_dogetifaddrs(&rdata, &rlen);
+		logerrx("dogetif %zd %p %zu", err, rdata, rlen);
+		free_rdata = true;
+		break;
+#endif
 	default:
 		err = ps_root_os(psm, msg);
 		break;
 	}
 
-	return ps_root_writeerror(ctx, err, data, retdata ? len : 0);
+	err = ps_root_writeerror(ctx, err, rlen != 0 ? rdata : 0, rlen);
+	if (free_rdata)
+		free(rdata);
+	return err;
 }
 
 /* Receive from state engine, do an action. */
@@ -556,3 +712,70 @@
 		return -1;
 	return ps_root_readerror(ctx, time, sizeof(*time));
 }
+
+#ifdef HAVE_CAPSICUM
+int
+ps_root_getifaddrs(struct dhcpcd_ctx *ctx, struct ifaddrs **ifahead)
+{
+	struct ifaddrs *ifa;
+	void *buf = NULL;
+	char *bp;
+	unsigned char *sap;
+	size_t len;
+	ssize_t err;
+
+	if (ps_sendcmd(ctx, ctx->ps_root_fd,
+	    PS_GETIFADDRS, 0, NULL, 0) == -1)
+		return -1;
+	err = ps_root_mreaderror(ctx, &buf, &len);
+
+	if (err == -1)
+		return -1;
+
+	/* Should be impossible - lo0 will always exist. */
+	if (len == 0) {
+		*ifahead = NULL;
+		return 0;
+	}
+
+	bp = buf;
+	*ifahead = (struct ifaddrs *)(void *)bp;
+	for (ifa = *ifahead; len != 0; ifa = ifa->ifa_next) {
+		if (len < ALIGN(sizeof(*ifa)) +
+		    ALIGN(IFNAMSIZ) + ALIGN(sizeof(*sap) * IFA_NADDRS))
+			goto err;
+		bp += ALIGN(sizeof(*ifa));
+		ifa->ifa_name = bp;
+		bp += ALIGN(IFNAMSIZ);
+		sap = (unsigned char *)bp;
+		bp += ALIGN(sizeof(*sap) * IFA_NADDRS);
+		len -= ALIGN(sizeof(*ifa)) +
+		    ALIGN(IFNAMSIZ) + ALIGN(sizeof(*sap) * IFA_NADDRS);
+
+#define	COPYOUTSA(addr)						\
+	do {							\
+		if (len < *sap)					\
+			goto err;				\
+		if (*sap != 0) {				\
+			(addr) = (struct sockaddr *)bp;		\
+			bp += ALIGN(*sap);			\
+			len -= ALIGN(*sap);			\
+		}						\
+		sap++;						\
+	} while (0 /* CONSTCOND */)
+
+		COPYOUTSA(ifa->ifa_addr);
+		COPYOUTSA(ifa->ifa_netmask);
+		COPYOUTSA(ifa->ifa_broadaddr);
+		ifa->ifa_next = (struct ifaddrs *)(void *)bp;
+	}
+	ifa->ifa_next = NULL;
+	return 0;
+
+err:
+	free(buf);
+	*ifahead = NULL;
+	errno = EINVAL;
+	return -1;
+}
+#endif
--- a/src/privsep-root.h	Wed May 13 20:50:45 2020 +0100
+++ b/src/privsep-root.h	Wed May 13 20:52:24 2020 +0100
@@ -35,12 +35,16 @@
 int ps_root_stop(struct dhcpcd_ctx *ctx);
 
 ssize_t ps_root_readerror(struct dhcpcd_ctx *, void *, size_t);
+ssize_t ps_root_mreaderror(struct dhcpcd_ctx *, void **, size_t *);
 ssize_t ps_root_ioctl(struct dhcpcd_ctx *, ioctl_request_t, void *, size_t);
 ssize_t ps_root_unlink(struct dhcpcd_ctx *, const char *);
 ssize_t ps_root_filemtime(struct dhcpcd_ctx *, const char *, time_t *);
 ssize_t ps_root_readfile(struct dhcpcd_ctx *, const char *, void *, size_t);
 ssize_t ps_root_writefile(struct dhcpcd_ctx *, const char *, mode_t,
     const void *, size_t);
+ssize_t ps_root_script(const struct interface *, const void *, size_t);
+int ps_root_getifaddrs(struct dhcpcd_ctx *, struct ifaddrs **);
+
 ssize_t ps_root_os(struct ps_msghdr *, struct msghdr *);
 #if defined(BSD) || defined(__sun)
 ssize_t ps_root_route(struct dhcpcd_ctx *, void *, size_t);
@@ -54,6 +58,5 @@
 ssize_t ps_root_sendnetlink(struct dhcpcd_ctx *, int, struct msghdr *);
 ssize_t ps_root_writepathuint(struct dhcpcd_ctx *, const char *, unsigned int);
 #endif
-ssize_t ps_root_script(const struct interface *, const void *, size_t);
 
 #endif
--- a/src/privsep.c	Wed May 13 20:50:45 2020 +0100
+++ b/src/privsep.c	Wed May 13 20:52:24 2020 +0100
@@ -304,6 +304,18 @@
 		logerr(__func__);
 #endif
 	status = 0;
+
+#ifdef HAVE_CAPSICUM
+	unsigned int cap_mode = 0;
+	int cap_err = cap_getmode(&cap_mode);
+
+	if (cap_err == -1) {
+		if (errno != ENOSYS)
+			logerr("%s: cap_getmode", __func__);
+	} else if (cap_mode != 0)
+		goto nowait;
+#endif
+
 	/* Wait for the process to finish */
 	while (waitpid(*pid, &status, 0) == -1) {
 		if (errno != EINTR) {
@@ -316,6 +328,9 @@
 			logerr("%s: waitpid ", __func__);
 #endif
 	}
+#ifdef HAVE_CAPSICUM
+nowait:
+#endif
 	*pid = 0;
 
 #ifdef PRIVSEP_DEBUG
--- a/src/privsep.h	Wed May 13 20:50:45 2020 +0100
+++ b/src/privsep.h	Wed May 13 20:52:24 2020 +0100
@@ -58,6 +58,7 @@
 #define	PS_IOCTL6		0x0102
 #define	PS_IOCTLINDIRECT	0x0103
 #define	PS_IP6FORWARDING	0x0104
+#define	PS_GETIFADDRS		0x0105
 
 /* Linux commands */
 #define	PS_WRITEPATHUINT	0x0201