From e591578fa167bd838c8a0051eadf0a33062cbb06 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Fri, 15 May 2020 20:23:55 +0100 Subject: [PATCH] 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. --- src/bpf.c | 108 +++++++++++++++++++++++++++++++++++++++------ src/dhcpcd.c | 14 +++++- src/privsep-bpf.c | 19 ++++---- src/privsep-inet.c | 33 +++++++++++--- src/privsep.c | 31 +------------ src/privsep.h | 4 +- 6 files changed, 147 insertions(+), 62 deletions(-) diff --git a/src/bpf.c b/src/bpf.c index c9f3b084..1517355f 100644 --- a/src/bpf.c +++ b/src/bpf.c @@ -303,14 +303,22 @@ next: 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 @@ static const struct bpf_insn bpf_arp_filter [] = { #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 @@ bpf_arp(struct interface *ifp, int fd) 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 @@ noaddrs: 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 @@ static const struct bpf_insn bpf_bootp_ether[] = { #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 @@ static const struct bpf_insn bpf_bootp_filter[] = { 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) + +#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_FILTER_LEN __arraycount(bpf_bootp_filter) #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 @@ bpf_bootp(struct interface *ifp, int fd) } /* 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 @@ bpf_bootp(struct interface *ifp, int fd) 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 +} diff --git a/src/dhcpcd.c b/src/dhcpcd.c index 3f5c35d5..10e5e6ee 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -2269,10 +2269,22 @@ printpidfile: #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 diff --git a/src/privsep-bpf.c b/src/privsep-bpf.c index 8326425d..06247c0f 100644 --- a/src/privsep-bpf.c +++ b/src/privsep-bpf.c @@ -203,7 +203,6 @@ ps_bpf_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg) 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 @@ ps_bpf_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg) 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 @@ ps_bpf_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg) &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 diff --git a/src/privsep-inet.c b/src/privsep-inet.c index 70074160..74cdf722 100644 --- a/src/privsep-inet.c +++ b/src/privsep-inet.c @@ -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 #endif @@ -305,11 +302,25 @@ ps_inet_dodispatch(void *arg) 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 @@ ps_inet_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, &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; diff --git a/src/privsep.c b/src/privsep.c index 1bf068e9..621aed28 100644 --- a/src/privsep.c +++ b/src/privsep.c @@ -108,7 +108,7 @@ ps_init(struct dhcpcd_ctx *ctx) } 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 @@ ps_dropprivs(struct dhcpcd_ctx *ctx, unsigned int flags) 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 @@ ps_dostart(struct dhcpcd_ctx *ctx, } if (flags & PSF_DROPPRIVS) - ps_dropprivs(ctx, flags); + ps_dropprivs(ctx); return 0; diff --git a/src/privsep.h b/src/privsep.h index 94346cac..7fb5ca9b 100644 --- a/src/privsep.h +++ b/src/privsep.h @@ -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 @@ TAILQ_HEAD(ps_process_head, ps_process); #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 *); -- 2.47.2