changeset 5228:82c7e8204e9b draft

BPF: Set write filters where supported While here make Capsicum and Pledge more granular so we can easily Pledge the BPF BOOTP process but not the ARP one.
author Roy Marples <roy@marples.name>
date Fri, 15 May 2020 20:23:55 +0100
parents 7406014c9120
children bb468c1a3b46
files src/bpf.c src/dhcpcd.c src/privsep-bpf.c src/privsep-inet.c src/privsep.c src/privsep.h
diffstat 6 files changed, 147 insertions(+), 62 deletions(-) [+]
line wrap: on
line diff
--- a/src/bpf.c	Fri May 15 15:56:47 2020 +0100
+++ b/src/bpf.c	Fri May 15 20:23:55 2020 +0100
@@ -303,14 +303,22 @@
 int
 bpf_attach(int fd, void *filter, unsigned int filter_len)
 {
-	struct bpf_program pf;
+	struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len };
 
 	/* Install the filter. */
-	memset(&pf, 0, sizeof(pf));
-	pf.bf_insns = filter;
-	pf.bf_len = filter_len;
 	return ioctl(fd, BIOCSETF, &pf);
 }
+
+#ifdef BIOCSETWF
+static int
+bpf_wattach(int fd, void *filter, unsigned int filter_len)
+{
+	struct bpf_program pf = { .bf_insns = filter, .bf_len = filter_len };
+
+	/* Install the filter. */
+	return ioctl(fd, BIOCSETWF, &pf);
+}
+#endif
 #endif
 
 #ifndef __sun
@@ -488,8 +496,8 @@
 #define BPF_ARP_LEN		BPF_ARP_ETHER_LEN + BPF_ARP_FILTER_LEN + \
 				BPF_CMP_HWADDR_LEN + BPF_ARP_ADDRS_LEN
 
-int
-bpf_arp(struct interface *ifp, int fd)
+static int
+bpf_arp_rw(struct interface *ifp, int fd, bool read)
 {
 	struct bpf_insn bpf[BPF_ARP_LEN];
 	struct bpf_insn *bp;
@@ -518,6 +526,18 @@
 	memcpy(bp, bpf_arp_filter, sizeof(bpf_arp_filter));
 	bp += BPF_ARP_FILTER_LEN;
 
+#ifdef BIOCSETWF
+	if (!read) {
+		/* All passed, return the packet. */
+		BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET);
+		bp++;
+
+		return bpf_wattach(fd, bpf, (unsigned int)(bp - bpf));
+	}
+#else
+	UNUSED(read);
+#endif
+
 	/* Ensure it's not from us. */
 	bp += bpf_cmp_hwaddr(bp, BPF_CMP_HWADDR_LEN, sizeof(struct arphdr),
 	                     false, ifp->hwaddr, ifp->hwlen);
@@ -579,6 +599,19 @@
 
 	return bpf_attach(fd, bpf, (unsigned int)(bp - bpf));
 }
+
+int
+bpf_arp(struct interface *ifp, int fd)
+{
+
+#ifdef BIOCSETWF
+	if (bpf_arp_rw(ifp, fd, false) == -1)
+		return -1;
+#endif
+
+	return bpf_arp_rw(ifp, fd, true);
+}
+
 #endif
 
 #ifdef ARPHRD_NONE
@@ -602,7 +635,7 @@
 #define BOOTP_MIN_SIZE		sizeof(struct ip) + sizeof(struct udphdr) + \
 				sizeof(struct bootp)
 
-static const struct bpf_insn bpf_bootp_filter[] = {
+static const struct bpf_insn bpf_bootp_base[] = {
 	/* Make sure it's an IPv4 packet. */
 	BPF_STMT(BPF_LD + BPF_B + BPF_IND, 0),
 	BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0xf0),
@@ -625,22 +658,36 @@
 	BPF_STMT(BPF_ALU + BPF_MUL + BPF_K, 4),
 	BPF_STMT(BPF_ALU + BPF_ADD + BPF_X, 0),
 	BPF_STMT(BPF_MISC + BPF_TAX, 0),
+};
+#define BPF_BOOTP_BASE_LEN	__arraycount(bpf_bootp_base)
 
