Changeset View
Changeset View
Standalone View
Standalone View
contrib/dhcpcd/src/privsep-bpf.c
- This file was added.
/* SPDX-License-Identifier: BSD-2-Clause */ | |||||
/* | |||||
* Privilege Separation BPF Initiator | |||||
* Copyright (c) 2006-2021 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/socket.h> | |||||
#include <sys/types.h> | |||||
/* Need these headers just for if_ether on some OS. */ | |||||
#ifndef __NetBSD__ | |||||
#include <net/if.h> | |||||
#include <net/if_arp.h> | |||||
#include <netinet/in.h> | |||||
#endif | |||||
#include <netinet/if_ether.h> | |||||
#include <assert.h> | |||||
#include <pwd.h> | |||||
#include <errno.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <unistd.h> | |||||
#include "arp.h" | |||||
#include "bpf.h" | |||||
#include "dhcp.h" | |||||
#include "dhcp6.h" | |||||
#include "eloop.h" | |||||
#include "ipv6nd.h" | |||||
#include "logerr.h" | |||||
#include "privsep.h" | |||||
static void | |||||
ps_bpf_recvbpf(void *arg) | |||||
{ | |||||
struct ps_process *psp = arg; | |||||
struct bpf *bpf = psp->psp_bpf; | |||||
uint8_t buf[FRAMELEN_MAX]; | |||||
ssize_t len; | |||||
struct ps_msghdr psm = { | |||||
.ps_id = psp->psp_id, | |||||
.ps_cmd = psp->psp_id.psi_cmd, | |||||
}; | |||||
bpf->bpf_flags &= ~BPF_EOF; | |||||
/* A BPF read can read more than one filtered packet at time. | |||||
* This mechanism allows us to read each packet from the buffer. */ | |||||
while (!(bpf->bpf_flags & BPF_EOF)) { | |||||
len = bpf_read(bpf, buf, sizeof(buf)); | |||||
if (len == -1) { | |||||
int error = errno; | |||||
if (errno != ENETDOWN) | |||||
logerr("%s: %s", psp->psp_ifname, __func__); | |||||
if (error != ENXIO) | |||||
break; | |||||
/* If the interface has departed, close the BPF | |||||
* socket. This stops log spam if RTM_IFANNOUNCE is | |||||
* delayed in announcing the departing interface. */ | |||||
eloop_event_delete(psp->psp_ctx->eloop, bpf->bpf_fd); | |||||
bpf_close(bpf); | |||||
psp->psp_bpf = NULL; | |||||
break; | |||||
} | |||||
if (len == 0) | |||||
break; | |||||
psm.ps_flags = bpf->bpf_flags; | |||||
len = ps_sendpsmdata(psp->psp_ctx, psp->psp_ctx->ps_data_fd, | |||||
&psm, buf, (size_t)len); | |||||
if (len == -1) | |||||
logerr(__func__); | |||||
if (len == -1 || len == 0) | |||||
break; | |||||
} | |||||
} | |||||
static ssize_t | |||||
ps_bpf_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) | |||||
{ | |||||
struct ps_process *psp = arg; | |||||
struct iovec *iov = msg->msg_iov; | |||||
#ifdef PRIVSEP_DEBUG | |||||
logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp); | |||||
#endif | |||||
switch(psm->ps_cmd) { | |||||
#ifdef ARP | |||||
case PS_BPF_ARP: /* FALLTHROUGH */ | |||||
#endif | |||||
case PS_BPF_BOOTP: | |||||
break; | |||||
default: | |||||
/* IPC failure, we should not be processing any commands | |||||
* at this point!/ */ | |||||
errno = EINVAL; | |||||
return -1; | |||||
} | |||||
/* We might have had an earlier ENXIO error. */ | |||||
if (psp->psp_bpf == NULL) { | |||||
errno = ENXIO; | |||||
return -1; | |||||
} | |||||
return bpf_send(psp->psp_bpf, psp->psp_proto, | |||||
iov->iov_base, iov->iov_len); | |||||
} | |||||
static void | |||||
ps_bpf_recvmsg(void *arg) | |||||
{ | |||||
struct ps_process *psp = arg; | |||||
if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, | |||||
ps_bpf_recvmsgcb, arg) == -1) | |||||
logerr(__func__); | |||||
} | |||||
static int | |||||
ps_bpf_start_bpf(void *arg) | |||||
{ | |||||
struct ps_process *psp = arg; | |||||
struct dhcpcd_ctx *ctx = psp->psp_ctx; | |||||
char *addr; | |||||
struct in_addr *ia = &psp->psp_id.psi_addr.psa_in_addr; | |||||
if (ia->s_addr == INADDR_ANY) { | |||||
ia = NULL; | |||||
addr = NULL; | |||||
} else | |||||
addr = inet_ntoa(*ia); | |||||
setproctitle("[BPF %s] %s%s%s", psp->psp_protostr, psp->psp_ifname, | |||||
addr != NULL ? " " : "", addr != NULL ? addr : ""); | |||||
ps_freeprocesses(ctx, psp); | |||||
psp->psp_bpf = bpf_open(&psp->psp_ifp, psp->psp_filter, ia); | |||||
if (psp->psp_bpf == NULL) | |||||
logerr("%s: bpf_open",__func__); | |||||
#ifdef PRIVSEP_RIGHTS | |||||
else if (ps_rights_limit_fd(psp->psp_bpf->bpf_fd) == -1) | |||||
logerr("%s: ps_rights_limit_fd", __func__); | |||||
#endif | |||||
else if (eloop_event_add(ctx->eloop, | |||||
psp->psp_bpf->bpf_fd, ps_bpf_recvbpf, psp) == -1) | |||||
logerr("%s: eloop_event_add", __func__); | |||||
else { | |||||
psp->psp_work_fd = psp->psp_bpf->bpf_fd; | |||||
return 0; | |||||
} | |||||
eloop_exit(ctx->eloop, EXIT_FAILURE); | |||||
return -1; | |||||
} | |||||
ssize_t | |||||
ps_bpf_cmd(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg) | |||||
{ | |||||
uint16_t cmd; | |||||
struct ps_process *psp; | |||||
pid_t start; | |||||
struct iovec *iov = msg->msg_iov; | |||||
struct interface *ifp; | |||||
cmd = (uint16_t)(psm->ps_cmd & ~(PS_START | PS_STOP)); | |||||
psp = ps_findprocess(ctx, &psm->ps_id); | |||||
#ifdef PRIVSEP_DEBUG | |||||
logerrx("%s: IN cmd %x, psp %p", __func__, psm->ps_cmd, psp); | |||||
#endif | |||||
switch (cmd) { | |||||
#ifdef ARP | |||||
case PS_BPF_ARP: /* FALLTHROUGH */ | |||||
#endif | |||||
case PS_BPF_BOOTP: | |||||
break; | |||||
default: | |||||
logerrx("%s: unknown command %x", __func__, psm->ps_cmd); | |||||
errno = ENOTSUP; | |||||
return -1; | |||||
} | |||||
if (!(psm->ps_cmd & PS_START)) { | |||||
errno = EINVAL; | |||||
return -1; | |||||
} | |||||
if (psp != NULL) | |||||
return 1; | |||||
psp = ps_newprocess(ctx, &psm->ps_id); | |||||
if (psp == NULL) | |||||
return -1; | |||||
ifp = &psp->psp_ifp; | |||||
assert(msg->msg_iovlen == 1); | |||||
assert(iov->iov_len == sizeof(*ifp)); | |||||
memcpy(ifp, iov->iov_base, sizeof(*ifp)); | |||||
ifp->ctx = psp->psp_ctx; | |||||
ifp->options = NULL; | |||||
memset(ifp->if_data, 0, sizeof(ifp->if_data)); | |||||
memcpy(psp->psp_ifname, ifp->name, sizeof(psp->psp_ifname)); | |||||
switch (cmd) { | |||||
#ifdef ARP | |||||
case PS_BPF_ARP: | |||||
psp->psp_proto = ETHERTYPE_ARP; | |||||
psp->psp_protostr = "ARP"; | |||||
psp->psp_filter = bpf_arp; | |||||
break; | |||||
#endif | |||||
case PS_BPF_BOOTP: | |||||
psp->psp_proto = ETHERTYPE_IP; | |||||
psp->psp_protostr = "BOOTP"; | |||||
psp->psp_filter = bpf_bootp; | |||||
break; | |||||
} | |||||
start = ps_dostart(ctx, | |||||
&psp->psp_pid, &psp->psp_fd, | |||||
ps_bpf_recvmsg, NULL, psp, | |||||
ps_bpf_start_bpf, NULL, | |||||
PSF_DROPPRIVS); | |||||
switch (start) { | |||||
case -1: | |||||
ps_freeprocess(psp); | |||||
return -1; | |||||
case 0: | |||||
ps_entersandbox("stdio", NULL); | |||||
break; | |||||
default: | |||||
logdebugx("%s: spawned BPF %s on PID %d", | |||||
psp->psp_ifname, psp->psp_protostr, start); | |||||
break; | |||||
} | |||||
return start; | |||||
} | |||||
ssize_t | |||||
ps_bpf_dispatch(struct dhcpcd_ctx *ctx, | |||||
struct ps_msghdr *psm, struct msghdr *msg) | |||||
{ | |||||
struct iovec *iov = msg->msg_iov; | |||||
struct interface *ifp; | |||||
uint8_t *bpf; | |||||
size_t bpf_len; | |||||
switch (psm->ps_cmd) { | |||||
#ifdef ARP | |||||
case PS_BPF_ARP: | |||||
#endif | |||||
case PS_BPF_BOOTP: | |||||
break; | |||||
default: | |||||
errno = ENOTSUP; | |||||
return -1; | |||||
} | |||||
ifp = if_findindex(ctx->ifaces, psm->ps_id.psi_ifindex); | |||||
/* interface may have departed .... */ | |||||
if (ifp == NULL) | |||||
return -1; | |||||
bpf = iov->iov_base; | |||||
bpf_len = iov->iov_len; | |||||
switch (psm->ps_cmd) { | |||||
#ifdef ARP | |||||
case PS_BPF_ARP: | |||||
arp_packet(ifp, bpf, bpf_len, (unsigned int)psm->ps_flags); | |||||
break; | |||||
#endif | |||||
case PS_BPF_BOOTP: | |||||
dhcp_packet(ifp, bpf, bpf_len, (unsigned int)psm->ps_flags); | |||||
break; | |||||
} | |||||
return 1; | |||||
} | |||||
static ssize_t | |||||
ps_bpf_send(const struct interface *ifp, const struct in_addr *ia, | |||||
uint16_t cmd, const void *data, size_t len) | |||||
{ | |||||
struct dhcpcd_ctx *ctx = ifp->ctx; | |||||
struct ps_msghdr psm = { | |||||
.ps_cmd = cmd, | |||||
.ps_id = { | |||||
.psi_ifindex = ifp->index, | |||||
.psi_cmd = (uint8_t)(cmd & ~(PS_START | PS_STOP)), | |||||
}, | |||||
}; | |||||
if (ia != NULL) | |||||
psm.ps_id.psi_addr.psa_in_addr = *ia; | |||||
return ps_sendpsmdata(ctx, ctx->ps_root_fd, &psm, data, len); | |||||
} | |||||
#ifdef ARP | |||||
ssize_t | |||||
ps_bpf_openarp(const struct interface *ifp, const struct in_addr *ia) | |||||
{ | |||||
assert(ia != NULL); | |||||
return ps_bpf_send(ifp, ia, PS_BPF_ARP | PS_START, | |||||
ifp, sizeof(*ifp)); | |||||
} | |||||
ssize_t | |||||
ps_bpf_closearp(const struct interface *ifp, const struct in_addr *ia) | |||||
{ | |||||
return ps_bpf_send(ifp, ia, PS_BPF_ARP | PS_STOP, NULL, 0); | |||||
} | |||||
ssize_t | |||||
ps_bpf_sendarp(const struct interface *ifp, const struct in_addr *ia, | |||||
const void *data, size_t len) | |||||
{ | |||||
assert(ia != NULL); | |||||
return ps_bpf_send(ifp, ia, PS_BPF_ARP, data, len); | |||||
} | |||||
#endif | |||||
ssize_t | |||||
ps_bpf_openbootp(const struct interface *ifp) | |||||
{ | |||||
return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP | PS_START, | |||||
ifp, sizeof(*ifp)); | |||||
} | |||||
ssize_t | |||||
ps_bpf_closebootp(const struct interface *ifp) | |||||
{ | |||||
return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP | PS_STOP, NULL, 0); | |||||
} | |||||
ssize_t | |||||
ps_bpf_sendbootp(const struct interface *ifp, const void *data, size_t len) | |||||
{ | |||||
return ps_bpf_send(ifp, NULL, PS_BPF_BOOTP, data, len); | |||||
} |