From 2716d832cc4fdca101282ee846e1ff522c591e53 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Sun, 5 Jul 2026 15:15:57 +0100 Subject: [PATCH 01/17] control: remove unprivileged socket Instead workout credentials from the connected socket by getpeereid(2). Remove the limit of only one connected control client. This needs some testing and is only expected to compile on the BSDs right now. --- src/control.c | 238 +++++++++++++++++++++++++----------------- src/control.h | 8 +- src/dhcpcd.8.in | 6 +- src/dhcpcd.c | 20 ++-- src/dhcpcd.h | 15 ++- src/privsep-control.c | 105 ++++++++----------- src/privsep-control.h | 4 +- src/privsep-root.c | 46 ++++++++ src/privsep-root.h | 1 + src/privsep.c | 10 +- src/privsep.h | 7 +- 11 files changed, 261 insertions(+), 199 deletions(-) diff --git a/src/control.c b/src/control.c index ad006c20..09aebc04 100644 --- a/src/control.c +++ b/src/control.c @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -80,11 +81,6 @@ control_queue_free(struct fd_list *fd) void control_free(struct fd_list *fd) { -#ifdef PRIVSEP - if (fd->ctx->ps_control_client == fd) - fd->ctx->ps_control_client = NULL; -#endif - eloop_event_delete(fd->ctx->eloop, fd->fd); close(fd->fd); TAILQ_REMOVE(&fd->ctx->control_fds, fd, next); @@ -95,40 +91,59 @@ control_free(struct fd_list *fd) static void control_hangup(struct fd_list *fd) { -#ifdef PRIVSEP - if (IN_PRIVSEP(fd->ctx)) { - if (ps_ctl_sendeof(fd) == -1) - logerr(__func__); - } -#endif control_free(fd); } static int control_handle_read(struct fd_list *fd) { - char buffer[1024]; - ssize_t bytes; - - bytes = read(fd->fd, buffer, sizeof(buffer) - 1); + uid_t uid; + gid_t gid; + char buf[BUFSIZ]; + struct iovec iov[] = { { + .iov_base = buf, + .iov_len = sizeof(buf), + } }; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = __arraycount(iov), + }; + ssize_t bytes, err; + + bytes = recvmsg(fd->fd, &msg, 0); if (bytes == -1) logerr(__func__); if (bytes == -1 || bytes == 0) return (int)bytes; + if (getpeereid(fd->fd, &uid, &gid) == -1) { + logerr("%s: getpeereid", __func__); + return -1; + } + #ifdef PRIVSEP if (IN_PRIVSEP(fd->ctx)) { - ssize_t err; + ssize_t perr; + + perr = ps_root_user_ispriv(fd->ctx, uid, gid); + if (perr == -1) { + logerr(__func__); + return -1; + } + if (perr == 0) + fd->flags &= ~FD_PRIV; + else + fd->flags |= FD_PRIV; fd->flags |= FD_SENDLEN; - err = ps_ctl_handleargs(fd, buffer, (size_t)bytes); + perr = ps_ctl_handleargs(fd, buf, (size_t)bytes); fd->flags &= ~FD_SENDLEN; - if (err == -1) { + if (perr == -1) { logerr(__func__); return 0; } - if (err == 1 && - ps_ctl_sendargs(fd, buffer, (size_t)bytes) == -1) { + iov[0].iov_len = (size_t)bytes; + if (perr == 1 && ps_ctl_sendmsg(fd, &msg) == -1) { logerr(__func__); return -1; } @@ -136,7 +151,16 @@ control_handle_read(struct fd_list *fd) } #endif - return control_recvdata(fd, buffer, (size_t)bytes); + err = control_user_ispriv(fd->ctx, uid, gid); + if (err == -1) { + logerr(__func__); + return -1; + } + if (err == 0) + fd->flags &= ~FD_PRIV; + else + fd->flags |= FD_PRIV; + return control_recvmsg(fd, &msg, (size_t)bytes); } static int @@ -181,13 +205,6 @@ control_handle_write(struct fd_list *fd) if (TAILQ_FIRST(&fd->queue) != NULL) return 0; -#ifdef PRIVSEP - if (IN_PRIVSEP_SE(fd->ctx) && !(fd->flags & FD_LISTEN)) { - if (ps_ctl_sendeof(fd) == -1) - logerr(__func__); - } -#endif - /* Done sending data, stop watching write to fd */ if (eloop_event_add(fd->ctx->eloop, fd->fd, ELE_READ, control_handle_data, fd) == -1) @@ -211,7 +228,7 @@ control_handle_data(void *arg, unsigned short events) } if (events & ELE_READ) { err = control_handle_read(fd); - if (err == -1 || err == 0) + if ((err == -1 && errno != EPERM) || err == 0) goto hangup; } if (events & ELE_HANGUP) @@ -224,12 +241,21 @@ control_handle_data(void *arg, unsigned short events) } int -control_recvdata(struct fd_list *fd, char *data, size_t len) +control_recvmsg(struct fd_list *fd, struct msghdr *msg, size_t len) { - char *p = data, *e; + struct iovec *iov; + char *p, *e; char *argvp[255], **ap; int argc; + if (msg->msg_iovlen == 0) { + errno = EINVAL; + return -1; + } + + iov = msg->msg_iov; + p = (char *)iov->iov_base; + /* Each command is \n terminated * Each argument is NULL separated */ while (len != 0) { @@ -271,7 +297,7 @@ control_recvdata(struct fd_list *fd, char *data, size_t len) *ap = NULL; if (dhcpcd_handleargs(fd->ctx, fd, argc, argvp) == -1) { logerr(__func__); - if (errno != EINTR && errno != EAGAIN) + if (errno != EINTR && errno != EAGAIN && errno != EPERM) return -1; } } @@ -279,11 +305,31 @@ control_recvdata(struct fd_list *fd, char *data, size_t len) return 1; } +struct fd_list * +control_find(struct dhcpcd_ctx *ctx, int fd) +{ + struct fd_list *l; + + TAILQ_FOREACH(l, &ctx->control_fds, next) { + if (l->fd == fd) + return l; + } + + errno = ESRCH; + return NULL; +} + struct fd_list * control_new(struct dhcpcd_ctx *ctx, int fd, unsigned int flags) { struct fd_list *l; + l = control_find(ctx, fd); + if (l != NULL) { + l->flags = flags; + return l; + } + l = malloc(sizeof(*l)); if (l == NULL) return NULL; @@ -306,7 +352,7 @@ control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags, struct sockaddr_un run; socklen_t len; struct fd_list *l; - int fd, flags; + int fd, flags = 1; if (events != ELE_READ) logerrx("%s: unexpected event 0x%04x", __func__, events); @@ -335,6 +381,7 @@ control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags, if (eloop_event_add(ctx->eloop, l->fd, ELE_READ, control_handle_data, l) == -1) logerr("%s: eloop_event_add", __func__); + return; error: @@ -351,20 +398,10 @@ control_handle(void *arg, unsigned short events) control_handle1(ctx, ctx->control_fd, 0, events); } -static void -control_handle_unpriv(void *arg, unsigned short events) -{ - struct dhcpcd_ctx *ctx = arg; - - control_handle1(ctx, ctx->control_unpriv_fd, FD_UNPRIV, events); -} - static int -make_path(char *path, size_t len, const char *ifname, sa_family_t family, - bool unpriv) +make_path(char *path, size_t len, const char *ifname, sa_family_t family) { const char *per; - const char *sunpriv; switch (family) { case AF_INET: @@ -377,70 +414,57 @@ make_path(char *path, size_t len, const char *ifname, sa_family_t family, per = ""; break; } - if (unpriv) - sunpriv = ifname ? ".unpriv" : "unpriv."; - else - sunpriv = ""; return snprintf(path, len, CONTROLSOCKET, ifname ? ifname : "", - ifname ? per : "", sunpriv, ifname ? "." : ""); + ifname ? per : "", "", ifname ? "." : ""); } static int -make_sock(struct sockaddr_un *sa, const char *ifname, sa_family_t family, - bool unpriv) +make_sock(struct sockaddr_un *sa, const char *ifname, sa_family_t family) { int fd; - if ((fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0)) == -1) + if ((fd = xsocket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) return -1; memset(sa, 0, sizeof(*sa)); sa->sun_family = AF_UNIX; - make_path(sa->sun_path, sizeof(sa->sun_path), ifname, family, unpriv); + make_path(sa->sun_path, sizeof(sa->sun_path), ifname, family); return fd; } -#define S_PRIV (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) -#define S_UNPRIV (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) +#define SOCK_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) static int -control_start1(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family, - mode_t fmode) +control_start1(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family) { struct sockaddr_un sa; int fd; socklen_t len; - fd = make_sock(&sa, ifname, family, (fmode & S_UNPRIV) == S_UNPRIV); + fd = make_sock(&sa, ifname, family); if (fd == -1) return -1; len = (socklen_t)SUN_LEN(&sa); unlink(sa.sun_path); if (bind(fd, (struct sockaddr *)&sa, len) == -1 || - chmod(sa.sun_path, fmode) == -1 || + chmod(sa.sun_path, 0666) == -1 || (ctx->control_group && chown(sa.sun_path, geteuid(), ctx->control_group) == -1) || - listen(fd, sizeof(ctx->control_fds)) == -1) { - close(fd); - unlink(sa.sun_path); - return -1; - } + listen(fd, sizeof(ctx->control_fds)) == -1) + goto err; #ifdef PRIVSEP_RIGHTS - if (IN_PRIVSEP(ctx) && ps_rights_limit_fd_fctnl(fd) == -1) { - close(fd); - unlink(sa.sun_path); - return -1; - } + if (IN_PRIVSEP(ctx) && ps_rights_limit_fd_fctnl(fd) == -1) + goto err; #endif - if ((fmode & S_UNPRIV) == S_UNPRIV) - strlcpy(ctx->control_sock_unpriv, sa.sun_path, - sizeof(ctx->control_sock_unpriv)); - else - strlcpy(ctx->control_sock, sa.sun_path, - sizeof(ctx->control_sock)); + strlcpy(ctx->control_sock, sa.sun_path, sizeof(ctx->control_sock)); return fd; + +err: + close(fd); + unlink(sa.sun_path); + return -1; } int @@ -451,14 +475,12 @@ control_start(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family) #ifdef PRIVSEP if (IN_PRIVSEP_SE(ctx)) { make_path(ctx->control_sock, sizeof(ctx->control_sock), ifname, - family, false); - make_path(ctx->control_sock_unpriv, - sizeof(ctx->control_sock_unpriv), ifname, family, true); + family); return 0; } #endif - if ((fd = control_start1(ctx, ifname, family, S_PRIV)) == -1) + if ((fd = control_start1(ctx, ifname, family)) == -1) return -1; ctx->control_fd = fd; @@ -466,12 +488,6 @@ control_start(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family) -1) logerr("%s: eloop_event_add", __func__); - if ((fd = control_start1(ctx, ifname, family, S_UNPRIV)) != -1) { - ctx->control_unpriv_fd = fd; - if (eloop_event_add(ctx->eloop, fd, ELE_READ, - control_handle_unpriv, ctx) == -1) - logerr("%s: eloop_event_add", __func__); - } return ctx->control_fd; } @@ -508,9 +524,6 @@ control_stop(struct dhcpcd_ctx *ctx) if (ctx->control_sock[0] != '\0' && ps_root_unlink(ctx, ctx->control_sock) == -1) retval = -1; - if (ctx->control_sock_unpriv[0] != '\0' && - ps_root_unlink(ctx, ctx->control_sock_unpriv) == -1) - retval = -1; return retval; } else if (ctx->options & DHCPCD_FORKED) return retval; @@ -524,24 +537,16 @@ control_stop(struct dhcpcd_ctx *ctx) retval = -1; } - if (ctx->control_unpriv_fd != -1) { - eloop_event_delete(ctx->eloop, ctx->control_unpriv_fd); - close(ctx->control_unpriv_fd); - ctx->control_unpriv_fd = -1; - if (control_unlink(ctx, ctx->control_sock_unpriv) == -1) - retval = -1; - } - return retval; } int -control_open(const char *ifname, sa_family_t family, bool unpriv) +control_open(const char *ifname, sa_family_t family) { struct sockaddr_un sa; int fd; - if ((fd = make_sock(&sa, ifname, family, unpriv)) != -1) { + if ((fd = make_sock(&sa, ifname, family)) != -1) { socklen_t len; len = (socklen_t)SUN_LEN(&sa); @@ -632,3 +637,40 @@ control_queue(struct fd_list *fd, void *data, size_t data_len) return eloop_event_add(fd->ctx->eloop, fd->fd, events, control_handle_data, fd); } + +int +control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid) +{ + gid_t *groups = NULL, *gp; + struct passwd *pw; + int ngroups = 10, err = -1; + + pw = getpwuid(uid); + if (pw == NULL) + return -1; + + groups = reallocarray(groups, (size_t)ngroups, sizeof(*groups)); + if (groups == NULL) + return -1; + + if (getgrouplist(pw->pw_name, gid, groups, &ngroups) == -1) { + gp = reallocarray(groups, (size_t)ngroups, sizeof(*groups)); + if (gp == NULL) + goto out; + groups = gp; + if (getgrouplist(pw->pw_name, gid, groups, &ngroups) == -1) + goto out; + } + + for (gp = groups; ngroups != 0; ngroups--, gp++) { + if (*gp == ctx->control_group) { + err = 1; + goto out; + } + } + err = 0; + +out: + free(groups); + return err; +} diff --git a/src/control.h b/src/control.h index 9e966f77..85086025 100644 --- a/src/control.h +++ b/src/control.h @@ -66,16 +66,18 @@ struct fd_list { TAILQ_HEAD(fd_list_head, fd_list); #define FD_LISTEN 0x01U -#define FD_UNPRIV 0x02U +#define FD_PRIV 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); +int control_open(const char *, sa_family_t); ssize_t control_send(struct dhcpcd_ctx *, int, char *const *); +struct fd_list *control_find(struct dhcpcd_ctx *, int); 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); -int control_recvdata(struct fd_list *fd, char *, size_t); +int control_recvmsg(struct fd_list *, struct msghdr *, size_t); +int control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid); #endif diff --git a/src/dhcpcd.8.in b/src/dhcpcd.8.in index 86b69132..26d37772 100644 --- a/src/dhcpcd.8.in +++ b/src/dhcpcd.8.in @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd May 8, 2025 +.Dd July 5, 2026 .Dt DHCPCD 8 .Os .Sh NAME @@ -861,12 +861,8 @@ running on the .Ar interface . .It Pa @RUNDIR@/sock Control socket to the manager daemon. -.It Pa @RUNDIR@/unpriv.sock -Unprivileged socket to the manager daemon, only allows state retrieval. .It Pa @RUNDIR@/ Ns Ar interface Ns .sock Control socket to per interface daemon. -.It Pa @RUNDIR@/ Ns Ar interface Ns .unpriv.sock -Unprivileged socket to per interface daemon, only allows state retrieval. .El .Sh SEE ALSO .Xr fnmatch 3 , diff --git a/src/dhcpcd.c b/src/dhcpcd.c index 36ee0e3b..f45c4f38 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -1704,7 +1704,7 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, } /* Only privileged users can control dhcpcd via the socket. */ - if (fd->flags & FD_UNPRIV) { + if (!(fd->flags & FD_PRIV)) { errno = EPERM; return -1; } @@ -2078,7 +2078,7 @@ main(int argc, char **argv, char **envp) ifo = NULL; ctx.cffile = CONFIG; ctx.script = UNCONST(dhcpcd_default_script); - ctx.control_fd = ctx.control_unpriv_fd = ctx.link_fd = -1; + ctx.control_fd = ctx.link_fd = -1; ctx.pf_inet_fd = -1; #ifdef PF_LINK ctx.pf_link_fd = -1; @@ -2421,20 +2421,12 @@ main(int argc, char **argv, char **envp) if (!(ctx.options & DHCPCD_TEST)) { ctx.options |= DHCPCD_FORKED; /* avoid socket unlink */ if (!(ctx.options & DHCPCD_MANAGER)) - ctx.control_fd = control_open(argv[optind], family, - ctx.options & DHCPCD_DUMPLEASE); + ctx.control_fd = control_open(argv[optind], family); if (!(ctx.options & DHCPCD_MANAGER) && ctx.control_fd == -1) - ctx.control_fd = control_open(argv[optind], AF_UNSPEC, - ctx.options & DHCPCD_DUMPLEASE); + ctx.control_fd = control_open(argv[optind], AF_UNSPEC); if (ctx.control_fd == -1) - ctx.control_fd = control_open(NULL, AF_UNSPEC, - ctx.options & DHCPCD_DUMPLEASE); + ctx.control_fd = control_open(NULL, AF_UNSPEC); if (ctx.control_fd != -1) { -#ifdef PRIVSEP - if (IN_PRIVSEP(&ctx) && - ps_managersandbox(&ctx, NULL) == -1) - goto exit_failure; -#endif if (!(ctx.options & DHCPCD_DUMPLEASE)) loginfox("sending commands to dhcpcd process"); len = control_send(&ctx, argc, argv); @@ -2580,6 +2572,8 @@ main(int argc, char **argv, char **envp) logdebugx("spawned manager process on PID %d", (int)getpid()); start_manager: + + logdebugx("spawned manager process on PID %d", (int)getpid()); ctx.options |= DHCPCD_STARTED; if ((pid = pidfile_lock(ctx.pidfile)) != 0) { logerr("%s: pidfile_lock %d", __func__, (int)pid); diff --git a/src/dhcpcd.h b/src/dhcpcd.h index cb2a9303..e1865cb5 100644 --- a/src/dhcpcd.h +++ b/src/dhcpcd.h @@ -174,10 +174,8 @@ struct dhcpcd_ctx { size_t io_buflen; int control_fd; - int control_unpriv_fd; struct fd_list_head control_fds; char control_sock[sizeof(CONTROLSOCKET) + IF_NAMESIZE]; - char control_sock_unpriv[sizeof(CONTROLSOCKET) + IF_NAMESIZE + 7]; gid_t control_group; /* DHCP Enterprise options, RFC3925 */ @@ -199,13 +197,12 @@ struct dhcpcd_ctx { struct ps_process *ps_root; struct ps_process *ps_inet; struct ps_process *ps_ctl; - int ps_data_fd; /* data returned from processes */ - int ps_log_fd; /* chroot logging */ - int ps_log_root_fd; /* outside chroot log reader */ - struct fd_list *ps_control; /* Queue for the above */ - struct fd_list *ps_control_client; /* Queue for the above */ - void *ps_buf; /* IPC buffer */ - size_t ps_buflen; /* IPC buffer length */ + int ps_data_fd; /* data returned from processes */ + int ps_log_fd; /* chroot logging */ + int ps_log_root_fd; /* outside chroot log reader */ + struct fd_list *ps_control; /* Queue for the above */ + void *ps_buf; /* IPC buffer */ + size_t ps_buflen; /* IPC buffer length */ #endif #ifdef INET diff --git a/src/privsep-control.c b/src/privsep-control.c index 3737d07d..8c2fa514 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -37,6 +37,9 @@ #include "logerr.h" #include "privsep.h" +#define PS_CTL_FD(ctx) (ctx)->ps_ctl->psp_fd +#define PS_CTL_FLAGS_PRIV ~(~0UL >> 1) + /* We expect to have open 2 privsep STREAM, 2 STREAM and 2 file STREAM fds */ static int @@ -93,12 +96,6 @@ ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len) 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; } @@ -106,41 +103,24 @@ 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; int err; - 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_PRIV: + fd_flags |= FD_PRIV; /* FALLTHROUGH */ 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_ctl->psp_work_fd, fd_flags); if (fd == NULL) return -1; - ctx->ps_control_client = fd; - err = control_recvdata(fd, iov->iov_base, iov->iov_len); - if (err == -1 || err == 0) { + err = control_recvmsg(fd, msg, psm->ps_datalen); + if (err == -1 || err == 0) control_free(fd); - ctx->ps_control_client = NULL; - } - break; - case PS_CTL_EOF: - ctx->ps_control_client = NULL; break; default: errno = ENOTSUP; @@ -162,29 +142,34 @@ ps_ctl_dodispatch(void *arg, unsigned short events) static void ps_ctl_recv(void *arg, unsigned short events) { - struct dhcpcd_ctx *ctx = arg; char buf[BUFSIZ]; + struct dhcpcd_ctx *ctx = arg; ssize_t len; + struct fd_list *fd; - if (!(events & (ELE_READ | ELE_HANGUP))) + if (events & ELE_HANGUP) { + hangup: + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return; + } + + if (!(events & ELE_READ)) { logerrx("%s: unexpected event 0x%04x", __func__, events); + return; + } - if (events & ELE_READ) { - len = read(ctx->ps_ctl->psp_work_fd, buf, sizeof(buf)); - if (len == -1) - logerr("%s: read", __func__); - else if (len == 0) - // FIXME: Why does this happen? - ; - else if (ctx->ps_control_client == NULL) - logerrx("%s: clientfd #%d disconnected (len=%zd)", - __func__, ctx->ps_ctl->psp_work_fd, len); - else { - errno = 0; - if (control_queue(ctx->ps_control_client, buf, - (size_t)len) == -1) - logerr("%s: control_queue", __func__); - } + len = read(ctx->ps_ctl->psp_work_fd, buf, sizeof(buf)); + if (len == -1) { + logerr("%s: read", __func__); + return; + } else if (len == 0) + goto hangup; + + 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__); } } @@ -196,6 +181,12 @@ ps_ctl_listen(void *arg, unsigned short events) ssize_t len; struct fd_list *fd; + if (events & ELE_HANGUP) { + hangup: + eloop_exit(ctx->eloop, EXIT_SUCCESS); + return; + } + if (!(events & ELE_READ)) logerrx("%s: unexpected event 0x%04x", __func__, events); @@ -205,10 +196,12 @@ ps_ctl_listen(void *arg, unsigned short events) eloop_exit(ctx->eloop, EXIT_FAILURE); return; } + if (len == 0) + goto hangup; /* Send to our listeners */ TAILQ_FOREACH(fd, &ctx->control_fds, next) { - if (!(fd->flags & FD_LISTEN)) + if (fd == ctx->ps_control || !(fd->flags & FD_LISTEN)) continue; if (control_queue(fd, buf, (size_t)len) == -1) logerr("%s: control_queue", __func__); @@ -263,7 +256,7 @@ ps_ctl_start(struct dhcpcd_ctx *ctx) ctx) == -1) return -1; - ctx->ps_control = control_new(ctx, listen_fd[1], 0); + ctx->ps_control = control_new(ctx, listen_fd[1], FD_LISTEN); if (ctx->ps_control == NULL) return -1; if (eloop_event_add(ctx->eloop, ctx->ps_control->fd, ELE_READ, @@ -281,21 +274,11 @@ ps_ctl_stop(struct dhcpcd_ctx *ctx) } 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_ctl->psp_fd, PS_CTL, - fd->flags & FD_UNPRIV ? PS_CTL_UNPRIV : PS_CTL_PRIV, data, len); -} - -ssize_t -ps_ctl_sendeof(struct fd_list *fd) +ps_ctl_sendmsg(struct fd_list *fd, const struct msghdr *msg) { struct dhcpcd_ctx *ctx = fd->ctx; + uint16_t cmd = fd->flags & FD_PRIV ? PS_CTL_PRIV : PS_CTL; + unsigned long flags = (unsigned long)fd->fd; - return ps_sendcmd(ctx, ctx->ps_ctl->psp_fd, PS_CTL_EOF, 0, NULL, 0); + return ps_sendmsg(ctx, PS_CTL_FD(ctx), cmd, flags, msg); } diff --git a/src/privsep-control.h b/src/privsep-control.h index ef2a76d6..08b75862 100644 --- a/src/privsep-control.h +++ b/src/privsep-control.h @@ -35,7 +35,7 @@ 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); +ssize_t ps_ctl_sendmsg(struct fd_list *, const struct msghdr *); +ssize_t ps_ctl_sendeof(struct dhcpcd_ctx *); #endif diff --git a/src/privsep-root.c b/src/privsep-root.c index 497ca893..755b3b43 100644 --- a/src/privsep-root.c +++ b/src/privsep-root.c @@ -320,6 +320,26 @@ ps_root_dowritefile(const struct dhcpcd_ctx *ctx, mode_t mode, void *data, return writefile(file, mode, nc, len - flen); } +static ssize_t +ps_root_douser_ispriv(struct dhcpcd_ctx *ctx, void *data, size_t len) +{ + uid_t uid; + gid_t gid; + uint8_t *p = data; + + if (len != sizeof(uid) + sizeof(gid)) { + errno = EINVAL; + return -1; + } + + p = data; + memcpy(&uid, p, sizeof(uid)); + p += sizeof(uid); + memcpy(&gid, p, sizeof(gid)); + + return control_user_ispriv(ctx, uid, gid); +} + #ifdef AUTH static ssize_t ps_root_monordm(uint64_t *rdm, size_t len) @@ -563,6 +583,9 @@ ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) case PS_LOGREOPEN: err = logopen(ctx->logfile); break; + case PS_USER_ISPRIV: + err = ps_root_douser_ispriv(ctx, data, len); + break; #ifdef AUTH case PS_AUTH_MONORDM: err = ps_root_monordm(data, len); @@ -1119,6 +1142,29 @@ ps_root_logreopen(struct dhcpcd_ctx *ctx) return ps_root_readerror(ctx, NULL, 0); } +ssize_t +ps_root_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid) +{ + struct iovec iov[] = { { + .iov_base = &uid, + .iov_len = sizeof(uid), + }, + { + .iov_base = &gid, + .iov_len = sizeof(gid), + } }; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = __arraycount(iov), + }; + + if (ps_sendmsg(ctx, PS_ROOT_FD(ctx), PS_USER_ISPRIV, 0, &msg) == -1) { + logerr(__func__); + return -1; + } + return ps_root_readerror(ctx, NULL, 0); +} + #ifdef PRIVSEP_GETHOSTNAME int ps_root_gethostname(struct dhcpcd_ctx *ctx, char *hname, size_t hnamelen) diff --git a/src/privsep-root.h b/src/privsep-root.h index 58fe8da8..af5fad95 100644 --- a/src/privsep-root.h +++ b/src/privsep-root.h @@ -53,6 +53,7 @@ ssize_t ps_root_logreopen(struct dhcpcd_ctx *); ssize_t ps_root_script(struct dhcpcd_ctx *, const void *, size_t); ssize_t ps_root_stopprocesses(struct dhcpcd_ctx *); int ps_root_getauthrdm(struct dhcpcd_ctx *, uint64_t *); +ssize_t ps_root_user_ispriv(struct dhcpcd_ctx *, uid_t, gid_t); #ifdef PRIVSEP_GETHOSTNAME int ps_root_gethostname(struct dhcpcd_ctx *, char *, size_t); #endif diff --git a/src/privsep.c b/src/privsep.c index a0806994..65955007 100644 --- a/src/privsep.c +++ b/src/privsep.c @@ -358,11 +358,13 @@ ps_startprocess(struct ps_process *psp, /* Close things we no longer need */ pidfile_unlock(); - eloop_closefdwaiter(ctx->eloop); + if (ctx->ps_ctl != psp) + eloop_closefdwaiter(ctx->eloop); /* Close more if we are not root */ if (ctx->ps_root != psp) { - ps_root_close(ctx); + if (ctx->ps_ctl != psp) + ps_root_close(ctx); #ifdef PLUGIN_DEV dev_stop(ctx); #endif @@ -391,7 +393,6 @@ ps_startprocess(struct ps_process *psp, if (ctx->ps_root != psp) { ctx->options &= ~DHCPCD_PRIVSEPROOT; - ctx->ps_root = NULL; if (ctx->ps_log_root_fd != -1) { /* Already removed from eloop thanks to above clear. */ close(ctx->ps_log_root_fd); @@ -1185,6 +1186,9 @@ ps_freeprocesses(struct dhcpcd_ctx *ctx, struct ps_process *notthis) TAILQ_FOREACH_SAFE(psp, &ctx->ps_processes, next, psn) { if (psp == notthis) continue; + /* control needs root access to work out user group */ + if (ctx->ps_ctl == notthis && psp == ctx->ps_root) + continue; ps_freeprocess(psp); } } diff --git a/src/privsep.h b/src/privsep.h index 8c6ec054..20e59170 100644 --- a/src/privsep.h +++ b/src/privsep.h @@ -52,10 +52,11 @@ #define PS_FILEMTIME 0x0016 #define PS_AUTH_MONORDM 0x0017 #define PS_CTL 0x0018 -#define PS_CTL_EOF 0x0019 +#define PS_CTL_PRIV 0x0019 #define PS_LOGREOPEN 0x0020 #define PS_STOPPROCS 0x0021 #define PS_DAEMONISED 0x0022 +#define PS_USER_ISPRIV 0x0023 /* Domains */ #define PS_ROOT 0x0101 @@ -82,10 +83,6 @@ #define PS_DEV_IFREMOVED 0x0002 #define PS_DEV_IFUPDATED 0x0003 -/* Control Type (via flags) */ -#define PS_CTL_PRIV 0x0004 -#define PS_CTL_UNPRIV 0x0005 - /* Sysctl Needs (via flags) */ #define PS_SYSCTL_OLEN 0x0001 #define PS_SYSCTL_ODATA 0x0002 From 3554f56523dff642a90bb4cda21c18cbee7a519e Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Sun, 5 Jul 2026 16:01:13 +0100 Subject: [PATCH 02/17] Fix compile on Linux --- src/control.c | 31 ++++++++++++++++++++++++------- src/privsep-control.c | 1 - src/privsep-root.c | 2 +- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/control.c b/src/control.c index 09aebc04..960ac4c3 100644 --- a/src/control.c +++ b/src/control.c @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -54,6 +55,10 @@ (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) #endif +#define SUN_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) + +#define LISTEN_BACKLOG 5 + static void control_handle_data(void *, unsigned short); static void @@ -94,6 +99,22 @@ control_hangup(struct fd_list *fd) control_free(fd); } +#ifdef SO_PEERCRED +static int +getpeereid(int fd, uid_t *uid, gid_t *gid) +{ + struct ucred creds; + socklen_t creds_len = sizeof(creds); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &creds, &creds_len) == -1) + return -1; + + *uid = creds.uid; + *gid = creds.gid; + return 0; +} +#endif + static int control_handle_read(struct fd_list *fd) { @@ -431,8 +452,6 @@ make_sock(struct sockaddr_un *sa, const char *ifname, sa_family_t family) return fd; } -#define SOCK_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) - static int control_start1(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family) { @@ -447,10 +466,8 @@ control_start1(struct dhcpcd_ctx *ctx, const char *ifname, sa_family_t family) len = (socklen_t)SUN_LEN(&sa); unlink(sa.sun_path); if (bind(fd, (struct sockaddr *)&sa, len) == -1 || - chmod(sa.sun_path, 0666) == -1 || - (ctx->control_group && - chown(sa.sun_path, geteuid(), ctx->control_group) == -1) || - listen(fd, sizeof(ctx->control_fds)) == -1) + chmod(sa.sun_path, SUN_MODE) == -1 || + listen(fd, LISTEN_BACKLOG) == -1) goto err; #ifdef PRIVSEP_RIGHTS @@ -647,7 +664,7 @@ control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid) pw = getpwuid(uid); if (pw == NULL) - return -1; + return 0; /* unknown user is not privileged */ groups = reallocarray(groups, (size_t)ngroups, sizeof(*groups)); if (groups == NULL) diff --git a/src/privsep-control.c b/src/privsep-control.c index 8c2fa514..3ace3ad3 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -38,7 +38,6 @@ #include "privsep.h" #define PS_CTL_FD(ctx) (ctx)->ps_ctl->psp_fd -#define PS_CTL_FLAGS_PRIV ~(~0UL >> 1) /* We expect to have open 2 privsep STREAM, 2 STREAM and 2 file STREAM fds */ diff --git a/src/privsep-root.c b/src/privsep-root.c index 755b3b43..dec46b0d 100644 --- a/src/privsep-root.c +++ b/src/privsep-root.c @@ -325,7 +325,7 @@ ps_root_douser_ispriv(struct dhcpcd_ctx *ctx, void *data, size_t len) { uid_t uid; gid_t gid; - uint8_t *p = data; + uint8_t *p; if (len != sizeof(uid) + sizeof(gid)) { errno = EINVAL; From 86997131e5b596a0e73d5cee09881f8536751d38 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Sun, 5 Jul 2026 16:05:25 +0100 Subject: [PATCH 03/17] Add compat for Ilumos --- src/control.c | 20 +++++++++++++++++++- src/privsep-control.c | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/control.c b/src/control.c index 960ac4c3..8afc4f62 100644 --- a/src/control.c +++ b/src/control.c @@ -41,6 +41,10 @@ #include #include +#if defined(__sun) +#include +#endif + #include "common.h" #include "config.h" #include "control.h" @@ -55,7 +59,7 @@ (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) #endif -#define SUN_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) +#define SUN_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) #define LISTEN_BACKLOG 5 @@ -113,6 +117,20 @@ getpeereid(int fd, uid_t *uid, gid_t *gid) *gid = creds.gid; return 0; } +#elif defined(__sun) +static int +getpeereid(int fd, uid_t *uid, gid_t *gid) +{ + ucred_t *ucred = NULL; + + if (getpeerucred(fd, &ucred) == -1) + return -1; + + *uid = ucred_geteuid(ucred); + *gid_t gid = ucred_getegid(ucred); + ucred_free(ucred); + return 0; +} #endif static int diff --git a/src/privsep-control.c b/src/privsep-control.c index 3ace3ad3..cb27de1b 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -37,7 +37,7 @@ #include "logerr.h" #include "privsep.h" -#define PS_CTL_FD(ctx) (ctx)->ps_ctl->psp_fd +#define PS_CTL_FD(ctx) (ctx)->ps_ctl->psp_fd /* We expect to have open 2 privsep STREAM, 2 STREAM and 2 file STREAM fds */ From 90b239e1e847b71e5fca84d805edbcebcfa4a3ac Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Tue, 14 Jul 2026 15:01:47 +0100 Subject: [PATCH 04/17] Fix the control socket for dhcpcd-ui Add --isprivileged to return either true or false so the caller can work this out as we now just have the one socket. Bump version for this. --- src/control.c | 38 +++++++++------- src/control.h | 7 +-- src/defs.h | 2 +- src/dhcpcd.c | 43 ++++++++++-------- src/privsep-control.c | 100 +++++++++++++++++++++++++++++------------- 5 files changed, 120 insertions(+), 70 deletions(-) diff --git a/src/control.c b/src/control.c index 8afc4f62..473d6f37 100644 --- a/src/control.c +++ b/src/control.c @@ -133,7 +133,7 @@ getpeereid(int fd, uid_t *uid, gid_t *gid) } #endif -static int +static ssize_t control_handle_read(struct fd_list *fd) { uid_t uid; @@ -153,7 +153,7 @@ control_handle_read(struct fd_list *fd) if (bytes == -1) logerr(__func__); if (bytes == -1 || bytes == 0) - return (int)bytes; + return bytes; if (getpeereid(fd->fd, &uid, &gid) == -1) { logerr("%s: getpeereid", __func__); @@ -177,12 +177,14 @@ control_handle_read(struct fd_list *fd) fd->flags |= FD_SENDLEN; perr = ps_ctl_handleargs(fd, buf, (size_t)bytes); fd->flags &= ~FD_SENDLEN; + if (!(fd->flags & FD_LISTEN)) + fd->flags |= FD_COMMAND; if (perr == -1) { logerr(__func__); return 0; } iov[0].iov_len = (size_t)bytes; - if (perr == 1 && ps_ctl_sendmsg(fd, &msg) == -1) { + if (perr == 0 && ps_ctl_sendmsg(fd, &msg) == -1) { logerr(__func__); return -1; } @@ -202,28 +204,30 @@ control_handle_read(struct fd_list *fd) return control_recvmsg(fd, &msg, (size_t)bytes); } -static int +static ssize_t control_handle_write(struct fd_list *fd) { struct iovec iov[2]; - int iov_len; + struct msghdr msg = { .msg_iov = iov }; struct fd_data *data; + ssize_t len; data = TAILQ_FIRST(&fd->queue); if (data->data_flags & FD_SENDLEN) { iov[0].iov_base = &data->data_len; - iov[0].iov_len = sizeof(size_t); + iov[0].iov_len = sizeof(data->data_len); iov[1].iov_base = data->data; iov[1].iov_len = data->data_len; - iov_len = 2; + msg.msg_iovlen = 2; } else { iov[0].iov_base = data->data; iov[0].iov_len = data->data_len; - iov_len = 1; + msg.msg_iovlen = 1; } - if (writev(fd->fd, iov, iov_len) == -1) { + len = sendmsg(fd->fd, &msg, 0); + if (len == -1) { if (errno != EPIPE && errno != ENOTCONN) { // We don't get ELE_HANGUP for some reason logerr("%s: write", __func__); @@ -242,20 +246,23 @@ control_handle_write(struct fd_list *fd) #endif if (TAILQ_FIRST(&fd->queue) != NULL) - return 0; + return len; /* Done sending data, stop watching write to fd */ if (eloop_event_add(fd->ctx->eloop, fd->fd, ELE_READ, control_handle_data, fd) == -1) logerr("%s: eloop_event_add", __func__); - return 0; + return len; } static void control_handle_data(void *arg, unsigned short events) { struct fd_list *fd = arg; - int err; + ssize_t err; + + if (events & ELE_HANGUP) + goto hangup; if (!(events & (ELE_READ | ELE_WRITE | ELE_HANGUP))) logerrx("%s: unexpected event 0x%04x", __func__, events); @@ -270,8 +277,6 @@ control_handle_data(void *arg, unsigned short events) if ((err == -1 && errno != EPERM) || err == 0) goto hangup; } - if (events & ELE_HANGUP) - goto hangup; return; @@ -339,6 +344,8 @@ control_recvmsg(struct fd_list *fd, struct msghdr *msg, size_t len) if (errno != EINTR && errno != EAGAIN && errno != EPERM) return -1; } + if (!(fd->flags & FD_LISTEN)) + fd->flags |= FD_COMMAND; } return 1; @@ -412,7 +419,6 @@ control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags, else #endif fd_flags |= FD_SENDLEN; - l = control_new(ctx, fd, fd_flags); if (l == NULL) goto error; @@ -618,7 +624,7 @@ control_send(struct dhcpcd_ctx *ctx, int argc, char *const *argv) } int -control_queue(struct fd_list *fd, void *data, size_t data_len) +control_queue(struct fd_list *fd, const void *data, size_t data_len) { struct fd_data *d; unsigned short events; diff --git a/src/control.h b/src/control.h index 85086025..fd8d64b7 100644 --- a/src/control.h +++ b/src/control.h @@ -66,8 +66,9 @@ struct fd_list { TAILQ_HEAD(fd_list_head, fd_list); #define FD_LISTEN 0x01U -#define FD_PRIV 0x02U -#define FD_SENDLEN 0x04U +#define FD_COMMAND 0x02U +#define FD_PRIV 0x04U +#define FD_SENDLEN 0x08U int control_start(struct dhcpcd_ctx *, const char *, sa_family_t); int control_stop(struct dhcpcd_ctx *); @@ -77,7 +78,7 @@ struct fd_list *control_find(struct dhcpcd_ctx *, int); 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); +int control_queue(struct fd_list *, const void *, size_t); int control_recvmsg(struct fd_list *, struct msghdr *, size_t); int control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid); #endif diff --git a/src/defs.h b/src/defs.h index d21a8fae..61f80579 100644 --- a/src/defs.h +++ b/src/defs.h @@ -29,7 +29,7 @@ #define DEFS_H #define PACKAGE "dhcpcd" -#define VERSION "10.3.2" +#define VERSION "10.5.0" #ifndef PRIVSEP_USER #define PRIVSEP_USER "_" PACKAGE diff --git a/src/dhcpcd.c b/src/dhcpcd.c index f45c4f38..d3b2bbea 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -1587,24 +1587,6 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, 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); - } else if (strcmp(*argv, "--getconfigfile") == 0) { - return control_queue(fd, UNCONST(fd->ctx->cffile), - strlen(fd->ctx->cffile) + 1); - } else if (strcmp(*argv, "--getinterfaces") == 0) { - oifind = argc = 0; - goto dumplease; - } else if (strcmp(*argv, "--listen") == 0) { - fd->flags |= FD_LISTEN; - return 0; - } - /* Log the command */ len = 1; for (opt = 0; opt < argc; opt++) @@ -1624,6 +1606,27 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, loginfox("control command: %s", tmp); free(tmp); + /* 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, VERSION, strlen(VERSION) + 1); + } else if (strcmp(*argv, "--getconfigfile") == 0) { + return control_queue(fd, fd->ctx->cffile, + strlen(fd->ctx->cffile) + 1); + } else if (strcmp(*argv, "--getinterfaces") == 0) { + oifind = argc = 0; + goto dumplease; + } else if (strcmp(*argv, "--isprivileged") == 0) { + const char *ret = fd->flags & FD_PRIV ? "true" : "false"; + return control_queue(fd, ret, strlen(ret) + 1); + } else if (strcmp(*argv, "--listen") == 0) { + fd->flags |= FD_LISTEN; + return 0; + } + optind = 0; oi = 0; opts = 0; @@ -1682,8 +1685,10 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, nifaces += (size_t)opt; } } - if (write(fd->fd, &nifaces, sizeof(nifaces)) != sizeof(nifaces)) + fd->flags &= ~FD_SENDLEN; + if (control_queue(fd, &nifaces, sizeof(nifaces)) == -1) goto dumperr; + fd->flags |= FD_SENDLEN; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (!ifp->active) continue; diff --git a/src/privsep-control.c b/src/privsep-control.c index cb27de1b..468270b8 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -83,19 +83,26 @@ ps_ctl_recvmsg(void *arg, unsigned short events) ssize_t ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len) { + int err; + /* 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); + err = control_queue(fd, UNCONST(VERSION), strlen(VERSION) + 1); + return err == -1 ? -1 : 1; } else if (strncmp(data, "--getconfigfile", MIN(strlen("--getconfigfile"), len)) == 0) { - return control_queue(fd, UNCONST(fd->ctx->cffile), + err = control_queue(fd, UNCONST(fd->ctx->cffile), strlen(fd->ctx->cffile) + 1); + return err == -1 ? -1 : 1; + } else if (strcmp(data, "--isprivileged") == 0) { + const char *ret = fd->flags & FD_PRIV ? "true" : "false"; + return control_queue(fd, ret, strlen(ret) + 1); } else if (strncmp(data, "--listen", MIN(strlen("--listen"), len)) == 0) { fd->flags |= FD_LISTEN; - return 0; + return 1; } - return 1; + return 0; } static ssize_t @@ -141,10 +148,19 @@ ps_ctl_dodispatch(void *arg, unsigned short events) static void ps_ctl_recv(void *arg, unsigned short events) { - char buf[BUFSIZ]; struct dhcpcd_ctx *ctx = arg; - ssize_t len; - struct fd_list *fd; + char buf[BUFSIZ]; + struct iovec iov[] = { { + .iov_base = buf, + .iov_len = sizeof(buf), + } }; + struct msghdr msg = { + .msg_iov = iov, + .msg_iovlen = __arraycount(iov), + }; + ssize_t msglen; + int fd; + struct fd_list *fdl; if (events & ELE_HANGUP) { hangup: @@ -152,22 +168,23 @@ ps_ctl_recv(void *arg, unsigned short events) return; } - if (!(events & ELE_READ)) { + if (!(events & ELE_READ)) logerrx("%s: unexpected event 0x%04x", __func__, events); - return; - } - len = read(ctx->ps_ctl->psp_work_fd, buf, sizeof(buf)); - if (len == -1) { - logerr("%s: read", __func__); - return; - } else if (len == 0) + fd = ctx->ps_ctl->psp_work_fd; + msglen = recvmsg(fd, &msg, 0); + if (msglen == 0) goto hangup; + if (msglen == -1) { + logerr("%s: read", __func__); + eloop_exit(ctx->eloop, EXIT_FAILURE); + } - TAILQ_FOREACH(fd, &ctx->control_fds, next) { - if (fd->flags & FD_LISTEN) + /* Send to our command controls */ + TAILQ_FOREACH(fdl, &ctx->control_fds, next) { + if (!(fdl->flags & FD_COMMAND)) continue; - if (control_queue(fd, buf, (size_t)len) == -1) + if (control_queue(fdl, buf, (size_t)msglen) == -1) logerr("%s: control_queue", __func__); } } @@ -176,9 +193,10 @@ static void ps_ctl_listen(void *arg, unsigned short events) { struct dhcpcd_ctx *ctx = arg; - char buf[BUFSIZ]; ssize_t len; - struct fd_list *fd; + size_t msglen; + int fd; + struct fd_list *fdl; if (events & ELE_HANGUP) { hangup: @@ -189,22 +207,40 @@ ps_ctl_listen(void *arg, unsigned short events) if (!(events & ELE_READ)) logerrx("%s: unexpected event 0x%04x", __func__, events); - len = read(ctx->ps_control->fd, buf, sizeof(buf)); - if (len == -1) { - logerr("%s: read", __func__); - eloop_exit(ctx->eloop, EXIT_FAILURE); - return; + fd = ctx->ps_control->fd; + len = read(fd, &msglen, sizeof(msglen)); + if (len == 0) + goto hangup; + if (len != sizeof(msglen)) { + logerr("%s: read len", __func__); + goto err; } + + if (ps_bufalloc(ctx, msglen) == -1) { + logerr("%s: realloc", __func__); + goto err; + } + + len = read(fd, ctx->ps_buf, msglen); if (len == 0) goto hangup; + if ((size_t)len != msglen) { + logerr("%s: read", __func__); + goto err; + } /* Send to our listeners */ - TAILQ_FOREACH(fd, &ctx->control_fds, next) { - if (fd == ctx->ps_control || !(fd->flags & FD_LISTEN)) + TAILQ_FOREACH(fdl, &ctx->control_fds, next) { + if (!(fdl->flags & FD_LISTEN)) continue; - if (control_queue(fd, buf, (size_t)len) == -1) + if (control_queue(fdl, ctx->ps_buf, msglen) == -1) logerr("%s: control_queue", __func__); } + + return; + +err: + eloop_exit(ctx->eloop, EXIT_FAILURE); } pid_t @@ -220,8 +256,10 @@ ps_ctl_start(struct dhcpcd_ctx *ctx) if_closesockets(ctx); - if (xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, work_fd) == -1 || - xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CXNB, 0, listen_fd) == -1) + if (xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, work_fd) == + -1 || + xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, listen_fd) == + -1) return -1; #ifdef PRIVSEP_RIGHTS if (ps_rights_limit_fdpair(work_fd) == -1 || @@ -255,7 +293,7 @@ ps_ctl_start(struct dhcpcd_ctx *ctx) ctx) == -1) return -1; - ctx->ps_control = control_new(ctx, listen_fd[1], FD_LISTEN); + ctx->ps_control = control_new(ctx, listen_fd[1], 0); if (ctx->ps_control == NULL) return -1; if (eloop_event_add(ctx->eloop, ctx->ps_control->fd, ELE_READ, From 56e36fb5107f8972296dba6261a1a4de4924194b Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Tue, 14 Jul 2026 15:25:08 +0100 Subject: [PATCH 05/17] Bound the data length --- src/privsep-control.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/privsep-control.c b/src/privsep-control.c index 468270b8..4df347e8 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -94,7 +94,8 @@ ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len) err = control_queue(fd, UNCONST(fd->ctx->cffile), strlen(fd->ctx->cffile) + 1); return err == -1 ? -1 : 1; - } else if (strcmp(data, "--isprivileged") == 0) { + } else if (strncmp(data, "--isprivileged", + MIN(strlen("--isprivileged"), len)) == 0) { const char *ret = fd->flags & FD_PRIV ? "true" : "false"; return control_queue(fd, ret, strlen(ret) + 1); } else if (strncmp(data, "--listen", MIN(strlen("--listen"), len)) == From e9496930e404eecd2e30adada9c59770d1ef9c4f Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Tue, 14 Jul 2026 15:35:33 +0100 Subject: [PATCH 06/17] recvmsg WAITALL --- src/dhcpcd.c | 38 +++++++++++++++++++------------------- src/privsep-control.c | 18 +++++++++++++----- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/dhcpcd.c b/src/dhcpcd.c index d3b2bbea..fb9a45c8 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -1587,25 +1587,6 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, size_t len, l, nifaces; char *tmp, *p; - /* Log the command */ - len = 1; - for (opt = 0; opt < argc; opt++) - len += strlen(argv[opt]) + 1; - tmp = malloc(len); - if (tmp == NULL) - return -1; - p = tmp; - for (opt = 0; opt < argc; opt++) { - l = strlen(argv[opt]); - strlcpy(p, argv[opt], len); - len -= l + 1; - p += l; - *p++ = ' '; - } - *--p = '\0'; - loginfox("control command: %s", tmp); - free(tmp); - /* 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 @@ -1627,6 +1608,25 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, return 0; } + /* Log the command */ + len = 1; + for (opt = 0; opt < argc; opt++) + len += strlen(argv[opt]) + 1; + tmp = malloc(len); + if (tmp == NULL) + return -1; + p = tmp; + for (opt = 0; opt < argc; opt++) { + l = strlen(argv[opt]); + strlcpy(p, argv[opt], len); + len -= l + 1; + p += l; + *p++ = ' '; + } + *--p = '\0'; + loginfox("control command: %s", tmp); + free(tmp); + optind = 0; oi = 0; opts = 0; diff --git a/src/privsep-control.c b/src/privsep-control.c index 4df347e8..d143bdbf 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -177,7 +177,7 @@ ps_ctl_recv(void *arg, unsigned short events) if (msglen == 0) goto hangup; if (msglen == -1) { - logerr("%s: read", __func__); + logerr("%s: recvmsg", __func__); eloop_exit(ctx->eloop, EXIT_FAILURE); } @@ -196,6 +196,12 @@ ps_ctl_listen(void *arg, unsigned short events) struct dhcpcd_ctx *ctx = arg; ssize_t len; size_t msglen; + struct iovec iov[] = { + { .iov_base = &msglen, .iov_len = sizeof(msglen), } + }; + struct msghdr msg = { + .msg_iov = iov, .msg_iovlen = __arraycount(iov), + }; int fd; struct fd_list *fdl; @@ -209,11 +215,11 @@ ps_ctl_listen(void *arg, unsigned short events) logerrx("%s: unexpected event 0x%04x", __func__, events); fd = ctx->ps_control->fd; - len = read(fd, &msglen, sizeof(msglen)); + len = recvmsg(fd, &msg, MSG_WAITALL); if (len == 0) goto hangup; if (len != sizeof(msglen)) { - logerr("%s: read len", __func__); + logerr("%s: recvmsg len %zd", __func__, len); goto err; } @@ -222,11 +228,13 @@ ps_ctl_listen(void *arg, unsigned short events) goto err; } - len = read(fd, ctx->ps_buf, msglen); + iov->iov_base = ctx->ps_buf; + iov->iov_len = msglen; + len = recvmsg(fd, &msg, MSG_WAITALL); if (len == 0) goto hangup; if ((size_t)len != msglen) { - logerr("%s: read", __func__); + logerr("%s: recvmsg", __func__); goto err; } From f8366075687be847d36749dfac27e0447c66ebd7 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Tue, 14 Jul 2026 15:37:19 +0100 Subject: [PATCH 07/17] Format --- src/privsep-control.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/privsep-control.c b/src/privsep-control.c index d143bdbf..68c55c87 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -196,11 +196,13 @@ ps_ctl_listen(void *arg, unsigned short events) struct dhcpcd_ctx *ctx = arg; ssize_t len; size_t msglen; - struct iovec iov[] = { - { .iov_base = &msglen, .iov_len = sizeof(msglen), } - }; + struct iovec iov[] = { { + .iov_base = &msglen, + .iov_len = sizeof(msglen), + } }; struct msghdr msg = { - .msg_iov = iov, .msg_iovlen = __arraycount(iov), + .msg_iov = iov, + .msg_iovlen = __arraycount(iov), }; int fd; struct fd_list *fdl; From ec2065bc86a1b0aa3c976d3288caa6f5f8461c83 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Wed, 15 Jul 2026 00:02:51 +0100 Subject: [PATCH 08/17] Fix compile on IllumOS --- src/control.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/control.c b/src/control.c index 473d6f37..547b4bef 100644 --- a/src/control.c +++ b/src/control.c @@ -127,7 +127,7 @@ getpeereid(int fd, uid_t *uid, gid_t *gid) return -1; *uid = ucred_geteuid(ucred); - *gid_t gid = ucred_getegid(ucred); + *gid = ucred_getegid(ucred); ucred_free(ucred); return 0; } From 77331decbef31b4147da667965d429132569a64f Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Wed, 15 Jul 2026 14:47:08 +0100 Subject: [PATCH 09/17] compat: add getpeereid.c and move compat out of control.c --- compat/getpeereid.c | 66 +++++++++++++++++++++++++++++++++++++++++++++ compat/getpeereid.h | 36 +++++++++++++++++++++++++ configure | 22 +++++++++++++++ src/control.c | 34 ----------------------- 4 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 compat/getpeereid.c create mode 100644 compat/getpeereid.h diff --git a/compat/getpeereid.c b/compat/getpeereid.c new file mode 100644 index 00000000..8f38b7b2 --- /dev/null +++ b/compat/getpeereid.c @@ -0,0 +1,66 @@ +/* + * compat: getpeereid + * SPDX-License-Identifier: BSD-2-Clause + * Copyright (c) 2006-2026 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 + +#ifdef __sun +#include +#endif + +int +getpeereid(int fd, uid_t *uid, gid_t *gid) +{ +#if defined(SO_PEERCRED) +#if defined(__OpenBSD__) + struct sockpeercred creds; +#else + struct ucred creds; +#endif + socklen_t creds_len = sizeof(creds); + + if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &creds, &creds_len) == -1) + return -1; + + *uid = creds.uid; + *gid = creds.gid; +#elif defined(__sun) + ucred_t *ucred = NULL; + + if (getpeerucred(fd, &ucred) == -1) + return -1; + + *uid = ucred_geteuid(ucred); + *gid = ucred_getegid(ucred); + ucred_free(ucred); +#else +#error OS has no getpeereid support! +#endif + + return 0; +} diff --git a/compat/getpeereid.h b/compat/getpeereid.h new file mode 100644 index 00000000..c8949dc6 --- /dev/null +++ b/compat/getpeereid.h @@ -0,0 +1,36 @@ +/* + * compat: getpeereid + * SPDX-License-Identifier: BSD-2-Clause + * Copyright (c) 2006-2026 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 GETPEEREID_H +#define GETPEEREID_H + +#include + +int getpeereid(int, uid_t *, gid_t *); + +#endif diff --git a/configure b/configure index b246fd3e..3c56eda7 100755 --- a/configure +++ b/configure @@ -938,6 +938,28 @@ if [ "$ARC4RANDOM_UNIFORM" = no ]; then echo "#include \"compat/arc4random_uniform.h\"" >>$CONFIG_H fi +if [ -z "$GETPEEREID" ]; then + printf "Testing for getpeereid ... " + cat <_getpeereid.c +#include +#include +int main(void) { + return getpeereid(0, NULL, NULL); +} +EOF + if $XCC _getpeereid.c -o _getpeereid 2>&3; then + GETPEEREID=yes + else + GETPEEREID=no + fi + echo "$GETPEEREID" + rm -rf _getpeereid.* _getpeereid +fi +if [ "$GETPEEREID" = no ]; then + echo "COMPAT_SRCS+= compat/getpeereid.c" >>$CONFIG_MK + echo "#include \"compat/getpeereid.h\"" >>$CONFIG_H +fi + if [ -z "$MEMSET_EXPLICIT" ]; then printf "Testing for memset_explicit ... " cat <_memset_explicit.c diff --git a/src/control.c b/src/control.c index 547b4bef..d4ff3a71 100644 --- a/src/control.c +++ b/src/control.c @@ -41,10 +41,6 @@ #include #include -#if defined(__sun) -#include -#endif - #include "common.h" #include "config.h" #include "control.h" @@ -103,36 +99,6 @@ control_hangup(struct fd_list *fd) control_free(fd); } -#ifdef SO_PEERCRED -static int -getpeereid(int fd, uid_t *uid, gid_t *gid) -{ - struct ucred creds; - socklen_t creds_len = sizeof(creds); - - if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &creds, &creds_len) == -1) - return -1; - - *uid = creds.uid; - *gid = creds.gid; - return 0; -} -#elif defined(__sun) -static int -getpeereid(int fd, uid_t *uid, gid_t *gid) -{ - ucred_t *ucred = NULL; - - if (getpeerucred(fd, &ucred) == -1) - return -1; - - *uid = ucred_geteuid(ucred); - *gid = ucred_getegid(ucred); - ucred_free(ucred); - return 0; -} -#endif - static ssize_t control_handle_read(struct fd_list *fd) { From e57871189e1804d4e0fec362f3a54c31455e91e1 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Wed, 15 Jul 2026 14:56:32 +0100 Subject: [PATCH 10/17] Fix compile --- compat/getpeereid.c | 4 ---- configure | 1 + 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/compat/getpeereid.c b/compat/getpeereid.c index 8f38b7b2..dc2bfa02 100644 --- a/compat/getpeereid.c +++ b/compat/getpeereid.c @@ -37,11 +37,7 @@ int getpeereid(int fd, uid_t *uid, gid_t *gid) { #if defined(SO_PEERCRED) -#if defined(__OpenBSD__) - struct sockpeercred creds; -#else struct ucred creds; -#endif socklen_t creds_len = sizeof(creds); if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &creds, &creds_len) == -1) diff --git a/configure b/configure index 3c56eda7..32bf96ef 100755 --- a/configure +++ b/configure @@ -943,6 +943,7 @@ if [ -z "$GETPEEREID" ]; then cat <_getpeereid.c #include #include +#include int main(void) { return getpeereid(0, NULL, NULL); } From 71f802862b840cae708361b3088ede171c3ad7f2 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Wed, 15 Jul 2026 15:00:01 +0100 Subject: [PATCH 11/17] Fix compat --- compat/getpeereid.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compat/getpeereid.c b/compat/getpeereid.c index dc2bfa02..404f8ab9 100644 --- a/compat/getpeereid.c +++ b/compat/getpeereid.c @@ -33,6 +33,8 @@ #include #endif +#include "getpeereid.h" + int getpeereid(int fd, uid_t *uid, gid_t *gid) { From 5f6020c53f9fd7eadbf48cba9af59a366f280504 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Wed, 15 Jul 2026 16:06:48 +0100 Subject: [PATCH 12/17] eloop: fix a compile issue on Dragonfly while here --- src/eloop.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/eloop.c b/src/eloop.c index 912742a5..3880388b 100644 --- a/src/eloop.c +++ b/src/eloop.c @@ -236,7 +236,6 @@ eloop_event_count(const struct eloop *eloop) static int eloop_signal_kqueue(struct eloop *eloop, const int *signals, size_t nsignals) { - unsigned int cmd = nsignals == 0 ? EV_DELETE : EV_ADD; struct kevent *ke, *kep; size_t i; int err; @@ -253,8 +252,8 @@ eloop_signal_kqueue(struct eloop *eloop, const int *signals, size_t nsignals) return -1; for (i = 0; i < nsignals; i++) - EV_SET(kep++, (uintptr_t)signals[i], EVFILT_SIGNAL, cmd, 0, 0, - NULL); + EV_SET(kep++, (uintptr_t)signals[i], EVFILT_SIGNAL, + nsignals == 0 ? EV_DELETE : EV_ADD, 0, 0, NULL); err = kevent(eloop->fd, ke, (KEVENT_N)nsignals, NULL, 0, NULL); free(ke); From 422109a43d00bfe1a513822f18a448e5c2961d14 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Wed, 15 Jul 2026 16:25:13 +0100 Subject: [PATCH 13/17] Apple: remove compile warnings --- src/control.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/control.c b/src/control.c index d4ff3a71..233a7446 100644 --- a/src/control.c +++ b/src/control.c @@ -648,7 +648,14 @@ control_queue(struct fd_list *fd, const void *data, size_t data_len) int control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid) { +#ifdef __APPLE__ + /* why is getgrouplist API is different ..... */ + int *groups = NULL, *gp; +#define GID (int) +#else gid_t *groups = NULL, *gp; +#define GID +#endif struct passwd *pw; int ngroups = 10, err = -1; @@ -660,17 +667,17 @@ control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid) if (groups == NULL) return -1; - if (getgrouplist(pw->pw_name, gid, groups, &ngroups) == -1) { + if (getgrouplist(pw->pw_name, GID gid, groups, &ngroups) == -1) { gp = reallocarray(groups, (size_t)ngroups, sizeof(*groups)); if (gp == NULL) goto out; groups = gp; - if (getgrouplist(pw->pw_name, gid, groups, &ngroups) == -1) + if (getgrouplist(pw->pw_name, GID gid, groups, &ngroups) == -1) goto out; } for (gp = groups; ngroups != 0; ngroups--, gp++) { - if (*gp == ctx->control_group) { + if (*gp == GID ctx->control_group) { err = 1; goto out; } From aa61e1b58d9699e238e5094888f7160ee00e7cd9 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Wed, 15 Jul 2026 16:59:24 +0100 Subject: [PATCH 14/17] Fix eloop better --- src/eloop.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/eloop.c b/src/eloop.c index 3880388b..70090551 100644 --- a/src/eloop.c +++ b/src/eloop.c @@ -236,6 +236,7 @@ eloop_event_count(const struct eloop *eloop) static int eloop_signal_kqueue(struct eloop *eloop, const int *signals, size_t nsignals) { + unsigned short cmd = nsignals == 0 ? EV_DELETE : EV_ADD; struct kevent *ke, *kep; size_t i; int err; @@ -252,8 +253,8 @@ eloop_signal_kqueue(struct eloop *eloop, const int *signals, size_t nsignals) return -1; for (i = 0; i < nsignals; i++) - EV_SET(kep++, (uintptr_t)signals[i], EVFILT_SIGNAL, - nsignals == 0 ? EV_DELETE : EV_ADD, 0, 0, NULL); + EV_SET(kep++, (uintptr_t)signals[i], EVFILT_SIGNAL, cmd, 0, 0, + NULL); err = kevent(eloop->fd, ke, (KEVENT_N)nsignals, NULL, 0, NULL); free(ke); From 7eb4356f8eb5fc5e69691dfd4c7ceddd48185856 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Jul 2026 09:54:21 +0100 Subject: [PATCH 15/17] Improve --- src/control.c | 24 +++++++++++++----------- src/control.h | 2 +- src/privsep-control.c | 8 ++------ 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/control.c b/src/control.c index 233a7446..c9c041d6 100644 --- a/src/control.c +++ b/src/control.c @@ -128,29 +128,29 @@ control_handle_read(struct fd_list *fd) #ifdef PRIVSEP if (IN_PRIVSEP(fd->ctx)) { - ssize_t perr; - - perr = ps_root_user_ispriv(fd->ctx, uid, gid); - if (perr == -1) { + err = ps_root_user_ispriv(fd->ctx, uid, gid); + if (err == -1) { logerr(__func__); return -1; } - if (perr == 0) + if (err == 0) fd->flags &= ~FD_PRIV; else fd->flags |= FD_PRIV; fd->flags |= FD_SENDLEN; - perr = ps_ctl_handleargs(fd, buf, (size_t)bytes); + err = ps_ctl_handleargs(fd, buf, (size_t)bytes); fd->flags &= ~FD_SENDLEN; if (!(fd->flags & FD_LISTEN)) fd->flags |= FD_COMMAND; - if (perr == -1) { + if (err == -1) { logerr(__func__); return 0; } iov[0].iov_len = (size_t)bytes; - if (perr == 0 && ps_ctl_sendmsg(fd, &msg) == -1) { + /* If ps_ctl_handleargs returns 0 that means it didn't + * do anything with the command, so pass it to the manager. */ + if (err == 0 && ps_ctl_sendmsg(fd, &msg) == -1) { logerr(__func__); return -1; } @@ -589,7 +589,7 @@ control_send(struct dhcpcd_ctx *ctx, int argc, char *const *argv) return write(ctx->control_fd, buffer, len); } -int +ssize_t control_queue(struct fd_list *fd, const void *data, size_t data_len) { struct fd_data *d; @@ -641,8 +641,10 @@ control_queue(struct fd_list *fd, const void *data, size_t data_len) events = ELE_WRITE; if (fd->flags & FD_LISTEN) events |= ELE_READ; - return eloop_event_add(fd->ctx->eloop, fd->fd, events, - control_handle_data, fd); + if (eloop_event_add(fd->ctx->eloop, fd->fd, events, + control_handle_data, fd) == -1) + return -1; + return (ssize_t)d->data_len; } int diff --git a/src/control.h b/src/control.h index fd8d64b7..c1159e8e 100644 --- a/src/control.h +++ b/src/control.h @@ -78,7 +78,7 @@ struct fd_list *control_find(struct dhcpcd_ctx *, int); 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 *, const void *, size_t); +ssize_t control_queue(struct fd_list *, const void *, size_t); int control_recvmsg(struct fd_list *, struct msghdr *, size_t); int control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid); #endif diff --git a/src/privsep-control.c b/src/privsep-control.c index 68c55c87..8054b499 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -83,17 +83,13 @@ ps_ctl_recvmsg(void *arg, unsigned short events) ssize_t ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len) { - int err; - /* Make any change here in dhcpcd.c as well. */ if (strncmp(data, "--version", MIN(strlen("--version"), len)) == 0) { - err = control_queue(fd, UNCONST(VERSION), strlen(VERSION) + 1); - return err == -1 ? -1 : 1; + return control_queue(fd, VERSION, strlen(VERSION) + 1); } else if (strncmp(data, "--getconfigfile", MIN(strlen("--getconfigfile"), len)) == 0) { - err = control_queue(fd, UNCONST(fd->ctx->cffile), + return control_queue(fd, fd->ctx->cffile, strlen(fd->ctx->cffile) + 1); - return err == -1 ? -1 : 1; } else if (strncmp(data, "--isprivileged", MIN(strlen("--isprivileged"), len)) == 0) { const char *ret = fd->flags & FD_PRIV ? "true" : "false"; From 9bcaddc55a9bab672cfa9f0c9a4fd550fac312ed Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Thu, 16 Jul 2026 10:45:01 +0100 Subject: [PATCH 16/17] Improve --- src/privsep-control.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/privsep-control.c b/src/privsep-control.c index 8054b499..7a6f7cd6 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -81,21 +81,19 @@ ps_ctl_recvmsg(void *arg, unsigned short events) } ssize_t -ps_ctl_handleargs(struct fd_list *fd, char *data, size_t len) +ps_ctl_handleargs(struct fd_list *fd, char *data, __unused size_t len) { - /* Make any change here in dhcpcd.c as well. */ - if (strncmp(data, "--version", MIN(strlen("--version"), len)) == 0) { + /* Make any change here in dhcpcd.c as well. + * --version is NOT terminated with \n. */ + if (strcmp(data, "--version") == 0) { return control_queue(fd, VERSION, strlen(VERSION) + 1); - } else if (strncmp(data, "--getconfigfile", - MIN(strlen("--getconfigfile"), len)) == 0) { + } else if (strcmp(data, "--getconfigfile\n") == 0) { return control_queue(fd, fd->ctx->cffile, strlen(fd->ctx->cffile) + 1); - } else if (strncmp(data, "--isprivileged", - MIN(strlen("--isprivileged"), len)) == 0) { + } else if (strcmp(data, "--isprivileged\n") == 0) { const char *ret = fd->flags & FD_PRIV ? "true" : "false"; return control_queue(fd, ret, strlen(ret) + 1); - } else if (strncmp(data, "--listen", MIN(strlen("--listen"), len)) == - 0) { + } else if (strcmp(data, "--listen\n") == 0) { fd->flags |= FD_LISTEN; return 1; } From 3b36cd9753adf746bb841de728e7419fe32ba6f6 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Wed, 29 Jul 2026 19:13:02 +0100 Subject: [PATCH 17/17] Add readgroup, to so we can deny reading of dhcpcd interface data to everyone on the system. Always send an error back when controlling dhcpcd. --- src/control.c | 103 ++++++++++++++++++++++---------- src/control.h | 11 ++-- src/dhcpcd.c | 132 ++++++++++++++++++++++++++++++------------ src/dhcpcd.conf.5.in | 10 ++-- src/dhcpcd.h | 3 +- src/if-options.c | 26 +++++++-- src/if-options.h | 1 + src/privsep-control.c | 33 ++++++----- src/privsep-control.h | 2 +- src/privsep-root.c | 35 ++++++----- src/privsep-root.h | 2 +- src/privsep.h | 11 ++-- src/script.c | 2 +- 13 files changed, 256 insertions(+), 115 deletions(-) diff --git a/src/control.c b/src/control.c index c9c041d6..9526cc15 100644 --- a/src/control.c +++ b/src/control.c @@ -103,7 +103,7 @@ static ssize_t control_handle_read(struct fd_list *fd) { uid_t uid; - gid_t gid; + gid_t gid, in_gid; char buf[BUFSIZ]; struct iovec iov[] = { { .iov_base = buf, @@ -128,25 +128,41 @@ control_handle_read(struct fd_list *fd) #ifdef PRIVSEP if (IN_PRIVSEP(fd->ctx)) { - err = ps_root_user_ispriv(fd->ctx, uid, gid); + in_gid = fd->ctx->control_group; + if (uid == 0) + err = 1; + else + err = ps_root_user_ingroup(fd->ctx, uid, gid, in_gid); if (err == -1) { logerr(__func__); return -1; } - if (err == 0) - fd->flags &= ~FD_PRIV; - else - fd->flags |= FD_PRIV; + if (err == 0) { + fd->flags &= ~FD_CONTROL; + in_gid = fd->ctx->read_group; + err = ps_root_user_ingroup(fd->ctx, uid, gid, in_gid); + if (err == -1) { + logerr(__func__); + return -1; + } + if (err == 0) + fd->flags &= ~FD_READ; + else + fd->flags |= FD_READ; + } else + fd->flags |= FD_CONTROL | FD_READ; - fd->flags |= FD_SENDLEN; err = ps_ctl_handleargs(fd, buf, (size_t)bytes); - fd->flags &= ~FD_SENDLEN; - if (!(fd->flags & FD_LISTEN)) - fd->flags |= FD_COMMAND; if (err == -1) { logerr(__func__); return 0; } + + if (fd->flags & FD_LISTEN) + fd->flags &= ~FD_COMMAND; + else + fd->flags |= FD_COMMAND; + iov[0].iov_len = (size_t)bytes; /* If ps_ctl_handleargs returns 0 that means it didn't * do anything with the command, so pass it to the manager. */ @@ -158,15 +174,26 @@ control_handle_read(struct fd_list *fd) } #endif - err = control_user_ispriv(fd->ctx, uid, gid); + in_gid = fd->ctx->control_group; + err = control_user_ingroup(uid, gid, in_gid); if (err == -1) { logerr(__func__); return -1; } - if (err == 0) - fd->flags &= ~FD_PRIV; - else - fd->flags |= FD_PRIV; + if (err == 0) { + fd->flags &= ~FD_CONTROL; + in_gid = fd->ctx->read_group; + err = control_user_ingroup(uid, gid, in_gid); + if (err == -1) { + logerr(__func__); + return -1; + } + if (err == 0) + fd->flags &= ~FD_READ; + else + fd->flags |= FD_READ; + } else + fd->flags |= FD_CONTROL | FD_READ; return control_recvmsg(fd, &msg, (size_t)bytes); } @@ -180,12 +207,13 @@ control_handle_write(struct fd_list *fd) data = TAILQ_FIRST(&fd->queue); - if (data->data_flags & FD_SENDLEN) { + if (data->data_flags & FD_DATA_SENDLEN) { iov[0].iov_base = &data->data_len; iov[0].iov_len = sizeof(data->data_len); iov[1].iov_base = data->data; iov[1].iov_len = data->data_len; msg.msg_iovlen = 2; + } else { iov[0].iov_base = data->data; iov[0].iov_len = data->data_len; @@ -306,7 +334,7 @@ control_recvmsg(struct fd_list *fd, struct msghdr *msg, size_t len) } *ap = NULL; if (dhcpcd_handleargs(fd->ctx, fd, argc, argvp) == -1) { - logerr(__func__); + logerr("%s: dhcpcd_handleargs", __func__); if (errno != EINTR && errno != EAGAIN && errno != EPERM) return -1; } @@ -379,12 +407,6 @@ control_handle1(struct dhcpcd_ctx *ctx, int lfd, unsigned int fd_flags, fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) goto error; -#ifdef PRIVSEP - 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; @@ -590,7 +612,8 @@ control_send(struct dhcpcd_ctx *ctx, int argc, char *const *argv) } ssize_t -control_queue(struct fd_list *fd, const void *data, size_t data_len) +control_queuef(struct fd_list *fd, const void *data, size_t data_len, + unsigned int flags) { struct fd_data *d; unsigned short events; @@ -635,20 +658,26 @@ control_queue(struct fd_list *fd, const void *data, size_t data_len) } memcpy(d->data, data, data_len); d->data_len = data_len; - d->data_flags = fd->flags & FD_SENDLEN; + d->data_flags = flags; TAILQ_INSERT_TAIL(&fd->queue, d, next); events = ELE_WRITE; if (fd->flags & FD_LISTEN) events |= ELE_READ; - if (eloop_event_add(fd->ctx->eloop, fd->fd, events, - control_handle_data, fd) == -1) + if (eloop_event_add(fd->ctx->eloop, fd->fd, events, control_handle_data, + fd) == -1) return -1; return (ssize_t)d->data_len; } +ssize_t +control_queue(struct fd_list *fd, const void *data, size_t data_len) +{ + return control_queuef(fd, data, data_len, FD_DATA_SENDLEN); +} + int -control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid) +control_user_ingroup(uid_t uid, gid_t gid, gid_t grpid) { #ifdef __APPLE__ /* why is getgrouplist API is different ..... */ @@ -679,7 +708,7 @@ control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid) } for (gp = groups; ngroups != 0; ngroups--, gp++) { - if (*gp == GID ctx->control_group) { + if (*gp == GID grpid) { err = 1; goto out; } @@ -690,3 +719,19 @@ control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid) free(groups); return err; } + +ssize_t +control_handle_listen(struct fd_list *fd) +{ + int err; + + if (fd->flags & FD_READ) { + err = 0; + if (!(fd->flags & FD_COMMAND)) + fd->flags |= FD_LISTEN; + } else { + err = errno = EPERM; + logwarn(__func__); + } + return control_queuef(fd, &err, sizeof(err), 0); +} diff --git a/src/control.h b/src/control.h index c1159e8e..ff78ded3 100644 --- a/src/control.h +++ b/src/control.h @@ -50,6 +50,7 @@ struct fd_data { size_t data_size; size_t data_len; unsigned int data_flags; +#define FD_DATA_SENDLEN 0x01 }; TAILQ_HEAD(fd_data_head, fd_data); @@ -65,10 +66,10 @@ struct fd_list { }; TAILQ_HEAD(fd_list_head, fd_list); +#define FD_READ 0x02U +#define FD_CONTROL 0x08U +#define FD_COMMAND 0x04U #define FD_LISTEN 0x01U -#define FD_COMMAND 0x02U -#define FD_PRIV 0x04U -#define FD_SENDLEN 0x08U int control_start(struct dhcpcd_ctx *, const char *, sa_family_t); int control_stop(struct dhcpcd_ctx *); @@ -78,7 +79,9 @@ struct fd_list *control_find(struct dhcpcd_ctx *, int); struct fd_list *control_new(struct dhcpcd_ctx *, int, unsigned int); void control_free(struct fd_list *); void control_delete(struct fd_list *); +ssize_t control_queuef(struct fd_list *, const void *, size_t, unsigned int); ssize_t control_queue(struct fd_list *, const void *, size_t); int control_recvmsg(struct fd_list *, struct msghdr *, size_t); -int control_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid); +int control_user_ingroup(uid_t, gid_t, gid_t); +ssize_t control_handle_listen(struct fd_list *); #endif diff --git a/src/dhcpcd.c b/src/dhcpcd.c index fb9a45c8..9f7690d5 100644 --- a/src/dhcpcd.c +++ b/src/dhcpcd.c @@ -1575,18 +1575,20 @@ dhcpcd_signal_cb(int sig, void *arg) } #endif -int +ssize_t dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, char **argv) { struct interface *ifp; struct if_options *ifo; unsigned long long opts, orig_opts; - int opt, oi, oifind, af = AF_UNSPEC; - bool do_reboot, do_renew; + int opt, oi, oifind, af = AF_UNSPEC, error; + bool do_getifs = false, do_reboot = false, do_renew = false; size_t len, l, nifaces; char *tmp, *p; + optind = 0; + /* 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 @@ -1599,13 +1601,14 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, strlen(fd->ctx->cffile) + 1); } else if (strcmp(*argv, "--getinterfaces") == 0) { oifind = argc = 0; - goto dumplease; + opts |= DHCPCD_DUMPLEASE; + do_getifs = true; + goto doauth; } else if (strcmp(*argv, "--isprivileged") == 0) { - const char *ret = fd->flags & FD_PRIV ? "true" : "false"; + const char *ret = fd->flags & FD_CONTROL ? "true" : "false"; return control_queue(fd, ret, strlen(ret) + 1); } else if (strcmp(*argv, "--listen") == 0) { - fd->flags |= FD_LISTEN; - return 0; + return control_handle_listen(fd); } /* Log the command */ @@ -1627,7 +1630,6 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, loginfox("control command: %s", tmp); free(tmp); - optind = 0; oi = 0; opts = 0; do_reboot = do_renew = false; @@ -1664,12 +1666,36 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, } } - /* store the index; the optind will change when a getopt get called */ + /* If we are not dumping the lease we must be able to control + * dhcpcd */ +doauth: + if (opts & DHCPCD_DUMPLEASE) { + if (fd->flags & FD_READ) + error = 0; + else + error = EPERM; + } else { + if (fd->flags & FD_CONTROL) + error = 0; + else + error = EPERM; + } + + /* store the index; the optind will change when a getopt get + * called */ oifind = optind; if (opts & DHCPCD_DUMPLEASE) { - ctx->options |= DHCPCD_DUMPLEASE; - dumplease: + if (control_queuef(fd, &error, sizeof(error), 0) == -1) { + logerr("%s: control_queuef", __func__); + return -1; + } + if (error != 0) { + errno = error; + return -1; + } + if (!do_getifs) + ctx->options |= DHCPCD_DUMPLEASE; nifaces = 0; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (!ifp->active) @@ -1680,15 +1706,19 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, } if (oifind == argc || oi < argc) { opt = send_interface(NULL, ifp, af); - if (opt == -1) - goto dumperr; + if (opt == -1) { + nifaces = 0; + error = errno; + goto send_nifs; + } nifaces += (size_t)opt; } } - fd->flags &= ~FD_SENDLEN; - if (control_queue(fd, &nifaces, sizeof(nifaces)) == -1) + send_nifs: + if (control_queuef(fd, &nifaces, sizeof(nifaces), 0) == -1) + goto dumperr; + if (error != 0) goto dumperr; - fd->flags |= FD_SENDLEN; TAILQ_FOREACH(ifp, ctx->ifaces, next) { if (!ifp->active) continue; @@ -1705,23 +1735,21 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, return 0; dumperr: ctx->options &= ~DHCPCD_DUMPLEASE; - return -1; + error = errno; + goto out; } - /* Only privileged users can control dhcpcd via the socket. */ - if (!(fd->flags & FD_PRIV)) { - errno = EPERM; - return -1; - } + if (error != 0) + goto out; if (opts & (DHCPCD_EXITING | DHCPCD_RELEASE)) { if (oifind == argc && af == AF_UNSPEC) { ctx->options |= DHCPCD_EXITING; if (stop_all_interfaces(ctx, opts) == false) eloop_exit(ctx->eloop, EXIT_SUCCESS); - /* We did stop an interface, it will notify us once - * dropped so we can exit. */ - return 0; + /* We did stop an interface, it will notify us + * once dropped so we can exit. */ + goto out; } TAILQ_FOREACH(ifp, ctx->ifaces, next) { @@ -1753,24 +1781,34 @@ dhcpcd_handleargs(struct dhcpcd_ctx *ctx, struct fd_list *fd, int argc, stop_interface(ifp); ifo->options = orig_opts; } - return 0; + goto out; } if (do_renew) { if (oifind == argc) { dhcpcd_renew(ctx); - return 0; + goto out; } for (oi = oifind; oi < argc; oi++) { if ((ifp = if_find(ctx->ifaces, argv[oi])) == NULL) continue; dhcpcd_ifrenew(ifp); } - return 0; + goto out; } reload_config(ctx); reconf_reboot(ctx, do_reboot, argc, argv, oifind); + +out: + if (control_queuef(fd, &error, sizeof(error), 0) == -1) { + logerr("%s: control_queuef", __func__); + error = errno; + } + if (error != 0) { + errno = error; + return -1; + } return 0; } @@ -1852,6 +1890,23 @@ dhcpcd_readdump1(void *arg, unsigned short events) eloop_exit(ctx->eloop, EXIT_FAILURE); } +static int +dhcpcd_readerror(struct dhcpcd_ctx *ctx) +{ + int error = 0; + ssize_t len; + + len = read(ctx->control_fd, &error, sizeof(error)); + if (len == -1) + return -1; + if (len != sizeof(error)) { + errno = EINVAL; + return -1; + } + errno = error; + return error; +} + static void dhcpcd_readdump0(void *arg, unsigned short events) { @@ -2271,8 +2326,8 @@ main(int argc, char **argv, char **envp) if (!(ctx.options & (DHCPCD_TEST | DHCPCD_DUMPLEASE))) { printpidfile: - /* If we have any other args, we should run as a single dhcpcd - * instance for that interface. */ + /* If we have any other args, we should run as a single + * dhcpcd instance for that interface. */ if (optind == argc - 1 && !(ctx.options & DHCPCD_MANAGER)) { const char *per; const char *ifname; @@ -2307,8 +2362,8 @@ main(int argc, char **argv, char **envp) ctx.options |= DHCPCD_MANAGER; /* - * If we are given any interfaces or a family, we - * cannot send a signal as that would impact + * If we are given any interfaces or a family, + * we cannot send a signal as that would impact * other interfaces. */ if (optind != argc || family != AF_UNSPEC) @@ -2441,6 +2496,10 @@ main(int argc, char **argv, char **envp) logerr("%s: control_send", __func__); goto exit_failure; } + if (dhcpcd_readerror(&ctx) != 0) { + logerr("dhcpcd_control_read"); + goto exit_failure; + } if (ctx.options & DHCPCD_DUMPLEASE) { if (dhcpcd_readdump(&ctx) == -1) { logerr("%s: dhcpcd_readdump", __func__); @@ -2487,7 +2546,7 @@ main(int argc, char **argv, char **envp) loginfox(PACKAGE "-" VERSION " starting"); - // We don't need stdin past this point + /* We don't need stdin past this point */ dup_null(STDIN_FILENO); #if defined(USE_SIGNALS) && !defined(THERE_IS_NO_FORK) @@ -2671,8 +2730,8 @@ main(int argc, char **argv, char **envp) goto exit_failure; #endif - /* When running dhcpcd against a single interface, we need to retain - * the old behaviour of waiting for an IP address */ + /* When running dhcpcd against a single interface, we need to + * retain the old behaviour of waiting for an IP address */ if (ctx.ifc == 1 && !(ctx.options & DHCPCD_BACKGROUND)) ctx.options |= DHCPCD_WAITIP; @@ -2750,7 +2809,8 @@ main(int argc, char **argv, char **envp) logmessage(loglevel, "no interfaces have a carrier"); dhcpcd_daemonise(&ctx); } else if (t > 0 && - /* Test mode removes the daemonise bit, so check for both */ + /* Test mode removes the daemonise bit, so check for + both */ ctx.options & (DHCPCD_DAEMONISE | DHCPCD_TEST)) { eloop_timeout_add_sec(ctx.eloop, t, handle_exit_timeout, &ctx); diff --git a/src/dhcpcd.conf.5.in b/src/dhcpcd.conf.5.in index 82425b1c..b7d596e9 100644 --- a/src/dhcpcd.conf.5.in +++ b/src/dhcpcd.conf.5.in @@ -23,7 +23,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd June 29, 2026 +.Dd July 29, 2026 .Dt DHCPCD.CONF 5 .Os .Sh NAME @@ -162,10 +162,10 @@ In most cases, .Nm dhcpcd will set this automatically. .It Ic controlgroup Ar group -Sets the group ownership of -.Pa @RUNDIR@/sock -so that users other than root can connect to -.Nm dhcpcd . +Members of this user group can control dhcpcd, including but not limited to +stopping and starting interfaces. +.It Ic readgroup Ar group +Members of this user group can read dhcpcd interface configuration. .It Ic debug Echo debug messages to the stderr and syslog. .It Ic dev Ar value diff --git a/src/dhcpcd.h b/src/dhcpcd.h index e1865cb5..6cc5918d 100644 --- a/src/dhcpcd.h +++ b/src/dhcpcd.h @@ -176,6 +176,7 @@ struct dhcpcd_ctx { int control_fd; struct fd_list_head control_fds; char control_sock[sizeof(CONTROLSOCKET) + IF_NAMESIZE]; + gid_t read_group; gid_t control_group; /* DHCP Enterprise options, RFC3925 */ @@ -264,7 +265,7 @@ void dhcpcd_daemonised(struct dhcpcd_ctx *); void dhcpcd_daemonise(struct dhcpcd_ctx *); void dhcpcd_linkoverflow(struct dhcpcd_ctx *); -int dhcpcd_handleargs(struct dhcpcd_ctx *, struct fd_list *, int, char **); +ssize_t dhcpcd_handleargs(struct dhcpcd_ctx *, struct fd_list *, int, char **); void dhcpcd_handlecarrier(struct interface *, int, unsigned int); int dhcpcd_handleinterface(void *, int, const char *); void dhcpcd_handlehwaddr(struct interface *, uint16_t, const void *, uint8_t); diff --git a/src/if-options.c b/src/if-options.c index e584a2d5..df8e3486 100644 --- a/src/if-options.c +++ b/src/if-options.c @@ -158,6 +158,7 @@ const struct option cf_options[] = { { "background", no_argument, NULL, 'b' }, { "dhcp6", no_argument, NULL, O_DHCP6 }, { "nodhcp6", no_argument, NULL, O_NODHCP6 }, { "controlgroup", required_argument, NULL, O_CONTROLGRP }, + { "readgroup", required_argument, NULL, O_READGRP }, { "slaac", required_argument, NULL, O_SLAAC }, { "gateway", no_argument, NULL, O_GATEWAY }, { "reject", required_argument, NULL, O_REJECT }, @@ -2431,6 +2432,7 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, ifo->options &= ~DHCPCD_DHCP6; break; case O_CONTROLGRP: + case O_READGRP: ARG_REQUIRED; #ifdef PRIVSEP /* Control group is already set by this point. @@ -2472,21 +2474,33 @@ parse_option(struct dhcpcd_ctx *ctx, const char *ifname, struct if_options *ifo, return -1; } if (grp == NULL) { - if (!ctx->control_group) - logerrx("controlgroup: %s: not found", arg); + logerrx("group: %s: not found", arg); free(p); return -1; } - ctx->control_group = grp->gr_gid; + switch (opt) { + case O_CONTROLGRP: + ctx->control_group = grp->gr_gid; + break; + case O_READGRP: + ctx->read_group = grp->gr_gid; + break; + } free(p); #else grp = getgrnam(arg); if (grp == NULL) { - if (!ctx->control_group) - logerrx("controlgroup: %s: not found", arg); + logerrx("group: %s: not found", arg); return -1; } - ctx->control_group = grp->gr_gid; + switch (opt) { + case O_CONTROLGRP: + ctx->control_group = grp->gr_gid; + break; + case O_READGRP: + ctx->read_group = grp->gr_gid; + break; + } #endif break; case O_GATEWAY: diff --git a/src/if-options.h b/src/if-options.h index 288426ce..0158b9ae 100644 --- a/src/if-options.h +++ b/src/if-options.h @@ -204,6 +204,7 @@ #define O_BACKOFF_CUTOFF O_BASE + 61 #define O_BACKOFF_JITTER O_BASE + 62 #define O_ALLOW O_BASE + 63 +#define O_READGRP O_BASE + 64 extern const struct option cf_options[]; diff --git a/src/privsep-control.c b/src/privsep-control.c index 7a6f7cd6..5a60043c 100644 --- a/src/privsep-control.c +++ b/src/privsep-control.c @@ -81,7 +81,7 @@ ps_ctl_recvmsg(void *arg, unsigned short events) } ssize_t -ps_ctl_handleargs(struct fd_list *fd, char *data, __unused size_t len) +ps_ctl_handleargs(struct fd_list *fd, const char *data, __unused size_t len) { /* Make any change here in dhcpcd.c as well. * --version is NOT terminated with \n. */ @@ -91,12 +91,12 @@ ps_ctl_handleargs(struct fd_list *fd, char *data, __unused size_t len) return control_queue(fd, fd->ctx->cffile, strlen(fd->ctx->cffile) + 1); } else if (strcmp(data, "--isprivileged\n") == 0) { - const char *ret = fd->flags & FD_PRIV ? "true" : "false"; + const char *ret = fd->flags & FD_CONTROL ? "true" : "false"; return control_queue(fd, ret, strlen(ret) + 1); - } else if (strcmp(data, "--listen\n") == 0) { - fd->flags |= FD_LISTEN; - return 1; - } + } else if (strcmp(data, "--listen\n") == 0) + return control_handle_listen(fd); + + fd->flags |= FD_COMMAND; return 0; } @@ -105,12 +105,14 @@ ps_ctl_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg) { struct dhcpcd_ctx *ctx = arg; struct fd_list *fd; - unsigned int fd_flags = FD_SENDLEN; + unsigned int fd_flags = 0; int err; switch (psm->ps_cmd) { - case PS_CTL_PRIV: - fd_flags |= FD_PRIV; /* FALLTHROUGH */ + case PS_CTL_CONTROL: + fd_flags |= FD_CONTROL; /* FALLTHROUGH */ + case PS_CTL_READ: + fd_flags |= FD_READ; /* FALLTHROUGH */ case PS_CTL: if (msg->msg_iovlen != 1) { errno = EINVAL; @@ -179,7 +181,7 @@ ps_ctl_recv(void *arg, unsigned short events) TAILQ_FOREACH(fdl, &ctx->control_fds, next) { if (!(fdl->flags & FD_COMMAND)) continue; - if (control_queue(fdl, buf, (size_t)msglen) == -1) + if (control_queuef(fdl, buf, (size_t)msglen, 0) == -1) logerr("%s: control_queue", __func__); } } @@ -283,8 +285,7 @@ ps_ctl_start(struct dhcpcd_ctx *ctx) psp->psp_work_fd = work_fd[0]; close(work_fd[1]); close(listen_fd[1]); - ctx->ps_control = control_new(ctx, listen_fd[0], - FD_SENDLEN | FD_LISTEN); + ctx->ps_control = control_new(ctx, listen_fd[0], FD_LISTEN); if (ctx->ps_control == NULL) return -1; return pid; @@ -319,8 +320,14 @@ ssize_t ps_ctl_sendmsg(struct fd_list *fd, const struct msghdr *msg) { struct dhcpcd_ctx *ctx = fd->ctx; - uint16_t cmd = fd->flags & FD_PRIV ? PS_CTL_PRIV : PS_CTL; + uint16_t cmd; unsigned long flags = (unsigned long)fd->fd; + if (fd->flags & FD_CONTROL) + cmd = PS_CTL_CONTROL; + else if (fd->flags & FD_READ) + cmd = PS_CTL_READ; + else + cmd = PS_CTL; return ps_sendmsg(ctx, PS_CTL_FD(ctx), cmd, flags, msg); } diff --git a/src/privsep-control.h b/src/privsep-control.h index 08b75862..480fdf20 100644 --- a/src/privsep-control.h +++ b/src/privsep-control.h @@ -34,7 +34,7 @@ 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_handleargs(struct fd_list *, const char *, size_t); ssize_t ps_ctl_sendmsg(struct fd_list *, const struct msghdr *); ssize_t ps_ctl_sendeof(struct dhcpcd_ctx *); diff --git a/src/privsep-root.c b/src/privsep-root.c index dec46b0d..47170c8e 100644 --- a/src/privsep-root.c +++ b/src/privsep-root.c @@ -321,13 +321,13 @@ ps_root_dowritefile(const struct dhcpcd_ctx *ctx, mode_t mode, void *data, } static ssize_t -ps_root_douser_ispriv(struct dhcpcd_ctx *ctx, void *data, size_t len) +ps_root_douser_ingroup(void *data, size_t len) { uid_t uid; - gid_t gid; + gid_t gid, grpid; uint8_t *p; - if (len != sizeof(uid) + sizeof(gid)) { + if (len != sizeof(uid) + sizeof(gid) + sizeof(grpid)) { errno = EINVAL; return -1; } @@ -336,8 +336,10 @@ ps_root_douser_ispriv(struct dhcpcd_ctx *ctx, void *data, size_t len) memcpy(&uid, p, sizeof(uid)); p += sizeof(uid); memcpy(&gid, p, sizeof(gid)); + p += sizeof(gid); + memcpy(&grpid, p, sizeof(grpid)); - return control_user_ispriv(ctx, uid, gid); + return control_user_ingroup(uid, gid, grpid); } #ifdef AUTH @@ -583,8 +585,8 @@ ps_root_recvmsgcb(void *arg, struct ps_msghdr *psm, struct msghdr *msg) case PS_LOGREOPEN: err = logopen(ctx->logfile); break; - case PS_USER_ISPRIV: - err = ps_root_douser_ispriv(ctx, data, len); + case PS_USER_INGROUP: + err = ps_root_douser_ingroup(data, len); break; #ifdef AUTH case PS_AUTH_MONORDM: @@ -1143,22 +1145,29 @@ ps_root_logreopen(struct dhcpcd_ctx *ctx) } ssize_t -ps_root_user_ispriv(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid) +ps_root_user_ingroup(struct dhcpcd_ctx *ctx, uid_t uid, gid_t gid, gid_t grpid) { - struct iovec iov[] = { { - .iov_base = &uid, - .iov_len = sizeof(uid), - }, + struct iovec iov[] = { + { + .iov_base = &uid, + .iov_len = sizeof(uid), + }, { .iov_base = &gid, .iov_len = sizeof(gid), - } }; + }, + { + .iov_base = &grpid, + .iov_len = sizeof(grpid), + }, + + }; struct msghdr msg = { .msg_iov = iov, .msg_iovlen = __arraycount(iov), }; - if (ps_sendmsg(ctx, PS_ROOT_FD(ctx), PS_USER_ISPRIV, 0, &msg) == -1) { + if (ps_sendmsg(ctx, PS_ROOT_FD(ctx), PS_USER_INGROUP, 0, &msg) == -1) { logerr(__func__); return -1; } diff --git a/src/privsep-root.h b/src/privsep-root.h index af5fad95..737f52ab 100644 --- a/src/privsep-root.h +++ b/src/privsep-root.h @@ -53,7 +53,7 @@ ssize_t ps_root_logreopen(struct dhcpcd_ctx *); ssize_t ps_root_script(struct dhcpcd_ctx *, const void *, size_t); ssize_t ps_root_stopprocesses(struct dhcpcd_ctx *); int ps_root_getauthrdm(struct dhcpcd_ctx *, uint64_t *); -ssize_t ps_root_user_ispriv(struct dhcpcd_ctx *, uid_t, gid_t); +ssize_t ps_root_user_ingroup(struct dhcpcd_ctx *, uid_t, gid_t, gid_t); #ifdef PRIVSEP_GETHOSTNAME int ps_root_gethostname(struct dhcpcd_ctx *, char *, size_t); #endif diff --git a/src/privsep.h b/src/privsep.h index 20e59170..3d1fdea3 100644 --- a/src/privsep.h +++ b/src/privsep.h @@ -52,11 +52,12 @@ #define PS_FILEMTIME 0x0016 #define PS_AUTH_MONORDM 0x0017 #define PS_CTL 0x0018 -#define PS_CTL_PRIV 0x0019 -#define PS_LOGREOPEN 0x0020 -#define PS_STOPPROCS 0x0021 -#define PS_DAEMONISED 0x0022 -#define PS_USER_ISPRIV 0x0023 +#define PS_CTL_CONTROL 0x0019 +#define PS_CTL_READ 0x0020 +#define PS_LOGREOPEN 0x0021 +#define PS_STOPPROCS 0x0022 +#define PS_DAEMONISED 0x0023 +#define PS_USER_INGROUP 0x0024 /* Domains */ #define PS_ROOT 0x0101 diff --git a/src/script.c b/src/script.c index ca273e2b..210fb66a 100644 --- a/src/script.c +++ b/src/script.c @@ -593,7 +593,7 @@ make_env(struct dhcpcd_ctx *ctx, const struct interface *ifp, return -1; } -static int +static ssize_t send_interface1(struct fd_list *fd, const struct interface *ifp, const char *reason) {