+static const struct bpf_insn bpf_bootp_read[] = {
 	/* Make sure it's from and to the right port. */
 	BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0),
 	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPS << 16) + BOOTPC, 1, 0),
 	BPF_STMT(BPF_RET + BPF_K, 0),
 };
+#define BPF_BOOTP_READ_LEN	__arraycount(bpf_bootp_read)
 
-#define BPF_BOOTP_FILTER_LEN	__arraycount(bpf_bootp_filter)
+#ifdef BIOCSETWF
+static const struct bpf_insn bpf_bootp_write[] = {
+	/* Make sure it's from and to the right port. */
+	BPF_STMT(BPF_LD + BPF_W + BPF_IND, 0),
+	BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (BOOTPC << 16) + BOOTPS, 1, 0),
+	BPF_STMT(BPF_RET + BPF_K, 0),
+};
+#define BPF_BOOTP_WRITE_LEN	__arraycount(bpf_bootp_write)
+#endif
+
 #define BPF_BOOTP_CHADDR_LEN	((BOOTP_CHADDR_LEN / 4) * 3)
 #define	BPF_BOOTP_XID_LEN	4 /* BOUND check is 4 instructions */
 
-#define BPF_BOOTP_LEN		BPF_BOOTP_ETHER_LEN + BPF_BOOTP_FILTER_LEN \
-				+ BPF_BOOTP_XID_LEN + BPF_BOOTP_CHADDR_LEN + 4
+#define BPF_BOOTP_LEN		BPF_BOOTP_ETHER_LEN + \
+				BPF_BOOTP_BASE_LEN + BPF_BOOTP_READ_LEN + \
+				BPF_BOOTP_XID_LEN + BPF_BOOTP_CHADDR_LEN + 4
 
-int
-bpf_bootp(struct interface *ifp, int fd)
+static int
+bpf_bootp_rw(struct interface *ifp, int fd, bool read)
 {
 #if 0
 	const struct dhcp_state *state = D_CSTATE(ifp);
@@ -670,8 +717,26 @@
 	}
 
 	/* Copy in the main filter. */
-	memcpy(bp, bpf_bootp_filter, sizeof(bpf_bootp_filter));
-	bp += BPF_BOOTP_FILTER_LEN;
+	memcpy(bp, bpf_bootp_base, sizeof(bpf_bootp_base));
+	bp += BPF_BOOTP_BASE_LEN;
+
+#ifdef BIOCSETWF
+	if (!read) {
+		memcpy(bp, bpf_bootp_write, sizeof(bpf_bootp_write));
+		bp += BPF_BOOTP_WRITE_LEN;
+
+		/* All passed, return the packet. */
+		BPF_SET_STMT(bp, BPF_RET + BPF_K, BPF_WHOLEPACKET);
+		bp++;
+
+		return bpf_wattach(fd, bpf, (unsigned int)(bp - bpf));
+	}
+#else
+	UNUSED(read);
+#endif
+
+	memcpy(bp, bpf_bootp_read, sizeof(bpf_bootp_read));
+	bp += BPF_BOOTP_READ_LEN;
 
 	/* These checks won't work when same IP exists on other interfaces. */
 #if 0
@@ -715,3 +780,18 @@
 
 	return bpf_attach(fd, bpf, (unsigned int)(bp - bpf));
 }
