/* SPDX-License-Identifier: BSD-2-Clause */ /* * Privilege Separation for dhcpcd, privileged proxy * Copyright (c) 2006-2021 Roy Marples * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "auth.h" #include "common.h" #include "dev.h" #include "dhcpcd.h" #include "dhcp6.h" #include "eloop.h" #include "if.h" #include "ipv6nd.h" #include "logerr.h" #include "privsep.h" #include "sa.h" #include "script.h" __CTASSERT(sizeof(ioctl_request_t) <= sizeof(unsigned long)); struct psr_error { ssize_t psr_result; int psr_errno; char psr_pad[sizeof(ssize_t) - sizeof(int)]; size_t psr_datalen; }; struct psr_ctx { struct dhcpcd_ctx *psr_ctx; struct psr_error psr_error; size_t psr_datalen; void *psr_data; }; static void ps_root_readerrorcb(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 = psr_ctx->psr_data, .iov_len = psr_ctx->psr_datalen }, }; 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 == -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); } ssize_t ps_root_readerror(struct dhcpcd_ctx *ctx, void *data, size_t len) { struct psr_ctx psr_ctx = { .psr_ctx = ctx, .psr_data = data, .psr_datalen = len, }; if (eloop_event_add(ctx->ps_eloop, ctx->ps_root_fd, ps_root_readerrorcb, &psr_ctx) == -1) return -1; eloop_enter(ctx->ps_eloop); eloop_start(ctx->ps_eloop, &ctx->sigset); errno = psr_ctx.psr_error.psr_errno; return psr_ctx.psr_error.psr_result; } #ifdef PRIVSEP_GETIFADDRS 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 > SSIZE_MAX) PSR_ERROR(ENOBUFS); else 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_enter(ctx->ps_eloop); 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) { struct psr_error psr = { .psr_result = result, .psr_errno = errno, .psr_datalen = len, }; struct iovec iov[] = { { .iov_base = &psr, .iov_len = sizeof(psr) }, { .iov_base = data, .iov_len = len }, }; #ifdef PRIVSEP_DEBUG logdebugx("%s: result %zd errno %d", __func__, result, errno); #endif return writev(ctx->ps_root_fd, iov, __arraycount(iov)); } static ssize_t ps_root_doioctl(unsigned long req, void *data, size_t len) { int s, err; /* Only allow these ioctls */ switch(req) { #ifdef SIOCAIFADDR case SIOCAIFADDR: /* FALLTHROUGH */ case SIOCDIFADDR: /* FALLTHROUGH */ #endif #ifdef SIOCSIFHWADDR case SIOCSIFHWADDR: /* FALLTHROUGH */ #endif #ifdef SIOCGIFPRIORITY case SIOCGIFPRIORITY: /* FALLTHROUGH */ #endif case SIOCSIFFLAGS: /* FALLTHROUGH */ case SIOCGIFMTU: /* FALLTHROUGH */ case SIOCSIFMTU: break; default: errno = EPERM; return -1; } s = socket(PF_INET, SOCK_DGRAM, 0); if (s != -1) #ifdef IOCTL_REQUEST_TYPE { ioctl_request_t reqt; memcpy(&reqt, &req, sizeof(reqt)); err = ioctl(s, reqt, data, len); } #else err = ioctl(s, req, data, len); #endif else err = -1; if (s != -1) close(s); return err; } static ssize_t ps_root_run_script(struct dhcpcd_ctx *ctx, const void *data, size_t len) { const char *envbuf = data; char * const argv[] = { ctx->script, NULL }; pid_t pid; int status; if (len == 0) return 0; if (script_buftoenv(ctx, UNCONST(envbuf), len) == NULL) return -1; pid = script_exec(argv, ctx->script_env); if (pid == -1) return -1; /* Wait for the script to finish */ while (waitpid(pid, &status, 0) == -1) { if (errno != EINTR) { logerr(__func__); status = 0; break; } } return status; } static bool ps_root_validpath(const struct dhcpcd_ctx *ctx, uint16_t cmd, const char *path) { /* Avoid a previous directory attack to avoid /proc/../ * dhcpcd should never use a path with double dots. */ if (strstr(path, "..") != NULL) return false; if (cmd == PS_READFILE) { #ifdef EMBEDDED_CONFIG if (strcmp(ctx->cffile, EMBEDDED_CONFIG) == 0) return true; #endif if (strcmp(ctx->cffile, path) == 0) return true; } if (strncmp(DBDIR, path, strlen(DBDIR)) == 0) return true; if (strncmp(RUNDIR, path, strlen(RUNDIR)) == 0) return true; #ifdef __linux__ if (strncmp("/proc/net/", path, strlen("/proc/net/")) == 0 || strncmp("/proc/sys/net/", path, strlen("/proc/sys/net/")) == 0 || strncmp("/sys/class/net/", path, strlen("/sys/class/net/")) == 0) return true; #endif errno = EPERM; return false; } static ssize_t ps_root_dowritefile(const struct dhcpcd_ctx *ctx, mode_t mode, void *data, size_t len) { char *file = data, *nc; nc = memchr(file, '\0', len); if (nc == NULL) { errno = EINVAL; return -1; } if (!ps_root_validpath(ctx, PS_WRITEFILE, file)) return -1; nc++; return writefile(file, mode, nc, len - (size_t)(nc - file)); } #ifdef AUTH static ssize_t ps_root_monordm(uint64_t *rdm, size_t len) { if (len != sizeof(*rdm)) { errno = EINVAL; return -1; } return auth_get_rdm_monotonic(rdm); } #endif #ifdef PRIVSEP_GETIFADDRS #define IFA_NADDRS 4 static ssize_t ps_root_dogetifaddrs(void **rdata, size_t *rlen) { struct ifaddrs *ifaddrs, *ifa; size_t len; uint8_t *buf, *sap; socklen_t salen; 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(salen) * 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)); #ifdef BSD /* * On BSD we need to carry ifa_data so we can access * if_data->ifi_link_state */ if (ifa->ifa_addr != NULL && ifa->ifa_addr->sa_family == AF_LINK) len += ALIGN(sizeof(struct if_data)); #endif } /* 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) { memcpy(buf, ifa, sizeof(*ifa)); buf += ALIGN(sizeof(*ifa)); strlcpy((char *)buf, ifa->ifa_name, IFNAMSIZ); buf += ALIGN(IFNAMSIZ); sap = buf; buf += ALIGN(sizeof(salen) * IFA_NADDRS); #define COPYINSA(addr) \ do { \ if ((addr) != NULL) \ salen = sa_len((addr)); \ else \ salen = 0; \ if (salen != 0) { \ memcpy(sap, &salen, sizeof(salen)); \ memcpy(buf, (addr), salen); \ buf += ALIGN(salen); \ } \ sap += sizeof(salen); \ } while (0 /*CONSTCOND */) COPYINSA(ifa->ifa_addr); COPYINSA(ifa->ifa_netmask); COPYINSA(ifa->ifa_broadaddr); #ifdef BSD if (ifa->ifa_addr != NULL && ifa->ifa_addr->sa_family == AF_LINK) { salen = (socklen_t)sizeof(struct if_data); memcpy(buf, ifa->ifa_data, salen); buf += ALIGN(salen); } else #endif salen = 0; memcpy(sap, &salen, sizeof(salen)); } freeifaddrs(ifaddrs); return 0; } #endif static ssize_t ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) { struct dhcpcd_ctx *ctx = arg; uint16_t cmd; struct ps_process *psp; struct iovec *iov = msg->msg_iov; 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 free_rdata = false; 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 if (psp != NULL) { if (psm->ps_cmd & PS_STOP) { int ret = ps_dostop(ctx, &psp->psp_pid, &psp->psp_fd); ps_freeprocess(psp); return ret; } else if (psm->ps_cmd & PS_START) { /* Process has already started .... */ return 0; } err = ps_sendpsmmsg(ctx, psp->psp_fd, psm, msg); if (err == -1) { logerr("%s: failed to send message to pid %d", __func__, psp->psp_pid); shutdown(psp->psp_fd, SHUT_RDWR); close(psp->psp_fd); psp->psp_fd = -1; ps_freeprocess(psp); } return 0; } if (psm->ps_cmd & PS_STOP && psp == NULL) return 0; switch (cmd) { #ifdef INET #ifdef ARP case PS_BPF_ARP: /* FALLTHROUGH */ #endif case PS_BPF_BOOTP: return ps_bpf_cmd(ctx, psm, msg); #endif #ifdef INET case PS_BOOTP: return ps_inet_cmd(ctx, psm, msg); #endif #ifdef INET6 #ifdef DHCP6 case PS_DHCP6: /* FALLTHROUGH */ #endif case PS_ND: return ps_inet_cmd(ctx, psm, msg); #endif default: break; } assert(msg->msg_iovlen == 0 || msg->msg_iovlen == 1); /* Reset errno */ errno = 0; switch (psm->ps_cmd) { case PS_IOCTL: err = ps_root_doioctl(psm->ps_flags, data, len); if (err != -1) { rdata = data; rlen = len; } break; case PS_SCRIPT: err = ps_root_run_script(ctx, data, len); break; case PS_UNLINK: if (!ps_root_validpath(ctx, psm->ps_cmd, data)) { err = -1; break; } err = unlink(data); break; case PS_READFILE: if (!ps_root_validpath(ctx, psm->ps_cmd, data)) { err = -1; break; } err = readfile(data, buf, sizeof(buf)); if (err != -1) { rdata = buf; rlen = (size_t)err; } break; case PS_WRITEFILE: err = ps_root_dowritefile(ctx, (mode_t)psm->ps_flags, data, len); break; case PS_FILEMTIME: err = filemtime(data, &mtime); if (err != -1) { rdata = &mtime; rlen = sizeof(mtime); } break; case PS_LOGREOPEN: err = logopen(ctx->logfile); break; #ifdef AUTH case PS_AUTH_MONORDM: err = ps_root_monordm(data, len); if (err != -1) { rdata = data; rlen = len; } break; #endif #ifdef PRIVSEP_GETIFADDRS case PS_GETIFADDRS: err = ps_root_dogetifaddrs(&rdata, &rlen); free_rdata = true; break; #endif #if defined(INET6) && (defined(__linux__) || defined(HAVE_PLEDGE)) case PS_IP6FORWARDING: err = ip6_forwarding(data); break; #endif #ifdef PLUGIN_DEV case PS_DEV_INITTED: err = dev_initialised(ctx, data); break; case PS_DEV_LISTENING: err = dev_listening(ctx); break; #endif default: err = ps_root_os(psm, msg, &rdata, &rlen); break; } 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. */ static void ps_root_recvmsg(void *arg) { struct dhcpcd_ctx *ctx = arg; if (ps_recvpsmsg(ctx, ctx->ps_root_fd, ps_root_recvmsgcb, ctx) == -1) logerr(__func__); } #ifdef PLUGIN_DEV static int ps_root_handleinterface(void *arg, int action, const char *ifname) { struct dhcpcd_ctx *ctx = arg; unsigned long flag; if (action == 1) flag = PS_DEV_IFADDED; else if (action == -1) flag = PS_DEV_IFREMOVED; else if (action == 0) flag = PS_DEV_IFUPDATED; else { errno = EINVAL; return -1; } return (int)ps_sendcmd(ctx, ctx->ps_data_fd, PS_DEV_IFCMD, flag, ifname, strlen(ifname) + 1); } #endif static int ps_root_startcb(void *arg) { struct dhcpcd_ctx *ctx = arg; if (ctx->options & DHCPCD_MANAGER) setproctitle("[privileged proxy]"); else setproctitle("[privileged proxy] %s%s%s", ctx->ifv[0], ctx->options & DHCPCD_IPV4 ? " [ip4]" : "", ctx->options & DHCPCD_IPV6 ? " [ip6]" : ""); ctx->ps_root_pid = getpid(); ctx->options |= DHCPCD_PRIVSEPROOT; /* Open network sockets for sending. * This is a small bit wasteful for non sandboxed OS's * but makes life very easy for unicasting DHCPv6 in non manager * mode as we no longer care about address selection. * We can't call shutdown SHUT_RD on the socket because it's * not connectd. All we can do is try and set a zero sized * receive buffer and just let it overflow. * Reading from it just to drain it is a waste of CPU time. */ #ifdef INET if (ctx->options & DHCPCD_IPV4) { int buflen = 1; ctx->udp_wfd = xsocket(PF_INET, SOCK_RAW | SOCK_CXNB, IPPROTO_UDP); if (ctx->udp_wfd == -1) logerr("%s: dhcp_openraw", __func__); else if (setsockopt(ctx->udp_wfd, SOL_SOCKET, SO_RCVBUF, &buflen, sizeof(buflen)) == -1) logerr("%s: setsockopt SO_RCVBUF DHCP", __func__); } #endif #ifdef INET6 if (ctx->options & DHCPCD_IPV6) { int buflen = 1; ctx->nd_fd = ipv6nd_open(false); if (ctx->nd_fd == -1) logerr("%s: ipv6nd_open", __func__); else if (setsockopt(ctx->nd_fd, SOL_SOCKET, SO_RCVBUF, &buflen, sizeof(buflen)) == -1) logerr("%s: setsockopt SO_RCVBUF ND", __func__); } #endif #ifdef DHCP6 if (ctx->options & DHCPCD_IPV6) { int buflen = 1; ctx->dhcp6_wfd = dhcp6_openraw(); if (ctx->dhcp6_wfd == -1) logerr("%s: dhcp6_openraw", __func__); else if (setsockopt(ctx->dhcp6_wfd, SOL_SOCKET, SO_RCVBUF, &buflen, sizeof(buflen)) == -1) logerr("%s: setsockopt SO_RCVBUF DHCP6", __func__); } #endif #ifdef PLUGIN_DEV /* Start any dev listening plugin which may want to * change the interface name provided by the kernel */ if ((ctx->options & (DHCPCD_MANAGER | DHCPCD_DEV)) == (DHCPCD_MANAGER | DHCPCD_DEV)) dev_start(ctx, ps_root_handleinterface); #endif return 0; } static void ps_root_signalcb(int sig, __unused void *arg) { if (sig == SIGCHLD) { while (waitpid(-1, NULL, WNOHANG) > 0) ; return; } } int (*handle_interface)(void *, int, const char *); #ifdef PLUGIN_DEV static ssize_t ps_root_devcb(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg) { int action; struct iovec *iov = msg->msg_iov; if (msg->msg_iovlen != 1) { errno = EINVAL; return -1; } switch(psm->ps_flags) { case PS_DEV_IFADDED: action = 1; break; case PS_DEV_IFREMOVED: action = -1; break; case PS_DEV_IFUPDATED: action = 0; break; default: errno = EINVAL; return -1; } return dhcpcd_handleinterface(ctx, action, iov->iov_base); } #endif static ssize_t ps_root_dispatchcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) { struct dhcpcd_ctx *ctx = arg; ssize_t err; switch(psm->ps_cmd) { #ifdef PLUGIN_DEV case PS_DEV_IFCMD: err = ps_root_devcb(ctx, psm, msg); break; #endif default: #ifdef INET err = ps_bpf_dispatch(ctx, psm, msg); if (err == -1 && errno == ENOTSUP) #endif err = ps_inet_dispatch(ctx, psm, msg); } return err; } static void ps_root_dispatch(void *arg) { struct dhcpcd_ctx *ctx = arg; if (ps_recvpsmsg(ctx, ctx->ps_data_fd, ps_root_dispatchcb, ctx) == -1) logerr(__func__); } static void ps_root_log(void *arg) { struct dhcpcd_ctx *ctx = arg; if (logreadfd(ctx->ps_log_fd) == -1) logerr(__func__); } pid_t ps_root_start(struct dhcpcd_ctx *ctx) { int logfd[2], datafd[2]; pid_t pid; if (xsocketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, logfd) == -1) return -1; #ifdef PRIVSEP_RIGHTS if (ps_rights_limit_fdpair(logfd) == -1) return -1; #endif if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_CXNB, 0, datafd) == -1) return -1; if (ps_setbuf_fdpair(datafd) == -1) return -1; #ifdef PRIVSEP_RIGHTS if (ps_rights_limit_fdpair(datafd) == -1) return -1; #endif pid = ps_dostart(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd, ps_root_recvmsg, NULL, ctx, ps_root_startcb, ps_root_signalcb, 0); if (pid == 0) { ctx->ps_log_fd = logfd[1]; if (eloop_event_add(ctx->eloop, ctx->ps_log_fd, ps_root_log, ctx) == -1) return -1; close(logfd[0]); ctx->ps_data_fd = datafd[1]; close(datafd[0]); return 0; } else if (pid == -1) return -1; logsetfd(logfd[0]); close(logfd[1]); ctx->ps_data_fd = datafd[0]; close(datafd[1]); if (eloop_event_add(ctx->eloop, ctx->ps_data_fd, ps_root_dispatch, ctx) == -1) return -1; if ((ctx->ps_eloop = eloop_new()) == NULL) return -1; eloop_signal_set_cb(ctx->ps_eloop, dhcpcd_signals, dhcpcd_signals_len, ps_root_signalcb, ctx); return pid; } int ps_root_stop(struct dhcpcd_ctx *ctx) { return ps_dostop(ctx, &ctx->ps_root_pid, &ctx->ps_root_fd); } ssize_t ps_root_script(struct dhcpcd_ctx *ctx, const void *data, size_t len) { if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_SCRIPT, 0, data, len) == -1) return -1; return ps_root_readerror(ctx, NULL, 0); } ssize_t ps_root_ioctl(struct dhcpcd_ctx *ctx, ioctl_request_t req, void *data, size_t len) { #ifdef IOCTL_REQUEST_TYPE unsigned long ulreq = 0; memcpy(&ulreq, &req, sizeof(req)); if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, ulreq, data, len) == -1) return -1; #else if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IOCTL, req, data, len) == -1) return -1; #endif return ps_root_readerror(ctx, data, len); } ssize_t ps_root_unlink(struct dhcpcd_ctx *ctx, const char *file) { if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_UNLINK, 0, file, strlen(file) + 1) == -1) return -1; return ps_root_readerror(ctx, NULL, 0); } ssize_t ps_root_readfile(struct dhcpcd_ctx *ctx, const char *file, void *data, size_t len) { if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_READFILE, 0, file, strlen(file) + 1) == -1) return -1; return ps_root_readerror(ctx, data, len); } ssize_t ps_root_writefile(struct dhcpcd_ctx *ctx, const char *file, mode_t mode, const void *data, size_t len) { char buf[PS_BUFLEN]; size_t flen; flen = strlcpy(buf, file, sizeof(buf)); flen += 1; if (flen > sizeof(buf) || flen + len > sizeof(buf)) { errno = ENOBUFS; return -1; } memcpy(buf + flen, data, len); if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_WRITEFILE, mode, buf, flen + len) == -1) return -1; return ps_root_readerror(ctx, NULL, 0); } ssize_t ps_root_filemtime(struct dhcpcd_ctx *ctx, const char *file, time_t *time) { if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_FILEMTIME, 0, file, strlen(file) + 1) == -1) return -1; return ps_root_readerror(ctx, time, sizeof(*time)); } ssize_t ps_root_logreopen(struct dhcpcd_ctx *ctx) { if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_LOGREOPEN, 0, NULL, 0) == -1) return -1; return ps_root_readerror(ctx, NULL, 0); } #ifdef PRIVSEP_GETIFADDRS int ps_root_getifaddrs(struct dhcpcd_ctx *ctx, struct ifaddrs **ifahead) { struct ifaddrs *ifa; void *buf = NULL; char *bp, *sap; socklen_t salen; 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; ifa != NULL; ifa = ifa->ifa_next) { if (len < ALIGN(sizeof(*ifa)) + ALIGN(IFNAMSIZ) + ALIGN(sizeof(salen) * IFA_NADDRS)) goto err; bp += ALIGN(sizeof(*ifa)); ifa->ifa_name = bp; bp += ALIGN(IFNAMSIZ); sap = bp; bp += ALIGN(sizeof(salen) * IFA_NADDRS); len -= ALIGN(sizeof(*ifa)) + ALIGN(IFNAMSIZ) + ALIGN(sizeof(salen) * IFA_NADDRS); #define COPYOUTSA(addr) \ do { \ memcpy(&salen, sap, sizeof(salen)); \ if (len < salen) \ goto err; \ if (salen != 0) { \ (addr) = (struct sockaddr *)bp; \ bp += ALIGN(salen); \ len -= ALIGN(salen); \ } \ sap += sizeof(salen); \ } while (0 /* CONSTCOND */) COPYOUTSA(ifa->ifa_addr); COPYOUTSA(ifa->ifa_netmask); COPYOUTSA(ifa->ifa_broadaddr); memcpy(&salen, sap, sizeof(salen)); if (len < salen) goto err; if (salen != 0) { ifa->ifa_data = bp; bp += ALIGN(salen); len -= ALIGN(salen); } else ifa->ifa_data = NULL; if (len != 0) ifa->ifa_next = (struct ifaddrs *)(void *)bp; else ifa->ifa_next = NULL; } return 0; err: free(buf); *ifahead = NULL; errno = EINVAL; return -1; } #endif #if defined(__linux__) || defined(HAVE_PLEDGE) ssize_t ps_root_ip6forwarding(struct dhcpcd_ctx *ctx, const char *ifname) { if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_IP6FORWARDING, 0, ifname, ifname != NULL ? strlen(ifname) + 1 : 0) == -1) return -1; return ps_root_readerror(ctx, NULL, 0); } #endif #ifdef AUTH int ps_root_getauthrdm(struct dhcpcd_ctx *ctx, uint64_t *rdm) { if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_AUTH_MONORDM, 0, rdm, sizeof(*rdm))== -1) return -1; return (int)ps_root_readerror(ctx, rdm, sizeof(*rdm)); } #endif #ifdef PLUGIN_DEV int ps_root_dev_initialised(struct dhcpcd_ctx *ctx, const char *ifname) { if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_DEV_INITTED, 0, ifname, strlen(ifname) + 1)== -1) return -1; return (int)ps_root_readerror(ctx, NULL, 0); } int ps_root_dev_listening(struct dhcpcd_ctx * ctx) { if (ps_sendcmd(ctx, ctx->ps_root_fd, PS_DEV_LISTENING, 0, NULL, 0)== -1) return -1; return (int)ps_root_readerror(ctx, NULL, 0); } #endif