From: Roy Marples Date: Tue, 9 Jun 2020 17:25:18 +0000 (+0100) Subject: privsep: Implement a resource limited sandbox X-Git-Tag: v9.1.2~29 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bc4a5e852a8b810bdbe5679ef3e03a634709d722;p=thirdparty%2Fdhcpcd.git privsep: Implement a resource limited sandbox For systems without Capsicum or Pledge we can create a resource limited sandbox provided that either ppoll(2) or works with RLIMIT_NOFILES set to zero. As far as dhcpcd is concerned, that means Linux and Solaris won't work with this, but NetBSD and DragonFlyBSD will. To achieve this, a special control proxy process will be spawned just to accept new connections over the control socket because this *cannot* be limited by RLIMIT_NOFILES. --- diff --git a/configure b/configure index b2532680..4c947841 100755 --- a/configure +++ b/configure @@ -580,16 +580,18 @@ if [ "$PRIVSEP" = yes ]; then echo "#ifndef PRIVSEP_USER" >>$CONFIG_H echo "#define PRIVSEP_USER \"$PRIVSEP_USER\"" >>$CONFIG_H echo "#endif" >>$CONFIG_H - echo "DHCPCD_SRCS+= privsep.c privsep-root.c privsep-inet.c" \ + echo "PRIVSEP_SRCS= privsep.c privsep-root.c privsep-inet.c" \ >>$CONFIG_MK if [ -z "$INET" ] || [ "$INET" = yes ]; then - echo "DHCPCD_SRCS+= privsep-bpf.c" >>$CONFIG_MK + echo "PRIVSEP_SRCS+= privsep-bpf.c" >>$CONFIG_MK fi case "$OS" in - linux*) echo "DHCPCD_SRCS+= privsep-linux.c" >>$CONFIG_MK;; - solaris*|sunos*) echo "DHCPCD_SRCS+= privsep-sun.c" >>$CONFIG_MK;; - *) echo "DHCPCD_SRCS+= privsep-bsd.c" >>$CONFIG_MK;; + linux*) echo "PRIVSEP_SRCS+= privsep-linux.c" >>$CONFIG_MK;; + solaris*|sunos*) echo "PRIVSEP_SRCS+= privsep-sun.c" >>$CONFIG_MK;; + *) echo "PRIVSEP_SRCS+= privsep-bsd.c" >>$CONFIG_MK;; esac +else + echo "PRIVSEP_SRCS=" >>$CONFIG_MK fi echo "Using compiler .. $CC" @@ -622,16 +624,8 @@ fi [ "$CC" != cc ] && echo "CC= $CC" >>$CONFIG_MK $CC --version | $SED -e '1!d' -if [ -z "$EMBEDDED" -o "$EMBEDDED" = yes ]; then - echo "$DHCPCD_DEFS will be embedded in dhcpcd itself" - echo "DHCPCD_SRCS+= dhcpcd-embedded.c" >>$CONFIG_MK -else - echo "$DHCPCD_DEFS will be installed to $LIBEXECDIR" - echo "CPPFLAGS+= -DEMBEDDED_CONFIG=\\\"$LIBEXECDIR/dhcpcd-definitions.conf\\\"" >>$CONFIG_MK - echo "EMBEDDEDINSTALL= _embeddedinstall" >>$CONFIG_MK -fi - if [ "$PRIVSEP" = yes ]; then + PRIVSEP_CONTROLLER=true printf "Testing for capsicum ... " cat <_capsicum.c #include @@ -642,6 +636,7 @@ EOF if $XCC _capsicum.c -o _capsicum 2>&3; then echo "yes" echo "#define HAVE_CAPSICUM" >>$CONFIG_H + PRIVSEP_CONTROLLER=false else echo "no" fi @@ -657,10 +652,25 @@ EOF if $XCC _pledge.c -o _pledge 2>&3; then echo "yes" echo "#define HAVE_PLEDGE" >>$CONFIG_H + PRIVSEP_CONTROLLER=false else echo "no" fi rm -f _pledge.c _pledge + + if $PRIVSEP_CONTROLLER; then + echo "#define PRIVSEP_CONTROLLER" >>$CONFIG_H + echo "PRIVSEP_SRCS+= privsep-control.c" >>$CONFIG_MK + fi +fi + +if [ -z "$EMBEDDED" -o "$EMBEDDED" = yes ]; then + echo "$DHCPCD_DEFS will be embedded in dhcpcd itself" + echo "DHCPCD_SRCS+= dhcpcd-embedded.c" >>$CONFIG_MK +else + echo "$DHCPCD_DEFS will be installed to $LIBEXECDIR" + echo "CPPFLAGS+= -DEMBEDDED_CONFIG=\\\"$LIBEXECDIR/dhcpcd-definitions.conf\\\"" >>$CONFIG_MK + echo "EMBEDDEDINSTALL= _embeddedinstall" >>$CONFIG_MK fi if [ "$OS" = linux ]; then diff --git a/src/Makefile b/src/Makefile index 28fb6e35..930108da 100644 --- a/src/Makefile +++ b/src/Makefile @@ -15,7 +15,7 @@ CSTD?= c99 CFLAGS+= -std=${CSTD} CPPFLAGS+= -I${TOP} -I${TOP}/src -I./crypt -SRCS+= ${DHCPCD_SRCS} +SRCS+= ${DHCPCD_SRCS} ${PRIVSEP_SRCS} DHCPCD_DEF?= dhcpcd-definitions.conf DHCPCD_DEFS= dhcpcd-definitions.conf dhcpcd-definitions-small.conf diff --git a/src/control.c b/src/control.c index 6b4ecd21..2977423e 100644 --- a/src/control.c +++ b/src/control.c @@ -64,7 +64,6 @@ control_queue_free(struct fd_list *fd) free(fdp->data); free(fdp); } - fd->queue_len = 0; #ifdef CTL_FREE_LIST while ((fdp = TAILQ_FIRST(&fd->free_queue))) { @@ -76,56 +75,118 @@ control_queue_free(struct fd_list *fd) #endif } -static void -control_delete(struct fd_list *fd) +void +control_free(struct fd_list *fd) { +#ifdef PRIVSEP_CONTROLLER + if (fd->ctx->ps_control_client == fd) + fd->ctx->ps_control_client = NULL; +#endif + + eloop_event_remove_writecb(fd->ctx->eloop, fd->fd); TAILQ_REMOVE(&fd->ctx->control_fds, fd, next); - eloop_event_delete(fd->ctx->eloop, fd->fd); - close(fd->fd); control_queue_free(fd); free(fd); } +void +control_delete(struct fd_list *fd) +{ + +#ifdef PRIVSEP_CONTROLLER + if (IN_PRIVSEP_SE(fd->ctx)) + return; +#endif + + eloop_event_delete(fd->ctx->eloop, fd->fd); + close(fd->fd); + control_free(fd); +} + static void control_handle_data(void *arg) { struct fd_list *fd = arg; - char buffer[1024], *e, *p, *argvp[255], **ap, *a; + char buffer[1024]; ssize_t bytes; - size_t len; - int argc; bytes = read(fd->fd, buffer, sizeof(buffer) - 1); + if (bytes == -1 || bytes == 0) { /* Control was closed or there was an error. * Remove it from our list. */ control_delete(fd); return; } - buffer[bytes] = '\0'; - p = buffer; - e = buffer + bytes; + +#ifdef PRIVSEP_CONTROLLER + if (IN_PRIVSEP(fd->ctx)) { + ssize_t err; + + fd->flags |= FD_SENDLEN; + err = ps_ctl_handleargs(fd, buffer, (size_t)bytes); + fd->flags &= ~FD_SENDLEN; + if (err == -1) { + logerr(__func__); + return; + } + if (err == 1 && + ps_ctl_sendargs(fd, buffer, (size_t)bytes) == -1) { + logerr(__func__); + control_delete(fd); + } + return; + } +#endif + + control_recvdata(fd, buffer, (size_t)bytes); +} + +void +control_recvdata(struct fd_list *fd, char *data, size_t len) +{ + char *p = data, *e; + char *argvp[255], **ap; + int argc; /* Each command is \n terminated * Each argument is NULL separated */ - while (p < e) { + while (len != 0) { argc = 0; ap = argvp; - while (p < e) { - argc++; + while (len != 0) { + if (*p == '\0') { + p++; + len--; + continue; + } + e = memchr(p, '\0', len); + if (e == NULL) { + errno = EINVAL; + logerrx("%s: no terminator", __func__); + return; + } if ((size_t)argc >= sizeof(argvp) / sizeof(argvp[0])) { errno = ENOBUFS; + logerrx("%s: no arg buffer", __func__); return; } - a = *ap++ = p; - len = strlen(p); - p += len + 1; - if (len && a[len - 1] == '\n') { - a[len - 1] = '\0'; + *ap++ = p; + argc++; + e++; + len -= (size_t)(e - p); + p = e; + e--; + if (*(--e) == '\n') { + *e = '\0'; break; } } + if (argc == 0) { + logerrx("%s: no args", __func__); + continue; + } *ap = NULL; if (dhcpcd_handleargs(fd->ctx, fd, argc, argvp) == -1) { logerr(__func__); @@ -137,6 +198,26 @@ control_handle_data(void *arg) } } +struct fd_list * +control_new(struct dhcpcd_ctx *ctx, int fd, unsigned int flags) +{ + struct fd_list *l; + + l = malloc(sizeof(*l)); + if (l == NULL) + return NULL; + + l->ctx = ctx; + l->fd = fd; + l->flags = flags; + TAILQ_INIT(&l->queue); +#ifdef CTL_FREE_LIST + TAILQ_INIT(&l->free_queue); +#endif + TAILQ_INSERT_TAIL(&ctx->control_fds, l, next); + return l; +} + static void control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags) { @@ -155,20 +236,19 @@ control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags) fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) goto error; - l = malloc(sizeof(*l)); +#ifdef PRIVSEP_CONTROLLER + if (IN_PRIVSEP(ctx) && !IN_PRIVSEP_SE(ctx)) + ; + else +#endif + fd_flags |= FD_SENDLEN; + + l = control_new(ctx, fd, fd_flags); if (l == NULL) goto error; - l->ctx = ctx; - l->fd = fd; - l->flags = fd_flags; - TAILQ_INIT(&l->queue); - l->queue_len = 0; -#ifdef CTL_FREE_LIST - TAILQ_INIT(&l->free_queue); -#endif - TAILQ_INSERT_TAIL(&ctx->control_fds, l, next); - eloop_event_add(ctx->eloop, l->fd, control_handle_data, l); + if (eloop_event_add(ctx->eloop, l->fd, control_handle_data, l) == -1) + logerr(__func__); return; error: @@ -193,6 +273,26 @@ control_handle_unpriv(void *arg) control_handle1(ctx, ctx->control_unpriv_fd, FD_UNPRIV); } +static int +make_path(char *path, size_t len, const char *ifname, sa_family_t family) +{ + const char *per; + + switch(family) { + case AF_INET: + per = "-4"; + break; + case AF_INET6: + per = "-6"; + break; + default: + per = ""; + break; + } + return snprintf(path, len, CONTROLSOCKET, + ifname ? ifname : "", ifname ? per : "", ifname ? "." : ""); +} + static int make_sock(struct sockaddr_un *sa, const char *ifname, sa_family_t family, bool unpriv) @@ -205,23 +305,8 @@ make_sock(struct sockaddr_un *sa, const char *ifname, sa_family_t family, sa->sun_family = AF_UNIX; if (unpriv) strlcpy(sa->sun_path, UNPRIVSOCKET, sizeof(sa->sun_path)); - else { - const char *per; - - switch(family) { - case AF_INET: - per = "-4"; - break; - case AF_INET6: - per = "-6"; - break; - default: - per = ""; - break; - } - snprintf(sa->sun_path, sizeof(sa->sun_path), CONTROLSOCKET, - ifname ? ifname : "", ifname ? per : "", ifname ? "." : ""); - } + else + make_path(sa->sun_path, sizeof(sa->sun_path), ifname, family); return fd; } @@ -272,6 +357,14 @@ control_start(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family) { int fd; +#ifdef PRIVSEP_CONTROLLER + if (IN_PRIVSEP_SE(ctx)) { + make_path(ctx->control_sock, sizeof(ctx->control_sock), + ifname, family); + return 0; + } +#endif + if ((fd = control_start1(ctx, ifname, family, S_PRIV)) == -1) return -1; @@ -313,8 +406,21 @@ control_stop(struct dhcpcd_ctx *ctx) int retval = 0; struct fd_list *l; - if (ctx->options & DHCPCD_FORKED) - return 0; + while ((l = TAILQ_FIRST(&ctx->control_fds)) != NULL) { + control_free(l); + } + +#ifdef PRIVSEP_CONTROLLER + if (IN_PRIVSEP_SE(ctx)) { + if (ps_root_unlink(ctx, ctx->control_sock) == -1) + retval = -1; + if (ctx->options & DHCPCD_MASTER && + control_unlink(ctx, UNPRIVSOCKET) == -1) + retval = -1; + return retval; + } else if (ctx->options & DHCPCD_FORKED) + return retval; +#endif if (ctx->control_fd != -1) { eloop_event_delete(ctx->eloop, ctx->control_fd); @@ -332,14 +438,6 @@ control_stop(struct dhcpcd_ctx *ctx) retval = -1; } - while ((l = TAILQ_FIRST(&ctx->control_fds))) { - TAILQ_REMOVE(&ctx->control_fds, l, next); - eloop_event_delete(ctx->eloop, l->fd); - close(l->fd); - control_queue_free(l); - free(l); - } - return retval; } @@ -390,23 +488,31 @@ control_writeone(void *arg) { struct fd_list *fd; struct iovec iov[2]; + int iov_len; struct fd_data *data; fd = arg; data = TAILQ_FIRST(&fd->queue); - iov[0].iov_base = &data->data_len; - iov[0].iov_len = sizeof(size_t); - iov[1].iov_base = data->data; - iov[1].iov_len = data->data_len; - if (writev(fd->fd, iov, 2) == -1) { - logerr(__func__); - if (errno != EINTR && errno != EAGAIN) - control_delete(fd); + + if (data->data_flags & FD_SENDLEN) { + iov[0].iov_base = &data->data_len; + iov[0].iov_len = sizeof(size_t); + iov[1].iov_base = data->data; + iov[1].iov_len = data->data_len; + iov_len = 2; + } else { + iov[0].iov_base = data->data; + iov[0].iov_len = data->data_len; + iov_len = 1; + } + + if (writev(fd->fd, iov, iov_len) == -1) { + logerr("%s: write", __func__); + control_delete(fd); return; } TAILQ_REMOVE(&fd->queue, data, next); - fd->queue_len--; #ifdef CTL_FREE_LIST TAILQ_INSERT_TAIL(&fd->free_queue, data, next); #else @@ -415,30 +521,34 @@ control_writeone(void *arg) free(data); #endif - if (TAILQ_FIRST(&fd->queue) == NULL) - eloop_event_remove_writecb(fd->ctx->eloop, fd->fd); + if (TAILQ_FIRST(&fd->queue) != NULL) + return; + + eloop_event_remove_writecb(fd->ctx->eloop, fd->fd); +#ifdef PRIVSEP_CONTROLLER + if (IN_PRIVSEP_SE(fd->ctx) && !(fd->flags & FD_LISTEN)) { + if (ps_ctl_sendeof(fd) == -1) + logerr(__func__); + control_free(fd); + } +#endif } int -control_queue(struct fd_list *fd, void *data, size_t data_len, bool fit) +control_queue(struct fd_list *fd, void *data, size_t data_len) { struct fd_data *d; - if (data_len == 0) - return 0; + if (data_len == 0) { + errno = EINVAL; + return -1; + } #ifdef CTL_FREE_LIST struct fd_data *df; d = NULL; TAILQ_FOREACH(df, &fd->free_queue, next) { - if (!fit) { - if (df->data_size == 0) { - d = df; - break; - } - continue; - } if (d == NULL || d->data_size < df->data_size) { d = df; if (d->data_size <= data_len) @@ -450,28 +560,11 @@ control_queue(struct fd_list *fd, void *data, size_t data_len, bool fit) else #endif { - if (fd->queue_len == CONTROL_QUEUE_MAX) { - errno = ENOBUFS; - return -1; - } - fd->queue_len++; d = calloc(1, sizeof(*d)); if (d == NULL) return -1; } - if (!fit) { -#ifdef CTL_FREE_LIST - if (d->data_size != 0) { - free(d->data); - d->data_size = 0; - } -#endif - d->data = data; - d->data_len = data_len; - goto queue; - } - if (d->data_size == 0) d->data = NULL; if (d->data_size < data_len) { @@ -486,8 +579,8 @@ control_queue(struct fd_list *fd, void *data, size_t data_len, bool fit) } memcpy(d->data, data, data_len); d->data_len = data_len; + d->data_flags = fd->flags & FD_SENDLEN; -queue: TAILQ_INSERT_TAIL(&fd->queue, d, next); eloop_event_add_w(fd->ctx->eloop, fd->fd, control_writeone, fd); return 0; diff --git a/src/control.h b/src/control.h index a702af2f..606f94b6 100644 --- a/src/control.h +++ b/src/control.h @@ -47,6 +47,7 @@ struct fd_data { void *data; size_t data_size; size_t data_len; + unsigned int data_flags; }; TAILQ_HEAD(fd_data_head, fd_data); @@ -56,20 +57,23 @@ struct fd_list { int fd; unsigned int flags; struct fd_data_head queue; - size_t queue_len; #ifdef CTL_FREE_LIST struct fd_data_head free_queue; #endif }; TAILQ_HEAD(fd_list_head, fd_list); -#define FD_LISTEN (1<<0) -#define FD_UNPRIV (1<<1) +#define FD_LISTEN 0x01U +#define FD_UNPRIV 0x02U +#define FD_SENDLEN 0x04U int control_start(struct dhcpcd_ctx *, const char *, sa_family_t); int control_stop(struct dhcpcd_ctx *); int control_open(const char *, sa_family_t, bool); ssize_t control_send(struct dhcpcd_ctx *, int, char * const *); -int control_queue(struct fd_list *, void *, size_t, bool); - +struct fd_list *control_new(struct dhcpcd_ctx *, int, unsigned int); +void control_free(struct fd_list *); +void control_delete(struct fd_list *); +int control_queue(struct fd_list *, void *, size_t); +void control_recvdata(struct fd_list *fd, char *, size_t); #endif diff --git a/src/dhcpcd.c b/src/dhcpcd.c index 905a1029..234761c6 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -1400,6 +1400,11 @@ dhcpcd_signal_cb(int sig, void *arg) unsigned long long opts; int exit_code; + if (ctx->options & DHCPCD_DUMPLEASE) { + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + if (sig != SIGCHLD && ctx->options & DHCPCD_FORKED) { pid_t pid = pidfile_read(ctx->pidfile); if (pid == -1) { @@ -1462,48 +1467,6 @@ dhcpcd_signal_cb(int sig, void *arg) } #endif -static void -dhcpcd_getinterfaces(void *arg) -{ - struct fd_list *fd = arg; - struct interface *ifp; - size_t len; - - len = 0; - TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) { - if (!ifp->active) - continue; - len++; -#ifdef INET - if (D_STATE_RUNNING(ifp)) - len++; -#endif -#ifdef IPV4LL - if (IPV4LL_STATE_RUNNING(ifp)) - len++; -#endif -#ifdef INET6 - if (IPV6_STATE_RUNNING(ifp)) - len++; - if (RS_STATE_RUNNING(ifp)) - len++; -#endif -#ifdef DHCP6 - if (D6_STATE_RUNNING(ifp)) - len++; -#endif - } - if (write(fd->fd, &len, sizeof(len)) != sizeof(len)) - return; - eloop_event_remove_writecb(fd->ctx->eloop, fd->fd); - TAILQ_FOREACH(ifp, fd->ctx->ifaces, next) { - if (!ifp->active) - continue; - if (send_interface(fd, ifp, AF_UNSPEC) == -1) - logerr(__func__); - } -} - int dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, char **argv) @@ -1511,23 +1474,23 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, struct interface *ifp; unsigned long long opts; int opt, oi, do_reboot, do_renew, af = AF_UNSPEC; - size_t len, l; + size_t len, l, nifaces; char *tmp, *p; /* Special commands for our control socket * as the other end should be blocking until it gets the * expected reply we should be safely able just to change the * write callback on the fd */ + /* Make any change here in privsep-control.c as well. */ if (strcmp(*argv, "--version") == 0) { return control_queue(fd, UNCONST(VERSION), - strlen(VERSION) + 1, false); + strlen(VERSION) + 1); } else if (strcmp(*argv, "--getconfigfile") == 0) { return control_queue(fd, UNCONST(fd->ctx->cffile), - strlen(fd->ctx->cffile) + 1, false); + strlen(fd->ctx->cffile) + 1); } else if (strcmp(*argv, "--getinterfaces") == 0) { - eloop_event_add_w(fd->ctx->eloop, fd->fd, - dhcpcd_getinterfaces, fd); - return 0; + optind = argc = 0; + goto dumplease; } else if (strcmp(*argv, "--listen") == 0) { fd->flags |= FD_LISTEN; return 0; @@ -1591,8 +1554,8 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, if (opts & DHCPCD_DUMPLEASE) { ctx->options |= DHCPCD_DUMPLEASE; - size_t nifaces = 0; - +dumplease: + nifaces = 0; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (!ifp->active) continue; @@ -1672,68 +1635,113 @@ dumperr: return 0; } -static int -dhcpcd_readdump(struct dhcpcd_ctx *ctx) +static void dhcpcd_readdump1(void *); + +static void +dhcpcd_readdump2(void *arg) { - int error = 0; - size_t nifaces, buflen = 0, dlen; + struct dhcpcd_ctx *ctx = arg; ssize_t len; - char *buf = NULL; + int exit_code = EXIT_FAILURE; -again1: - len = read(ctx->control_fd, &nifaces, sizeof(nifaces)); + len = read(ctx->control_fd, ctx->ctl_buf + ctx->ctl_bufpos, + ctx->ctl_buflen - ctx->ctl_bufpos); if (len == -1) { - if (errno == EAGAIN) - goto again1; - return -1; + logerr(__func__); + goto finished; + } else if (len == 0) + goto finished; + if ((size_t)len + ctx->ctl_bufpos != ctx->ctl_buflen) { + ctx->ctl_bufpos += (size_t)len; + return; } - if (len != sizeof(nifaces)) { - errno = EINVAL; - return -1; + + script_dump(ctx->ctl_buf, ctx->ctl_buflen); + fflush(stdout); + if (--ctx->ctl_extra != 0) { + putchar('\n'); + eloop_event_add(ctx->eloop, ctx->control_fd, + dhcpcd_readdump1, ctx); + return; } - for (; nifaces > 0; nifaces--) { -again2: - len = read(ctx->control_fd, &dlen, sizeof(dlen)); - if (len == -1) { - if (errno == EAGAIN) - goto again2; - error = -1; - goto out; - } - if (len != sizeof(dlen)) { + exit_code = EXIT_SUCCESS; + +finished: + shutdown(ctx->control_fd, SHUT_RDWR); + eloop_exit(ctx->eloop, exit_code); +} + +static void +dhcpcd_readdump1(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + ssize_t len; + + len = read(ctx->control_fd, &ctx->ctl_buflen, sizeof(ctx->ctl_buflen)); + if (len != sizeof(ctx->ctl_buflen)) { + if (len != -1) errno = EINVAL; - goto out; - } - if (dlen > buflen) { - char *nbuf = realloc(buf, dlen); - if (nbuf == NULL) { - error = -1; - goto out; - } - buf = nbuf; - buflen = dlen; - } - if (dlen == 0) { + goto err; + } + + free(ctx->ctl_buf); + ctx->ctl_buf = malloc(ctx->ctl_buflen); + if (ctx->ctl_buf == NULL) + goto err; + + ctx->ctl_bufpos = 0; + eloop_event_add(ctx->eloop, ctx->control_fd, + dhcpcd_readdump2, ctx); + return; + +err: + logerr(__func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); +} + +static void +dhcpcd_readdump0(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + ssize_t len; + + len = read(ctx->control_fd, &ctx->ctl_extra, sizeof(ctx->ctl_extra)); + if (len != sizeof(ctx->ctl_extra)) { + if (len != -1) errno = EINVAL; - error = -1; - goto out; - } -again3: - if (read(ctx->control_fd, buf, dlen) != (ssize_t)dlen) { - if (errno == EAGAIN) - goto again3; - error = -1; - goto out; - } - script_dump(buf, dlen); - fflush(stdout); - if (nifaces != 1) - putchar('\n'); + logerr(__func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; } -out: - free(buf); - return error; + if (ctx->ctl_extra == 0) { + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return; + } + + eloop_event_add(ctx->eloop, ctx->control_fd, + dhcpcd_readdump1, ctx); +} + +static void +dhcpcd_readdumptimeout(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + logerrx(__func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); +} + +static int +dhcpcd_readdump(struct dhcpcd_ctx *ctx) +{ + + ctx->options |= DHCPCD_FORKED; + if (eloop_timeout_add_sec(ctx->eloop, 5, + dhcpcd_readdumptimeout, ctx) == -1) + return -1; + return eloop_event_add(ctx->eloop, ctx->control_fd, + dhcpcd_readdump0, ctx); } static void @@ -1844,6 +1852,9 @@ main(int argc, char **argv) #endif #ifdef PRIVSEP ctx.ps_root_fd = ctx.ps_data_fd = -1; +#ifdef PRIVSEP_COMTROLLER + ctx.ps_ctl_fd = -1; +#endif TAILQ_INIT(&ctx.ps_processes); #endif rt_init(&ctx); @@ -2156,6 +2167,7 @@ printpidfile: logerr("%s: dhcpcd_readdump", __func__); goto exit_failure; } + goto run_loop; } goto exit_success; } else { @@ -2166,6 +2178,8 @@ printpidfile: logerrx("dhcpcd is not running"); goto exit_failure; } + if (errno == EPERM || errno == EACCES) + goto exit_failure; } ctx.options &= ~DHCPCD_FORKED; } @@ -2260,7 +2274,7 @@ printpidfile: } #ifdef PRIVSEP - if (ctx.options & DHCPCD_PRIVSEP && ps_start(&ctx) == -1) { + if (IN_PRIVSEP(&ctx) && ps_start(&ctx) == -1) { logerr("ps_start"); goto exit_failure; } @@ -2268,12 +2282,14 @@ printpidfile: goto run_loop; #endif - if (!(ctx.options & DHCPCD_TEST) && - control_start(&ctx, - ctx.options & DHCPCD_MASTER ? NULL : argv[optind], family) == -1) - { - logerr("%s: control_start", __func__); - goto exit_failure; + if (!(ctx.options & DHCPCD_TEST)) { + if (control_start(&ctx, + ctx.options & DHCPCD_MASTER ? + NULL : argv[optind], family) == -1) + { + logerr("%s: control_start", __func__); + goto exit_failure; + } } #ifdef PLUGIN_DEV @@ -2479,6 +2495,7 @@ exit1: loginfox(PACKAGE " exited"); logclose(); free(ctx.logfile); + free(ctx.ctl_buf); #ifdef SETPROCTITLE_H setproctitle_free(); #endif diff --git a/src/dhcpcd.h b/src/dhcpcd.h index 21497559..7ab95e86 100644 --- a/src/dhcpcd.h +++ b/src/dhcpcd.h @@ -144,6 +144,11 @@ struct dhcpcd_ctx { size_t duid_len; struct if_head *ifaces; + char *ctl_buf; + size_t ctl_buflen; + size_t ctl_bufpos; + size_t ctl_extra; + rb_tree_t routes; /* our routes */ #ifdef RT_FREE_ROUTE_TABLE rb_tree_t froutes; /* free routes for re-use */ @@ -204,6 +209,13 @@ struct dhcpcd_ctx { struct ps_process_head ps_processes; /* List of spawned processes */ pid_t ps_inet_pid; int ps_inet_fd; /* Network Proxy commands and data */ +#ifdef PRIVSEP_CONTROLLER + pid_t ps_control_pid; + int ps_control_fd; + int ps_control_data_fd; + struct fd_list *ps_control; + struct fd_list *ps_control_client; +#endif #endif #ifdef INET diff --git a/src/privsep-control.c b/src/privsep-control.c new file mode 100644 index 00000000..114603a9 --- /dev/null +++ b/src/privsep-control.c @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd, control proxy + * Copyright (c) 2006-2020 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 "dhcpcd.h" +#include "control.h" +#include "eloop.h" +#include "logerr.h" +#include "privsep.h" + +int +ps_ctl_limitresources(struct dhcpcd_ctx *ctx) +{ + struct rlimit rzero = { .rlim_cur = 0, .rlim_max = 0 }; + + if (ctx->ps_control_pid != getpid()) { + /* Prohibit new files, sockets, etc */ + if (setrlimit(RLIMIT_NOFILE, &rzero) == -1) { + logerr("setrlimit RLIMIT_NOFILE"); + return -1; + } + } + + /* Prohibit large files */ + if (setrlimit(RLIMIT_FSIZE, &rzero) == -1) { + logerr("setrlimit RLIMIT_FSIZE"); + return -1; + } + + /* Prohibit forks */ + if (setrlimit(RLIMIT_NPROC, &rzero) == -1) { + logerr("setrlimit RLIMIT_NPROC"); + return -1; + } + + return 0; +} + +static int +ps_ctl_startcb(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + sa_family_t af; + + if (ctx->options & DHCPCD_MASTER) { + setproctitle("[control proxy]"); + af = AF_UNSPEC; + } else { + setproctitle("[control proxy] %s%s%s", + ctx->ifv[0], + ctx->options & DHCPCD_IPV4 ? " [ip4]" : "", + ctx->options & DHCPCD_IPV6 ? " [ip6]" : ""); + if ((ctx->options & + (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV4) + af = AF_INET; + else if ((ctx->options & + (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV6) + af = AF_INET6; + else + af = AF_UNSPEC; + } + + ctx->ps_control_pid = getpid(); + + return control_start(ctx, + ctx->options & DHCPCD_MASTER ? NULL : *ctx->ifv, af); +} + +static ssize_t +ps_ctl_recvmsgcb(void *arg, struct ps_msghdr *psm, __unused struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (psm->ps_cmd != PS_CTL_EOF) { + errno = ENOTSUP; + return -1; + } + + if (ctx->ps_control_client != NULL) + ctx->ps_control_client = NULL; + return 0; +} + +static void +ps_ctl_recvmsg(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvpsmsg(ctx, ctx->ps_control_fd, ps_ctl_recvmsgcb, ctx) == -1) + logerr(__func__); +} + +static void +ps_ctl_signalcb(int sig, void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + /* Ignore SIGINT, respect PS_STOP command or SIGTERM. */ + if (sig == SIGINT) + return; + + shutdown(ctx->ps_control_fd, SHUT_RDWR); + eloop_exit(ctx->eloop, sig == SIGTERM ? EXIT_SUCCESS : EXIT_FAILURE); +} + +ssize_t +ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len) +{ + + /* Make any change here in dhcpcd.c as well. */ + if (strncmp(data, "--version", + MIN(strlen("--version"), len)) == 0) { + return control_queue(fd, UNCONST(VERSION), + strlen(VERSION) + 1); + } else if (strncmp(data, "--getconfigfile", + MIN(strlen("--getconfigfile"), len)) == 0) { + return control_queue(fd, UNCONST(fd->ctx->cffile), + strlen(fd->ctx->cffile) + 1); + } else if (strncmp(data, "--listen", + MIN(strlen("--listen"), len)) == 0) { + fd->flags |= FD_LISTEN; + return 0; + } + + if (fd->ctx->ps_control_client != NULL && + fd->ctx->ps_control_client != fd) + { + logerrx("%s: cannot handle another client", __func__); + return 0; + } + return 1; +} + +static ssize_t +ps_ctl_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg) +{ + struct dhcpcd_ctx *ctx = arg; + struct iovec *iov = msg->msg_iov; + struct fd_list *fd; + unsigned int fd_flags = FD_SENDLEN; + + switch (psm->ps_flags) { + case PS_CTL_PRIV: + break; + case PS_CTL_UNPRIV: + fd_flags |= FD_UNPRIV; + break; + } + + switch (psm->ps_cmd) { + case PS_CTL: + if (msg->msg_iovlen != 1) { + errno = EINVAL; + return -1; + } + if (ctx->ps_control_client != NULL) { + logerrx("%s: cannot handle another client", __func__); + return 0; + } + fd = control_new(ctx, ctx->ps_control_data_fd, fd_flags); + if (fd == NULL) + return -1; + ctx->ps_control_client = fd; + control_recvdata(fd, iov->iov_base, iov->iov_len); + break; + case PS_CTL_EOF: + fd = ctx->ps_control_client; + control_free(ctx->ps_control_client); + break; + default: + errno = ENOTSUP; + return -1; + } + return 0; +} + +static void +ps_ctl_dodispatch(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + + if (ps_recvpsmsg(ctx, ctx->ps_control_fd, ps_ctl_dispatch, ctx) == -1) + logerr(__func__); +} + +static void +ps_ctl_recv(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + char buf[BUFSIZ]; + ssize_t len; + + errno = 0; + len = read(ctx->ps_control_data_fd, buf, sizeof(buf)); + if (len == -1 || len == 0) { + logerr("%s: read", __func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + if (ctx->ps_control_client == NULL) /* client disconnected */ + return; + errno = 0; + if (control_queue(ctx->ps_control_client, buf, (size_t)len) == -1) + logerr("%s: control_queue", __func__); +} + +static void +ps_ctl_listen(void *arg) +{ + struct dhcpcd_ctx *ctx = arg; + char buf[BUFSIZ]; + ssize_t len; + struct fd_list *fd; + + errno = 0; + len = read(ctx->ps_control->fd, buf, sizeof(buf)); + if (len == -1 || len == 0) { + logerr("%s: read", __func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); + return; + } + + /* Send to our listeners */ + TAILQ_FOREACH(fd, &ctx->control_fds, next) { + if (!(fd->flags & FD_LISTEN)) + continue; + if (control_queue(fd, buf, (size_t)len)== -1) + logerr("%s: control_queue", __func__); + } +} + +pid_t +ps_ctl_start(struct dhcpcd_ctx *ctx) +{ + int data_fd[2], listen_fd[2]; + pid_t pid; + + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, data_fd) == -1) + return -1; + if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, listen_fd) == -1) + return -1; +#ifdef PRIVSEP_RIGHTS + if (ps_rights_limit_fdpair(data_fd) == -1) + return -1; + if (ps_rights_limit_fdpair(listen_fd) == -1) + return -1; +#endif + + pid = ps_dostart(ctx, &ctx->ps_control_pid, &ctx->ps_control_fd, + ps_ctl_recvmsg, ps_ctl_dodispatch, ctx, + ps_ctl_startcb, ps_ctl_signalcb, + PSF_DROPPRIVS); + + if (pid != 0) { + ctx->ps_control_data_fd = data_fd[1]; + close(data_fd[0]); + ctx->ps_control = control_new(ctx, + listen_fd[1], FD_SENDLEN | FD_LISTEN); + if (ctx->ps_control == NULL) + return -1; + close(listen_fd[0]); + return pid; + } else if (pid == -1) + return -1; + + ctx->ps_control_data_fd = data_fd[0]; + close(data_fd[1]); + if (eloop_event_add(ctx->eloop, ctx->ps_control_data_fd, + ps_ctl_recv, ctx) == -1) + return -1; + + ctx->ps_control = control_new(ctx, + listen_fd[0], 0); + close(listen_fd[1]); + if (ctx->ps_control == NULL) + return -1; + if (eloop_event_add(ctx->eloop, ctx->ps_control->fd, + ps_ctl_listen, ctx) == -1) + return -1; + return 0; +} + +int +ps_ctl_stop(struct dhcpcd_ctx *ctx) +{ + + return ps_dostop(ctx, &ctx->ps_control_pid, &ctx->ps_control_fd); +} + +ssize_t +ps_ctl_sendargs(struct fd_list *fd, void *data, size_t len) +{ + struct dhcpcd_ctx *ctx = fd->ctx; + + if (ctx->ps_control_client != NULL && ctx->ps_control_client != fd) + logerrx("%s: cannot deal with another client", __func__); + ctx->ps_control_client = fd; + return ps_sendcmd(ctx, ctx->ps_control_fd, PS_CTL, + fd->flags & FD_UNPRIV ? PS_CTL_UNPRIV : PS_CTL_PRIV, + data, len); +} + +ssize_t +ps_ctl_sendeof(struct fd_list *fd) +{ + struct dhcpcd_ctx *ctx = fd->ctx; + + return ps_sendcmd(ctx, ctx->ps_control_fd, PS_CTL_EOF, 0, NULL, 0); +} diff --git a/src/privsep-control.h b/src/privsep-control.h new file mode 100644 index 00000000..7f0e473a --- /dev/null +++ b/src/privsep-control.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: BSD-2-Clause */ +/* + * Privilege Separation for dhcpcd + * Copyright (c) 2006-2020 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. + */ + +#ifndef PRIVSEP_CTL_H +#define PRIVSEP_CTL_H + +#define IN_PRIVSEP_CONTROLLER(ctx) \ + (IN_PRIVSEP((ctx)) && (ctx)->ps_control_pid == getpid()) + +int ps_ctl_limitresources(struct dhcpcd_ctx *); +pid_t ps_ctl_start(struct dhcpcd_ctx *); +int ps_ctl_stop(struct dhcpcd_ctx *); +ssize_t ps_ctl_handleargs(struct fd_list *, char *, size_t); +ssize_t ps_ctl_sendargs(struct fd_list *, void *, size_t); +ssize_t ps_ctl_sendeof(struct fd_list *fd); + +#endif diff --git a/src/privsep.c b/src/privsep.c index 3b836893..a4b03fde 100644 --- a/src/privsep.c +++ b/src/privsep.c @@ -39,7 +39,6 @@ * this in a script or something. */ -#include #include #include #include @@ -129,36 +128,11 @@ ps_dropprivs(struct dhcpcd_ctx *ctx) return -1; } -#if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE) - /* Resource limits are not needed for these sandboxes */ -#else - struct rlimit rzero = { .rlim_cur = 0, .rlim_max = 0 }; - - /* We can't use RLIMIT_NOFILE because that breaks our control socket. - * XXX Offload to a new process? */ -#if 0 -#ifndef __linux__ /* breaks ppoll */ - /* Prohibit new files, sockets, etc */ - if (setrlimit(RLIMIT_NOFILE, &rzero) == -1) { - logerr("setrlimit RLIMIT_NOFILE"); - return -1; - } -#endif -#endif - - /* Prohibit large files */ - if (setrlimit(RLIMIT_FSIZE, &rzero) == -1) { - logerr("setrlimit RLIMIT_FSIZE"); +#ifdef PRIVSEP_CONTROLLER + if (ps_ctl_limitresources(ctx) == -1) return -1; - } - -#ifdef RLIMIT_NPROC - /* Prohibit forks */ - if (setrlimit(RLIMIT_NPROC, &rzero) == -1) { - logerr("setrlimit RLIMIT_NPROC"); - return -1; - } -#endif +#elif !defined(HAVE_CAPSIUM) && !defined(HAVE_PLEDGE) +#warning No sandbox support #endif return 0; @@ -437,6 +411,18 @@ ps_start(struct dhcpcd_ctx *ctx) } started: +#ifdef PRIVSEP_CONTROLLER + if (!(ctx->options & DHCPCD_TEST)) { + switch (pid = ps_ctl_start(ctx)) { + case -1: + return -1; + case 0: + return 0; + default: + logdebugx("spawned controller on PID %d", pid); + } + } +#endif #ifdef ARC4RANDOM_H /* Seed the random number generator early incase it needs /dev/urandom @@ -491,6 +477,12 @@ ps_stop(struct dhcpcd_ctx *ctx) ctx->eloop == NULL) return 0; +#ifdef PRIVSEP_CONTROLLER + r = ps_ctl_stop(ctx); + if (r != 0) + ret = r; +#endif + r = ps_inet_stop(ctx); if (r != 0) ret = r; diff --git a/src/privsep.h b/src/privsep.h index 5d7b4ec0..7428adc7 100644 --- a/src/privsep.h +++ b/src/privsep.h @@ -50,6 +50,8 @@ #define PS_WRITEFILE 0x0015 #define PS_FILEMTIME 0x0016 #define PS_AUTH_MONORDM 0x0017 +#define PS_CTL 0x0018 +#define PS_CTL_EOF 0x0019 /* BSD Commands */ #define PS_IOCTLLINK 0x0101 @@ -69,6 +71,10 @@ #define PS_DEV_IFREMOVED 0x0002 #define PS_DEV_IFUPDATED 0x0003 +/* Control Type (via flags) */ +#define PS_CTL_PRIV 0x0301 +#define PS_CTL_UNPRIV 0x0302 + /* Process commands */ #define PS_START 0x4000 #define PS_STOP 0x8000 @@ -157,6 +163,9 @@ TAILQ_HEAD(ps_process_head, ps_process); #ifdef INET #include "privsep-bpf.h" #endif +#ifdef PRIVSEP_CONTROLLER +#include "privsep-control.h" +#endif int ps_init(struct dhcpcd_ctx *); int ps_dropprivs(struct dhcpcd_ctx *); diff --git a/src/script.c b/src/script.c index 282b5ac2..126d9249 100644 --- a/src/script.c +++ b/src/script.c @@ -560,7 +560,7 @@ send_interface1(struct fd_list *fd, const struct interface *ifp, len = make_env(ifp->ctx, ifp, reason); if (len == -1) return -1; - return control_queue(fd, ctx->script_buf, (size_t)len, 1); + return control_queue(fd, ctx->script_buf, (size_t)len); } int @@ -752,8 +752,7 @@ send_listeners: TAILQ_FOREACH(fd, &ctx->control_fds, next) { if (!(fd->flags & FD_LISTEN)) continue; - if (control_queue(fd, ctx->script_buf, ctx->script_buflen, - true) == -1) + if (control_queue(fd, ctx->script_buf, ctx->script_buflen)== -1) logerr("%s: control_queue", __func__); else status = 1;