+
+int
+bpf_bootp(struct interface *ifp, int fd)
+{
+
+#ifdef BIOCSETWF
+	if (bpf_bootp_rw(ifp, fd, true) == -1 ||
+	    bpf_bootp_rw(ifp, fd, false) == -1 ||
+	    ioctl(fd, BIOCLOCK) == -1)
+		return -1;
+	return 0;
+#else
+	return bpf_bootp_rw(ifp, fd, true);
+#endif
+}
--- a/src/dhcpcd.c	Fri May 15 15:56:47 2020 +0100
+++ b/src/dhcpcd.c	Fri May 15 20:23:55 2020 +0100
@@ -2269,10 +2269,22 @@
 
 #ifdef PRIVSEP
 	if (ctx.options & DHCPCD_PRIVSEP) {
-		if (ps_dropprivs(&ctx, PSF_CAP_ENTER | PSF_PLEDGE) == -1) {
+		if (ps_dropprivs(&ctx) == -1) {
 			logerr("ps_dropprivs");
 			goto exit_failure;
 		}
+#ifdef HAVE_CAPSICUM
+		if (cap_enter() == -1 && errno != ENOSYS) {
+			logerr("%s: cap_enter", __func__);
+			goto exit_failure;
+		}
+#endif
+#ifdef HAVE_PLEDGE
+		if (pledge("stdio inet route dns", NULL) == -1) {
+			logerr("%s: pledge", __func__);
+			goto exit_failure;
+		}
+#endif
 	}
 #endif
 
--- a/src/privsep-bpf.c	Fri May 15 15:56:47 2020 +0100
+++ b/src/privsep-bpf.c	Fri May 15 20:23:55 2020 +0100
@@ -203,7 +203,6 @@
 	struct iovec *iov = msg->msg_iov;
 	struct interface *ifp;
 	struct ipv4_state *istate;
-	unsigned int flags = PSF_DROPPRIVS | PSF_CAP_ENTER;
 
 	cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP));
 	psp = ps_findprocess(ctx, &psm->ps_id);
@@ -257,19 +256,12 @@
 		psp->psp_proto = ETHERTYPE_ARP;
 		psp->psp_protostr = "ARP";
 		psp->psp_filter = bpf_arp;
-		/*
-		 * Pledge is currently useless for BPF ARP because we cannot
-		 * change the filter:
-		 * http://openbsd-archive.7691.n7.nabble.com/ \
-		 *	  pledge-bpf-32bit-arch-unbreak-td299901.html
-		 */
 		break;
 #endif
 	case PS_BPF_BOOTP:
 		psp->psp_proto = ETHERTYPE_IP;
 		psp->psp_protostr = "BOOTP";
 		psp->psp_filter = bpf_bootp;
-		flags |= PSF_PLEDGE;
 		break;
 	}
 
@@ -277,12 +269,21 @@
 	    &psp->psp_pid, &psp->psp_fd,
 	    ps_bpf_recvmsg, NULL, psp,
 	    ps_bpf_start_bpf, ps_bpf_signal_bpfcb,
