From 2716d832cc4fdca101282ee846e1ff522c591e53 Mon Sep 17 00:00:00 2001 From: Roy Marples Date: Sun, 5 Jul 2026 15:15:57 +0100 Subject: [PATCH 1/3] 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 2/3] 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 3/3] 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 */