-	    flags);
+	    PSF_DROPPRIVS);
 	switch (start) {
 	case -1:
 		ps_freeprocess(psp);
 		return -1;
 	case 0:
+#ifdef HAVE_CAPSICUM
+	if (cap_enter() == -1 && errno != ENOSYS)
+		logerr("%s: cap_enter", __func__);
+#endif
+#ifdef HAVE_PLEDGE
+	/* Cant change BPF fitler for ARP yet. */
+	if (cmd != PS_BPF_ARP && pledge("stdio", NULL) == -1)
+		logerr("%s: pledge", __func__);
+#endif
 		break;
 	default:
 #ifdef PRIVSEP_DEBUG
--- a/src/privsep-inet.c	Fri May 15 15:56:47 2020 +0100
+++ b/src/privsep-inet.c	Fri May 15 20:23:55 2020 +0100
@@ -46,9 +46,6 @@
 #include "privsep.h"
 
 #ifdef HAVE_CAPSICUM
-/* We never call ps_dostart with PSF_CAP_ENTER because
- * our sockets require the use of CAP_CONNECT which does not
- * work in capabilities mode according to rights(4). */
 #include <sys/capsicum.h>
 #endif
 
@@ -305,11 +302,25 @@
 pid_t
 ps_inet_start(struct dhcpcd_ctx *ctx)
 {
+	pid_t pid;
 
-	return ps_dostart(ctx, &ctx->ps_inet_pid, &ctx->ps_inet_fd,
+	pid = ps_dostart(ctx, &ctx->ps_inet_pid, &ctx->ps_inet_fd,
 	    ps_inet_recvmsg, ps_inet_dodispatch, ctx,
 	    ps_inet_startcb, ps_inet_signalcb,
-	    PSF_DROPPRIVS | PSF_PLEDGE);
+	    PSF_DROPPRIVS);
+
+#ifdef HAVE_CAPSICUM
+#if 0		/* This breaks sendmsg() */
+	if (cap_enter() == -1 && errno != ENOSYS)
+		logerr("%s: cap_enter", __func__);
+#endif
+#endif
+#ifdef HAVE_PLEDGE
+	if (pid == 0 && pledge("stdio inet", NULL) == -1)
+		logerr("%s: pledge", __func__);
+#endif
+
+	return pid;
 }
 
 int
@@ -555,12 +566,22 @@
 	    &psp->psp_pid, &psp->psp_fd,
 	    ps_inet_recvmsgpsp, NULL, psp,
 	    start_func, ps_inet_signalcb,
-	    PSF_DROPPRIVS | PSF_PLEDGE);
+	    PSF_DROPPRIVS);
 	switch (start) {
 	case -1:
 		ps_freeprocess(psp);
 		return -1;
 	case 0:
+#ifdef HAVE_CAPSICUM
+#if 0		/* This breaks sendmsg() */
+		if (cap_enter() == -1 && errno != ENOSYS)
+			logerr("%s: cap_enter", __func__);
+#endif
+#endif
+#ifdef HAVE_PLEDGE
+		if (pledge("stdio inet", NULL) == -1)
+			logerr("%s: pledge", __func__);
+#endif
 		break;
 	default:
 		break;
--- a/src/privsep.c	Fri May 15 15:56:47 2020 +0100
+++ b/src/privsep.c	Fri May 15 20:23:55 2020 +0100
@@ -108,7 +108,7 @@
 }
 
 int
-ps_dropprivs(struct dhcpcd_ctx *ctx, unsigned int flags)
+ps_dropprivs(struct dhcpcd_ctx *ctx)
 {
 	struct passwd *pw = ctx->ps_user;
 
@@ -127,33 +127,6 @@
 		return -1;
 	}
 
-#ifdef HAVE_CAPSICUM
-	if (flags & PSF_CAP_ENTER) {
-		if (cap_enter() == -1 && errno != ENOSYS) {
-			logerr("%s: cap_enter", __func__);
-			return -1;
-		}
-	}
-#else
-	UNUSED(flags);
-#endif
-
-#ifdef HAVE_PLEDGE
-	if (flags & PSF_PLEDGE) {
-		const char *promises;
-
-		if (ctx->options & DHCPCD_UNPRIV)
-			promises = "stdio dns bpf";
-		else
-			/* SIOCGIFGROUP requires inet */
-			promises = "stdio dns inet route";
-                if (pledge(promises, NULL) == -1) {
-                        logerr("%s: pledge", __func__);
-                        return -1;
-                }
-	}
-#endif
-
 	return 0;
 }
 
@@ -267,7 +240,7 @@
 	}
 
 	if (flags & PSF_DROPPRIVS)
-		ps_dropprivs(ctx, flags);
+		ps_dropprivs(ctx);
 
 	return 0;
 
--- a/src/privsep.h	Fri May 15 15:56:47 2020 +0100
+++ b/src/privsep.h	Fri May 15 20:23:55 2020 +0100
@@ -33,8 +33,6 @@
 
 /* Start flags */
 #define	PSF_DROPPRIVS		0x01
-#define	PSF_CAP_ENTER		0x02
-#define	PSF_PLEDGE		0x04
 
 /* Protocols */
 #define	PS_BOOTP		0x0001
@@ -147,7 +145,7 @@
 #endif
 
 int ps_init(struct dhcpcd_ctx *);
-int ps_dropprivs(struct dhcpcd_ctx *, unsigned int);
+int ps_dropprivs(struct dhcpcd_ctx *);
 int ps_start(struct dhcpcd_ctx *);
 int ps_stop(struct dhcpcd_ctx *);