1 /* 2 * Privilege Separation for dhcpcd, control proxy 3 * SPDX-License-Identifier: BSD-2-Clause 4 * Copyright (c) 2006-2025 Roy Marples <roy (at) marples.name> 5 * All rights reserved 6 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include <errno.h> 30 #include <stdlib.h> 31 #include <string.h> 32 #include <unistd.h> 33 34 #include "control.h" 35 #include "dhcpcd.h" 36 #include "eloop.h" 37 #include "logerr.h" 38 #include "privsep.h" 39 40 #define PS_CTL_FD(ctx) (ctx)->ps_ctl->psp_fd 41 42 /* We expect to have open 2 privsep STREAM, 2 STREAM and 2 file STREAM fds */ 43 44 static int 45 ps_ctl_startcb(struct ps_process *psp) 46 { 47 struct dhcpcd_ctx *ctx = psp->psp_ctx; 48 sa_family_t af; 49 50 if (ctx->options & DHCPCD_MANAGER) { 51 #ifdef HAVE_SETPROCTITLE 52 setproctitle("[control proxy]"); 53 #endif 54 af = AF_UNSPEC; 55 } else { 56 #ifdef HAVE_SETPROCTITLE 57 setproctitle("[control proxy] %s%s%s", ctx->ifv[0], 58 ctx->options & DHCPCD_IPV4 ? " [ip4]" : "", 59 ctx->options & DHCPCD_IPV6 ? " [ip6]" : ""); 60 #endif 61 if ((ctx->options & (DHCPCD_IPV4 | DHCPCD_IPV6)) == DHCPCD_IPV4) 62 af = AF_INET; 63 else if ((ctx->options & (DHCPCD_IPV4 | DHCPCD_IPV6)) == 64 DHCPCD_IPV6) 65 af = AF_INET6; 66 else 67 af = AF_UNSPEC; 68 } 69 70 return control_start(ctx, 71 ctx->options & DHCPCD_MANAGER ? NULL : *ctx->ifv, af); 72 } 73 74 static void 75 ps_ctl_recvmsg(void *arg, unsigned short events) 76 { 77 struct ps_process *psp = arg; 78 79 if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, events, NULL, NULL) == -1) 80 logerr(__func__); 81 } 82 83 ssize_t 84 ps_ctl_handleargs(struct fd_list *fd, const char *data, size_t len) 85 { 86 #define strclcmp(d, l, c) \ 87 ((l) == (__arraycount((c))) ? strncmp((d), (c), (l)) : -1) 88 89 /* Make any change here in dhcpcd.c as well. 90 * --version is NOT terminated with \n. */ 91 if (strclcmp(data, len, "--version") == 0) 92 return control_queue(fd, VERSION, strlen(VERSION) + 1); 93 else if (strclcmp(data, len, "--getconfigfile\n") == 0) 94 return control_queue(fd, fd->ctx->cffile, 95 strlen(fd->ctx->cffile) + 1); 96 else if (strclcmp(data, len, "--isprivileged\n") == 0) { 97 const char *ret = fd->flags & FD_CONTROL ? "true" : "false"; 98 return control_queue(fd, ret, strlen(ret) + 1); 99 } else if (strclcmp(data, len, "--listen\n") == 0) 100 return control_handle_listen(fd); 101 102 fd->flags |= FD_COMMAND; 103 return 0; 104 } 105 106 static ssize_t 107 ps_ctl_dispatch(void *arg, struct ps_msghdr *psm, struct msghdr *msg) 108 { 109 struct ps_process *psp = arg; 110 struct dhcpcd_ctx *ctx = psp->psp_ctx; 111 struct fd_list *fd; 112 unsigned int fd_flags = 0; 113 int err; 114 115 switch (psm->ps_cmd) { 116 case PS_CTL_CONTROL: 117 fd_flags |= FD_CONTROL; /* FALLTHROUGH */ 118 case PS_CTL_READ: 119 fd_flags |= FD_READ; /* FALLTHROUGH */ 120 case PS_CTL: 121 if (msg->msg_iovlen != 1) { 122 errno = EINVAL; 123 return -1; 124 } 125 fd = control_new(ctx, ctx->ps_ctl->psp_work_fd, fd_flags); 126 if (fd == NULL) 127 return -1; 128 fd->peer_id = (unsigned int)psm->ps_flags; 129 err = control_recvmsg(fd, msg, psm->ps_datalen); 130 if (err == -1 || err == 0) 131 control_free(fd); 132 break; 133 default: 134 errno = ENOTSUP; 135 return -1; 136 } 137 return 0; 138 } 139 140 static void 141 ps_ctl_dodispatch(void *arg, unsigned short events) 142 { 143 struct ps_process *psp = arg; 144 145 if (ps_recvpsmsg(psp->psp_ctx, psp->psp_fd, events, ps_ctl_dispatch, 146 psp) == -1) 147 logerr(__func__); 148 } 149 150 static void 151 ps_ctl_recv(void *arg, unsigned short events) 152 { 153 struct dhcpcd_ctx *ctx = arg; 154 int fd; 155 unsigned int peer_id; 156 size_t msglen; 157 /* Control messages for a peer are prefixed with fd and message len */ 158 struct iovec iov[] = { 159 { 160 .iov_base = &peer_id, 161 .iov_len = sizeof(peer_id), 162 }, 163 { 164 .iov_base = &msglen, 165 .iov_len = sizeof(msglen), 166 }, 167 }; 168 struct msghdr msg = { 169 .msg_iov = iov, 170 .msg_iovlen = __arraycount(iov), 171 }; 172 ssize_t rlen; 173 struct fd_list *fdl; 174 175 if (events & ELE_HANGUP) { 176 hangup: 177 eloop_exit(ctx->eloop, EXIT_SUCCESS); 178 return; 179 } 180 181 if (!(events & ELE_READ)) 182 logerrx("%s: unexpected event 0x%04x", __func__, events); 183 184 fd = ctx->ps_ctl->psp_work_fd; 185 rlen = recvmsg(fd, &msg, MSG_WAITALL); 186 if (rlen == 0) 187 goto hangup; 188 if (rlen == -1) { 189 logerr("%s: recvmsg hdr", __func__); 190 eloop_exit(ctx->eloop, EXIT_FAILURE); 191 return; 192 } 193 if (rlen != sizeof(peer_id) + sizeof(msglen)) { 194 errno = EINVAL; 195 logerr("%s: recvmsg hdr", __func__); 196 eloop_exit(ctx->eloop, EXIT_FAILURE); 197 return; 198 } 199 200 if (msglen == 0) /* ulikely */ 201 return; 202 203 if (ctx->io_buflen < msglen) { 204 void *n = realloc(ctx->io_buf, msglen); 205 if (n == NULL) { 206 logerr(__func__); 207 eloop_exit(ctx->eloop, EXIT_FAILURE); 208 return; 209 } 210 ctx->io_buf = n; 211 ctx->io_buflen = msglen; 212 } 213 214 iov[0].iov_base = ctx->io_buf; 215 iov[0].iov_len = msglen; 216 msg.msg_iovlen = 1; 217 rlen = recvmsg(fd, &msg, MSG_WAITALL); 218 if (rlen == 0) 219 goto hangup; 220 if (rlen == -1) { 221 logerr("%s: recvmsg msg", __func__); 222 eloop_exit(ctx->eloop, EXIT_FAILURE); 223 return; 224 } 225 if ((size_t)rlen != msglen) { 226 errno = EINVAL; 227 logerr("%s: recvmsg msg", __func__); 228 eloop_exit(ctx->eloop, EXIT_FAILURE); 229 return; 230 } 231 232 /* Send to our peer */ 233 TAILQ_FOREACH(fdl, &ctx->control_fds, next) { 234 if (fdl->id != peer_id) 235 continue; 236 if (control_queuef(fdl, ctx->io_buf, (size_t)msglen, 0) == -1) 237 logerr("%s: control_queue", __func__); 238 break; 239 } 240 } 241 242 static void 243 ps_ctl_listen(void *arg, unsigned short events) 244 { 245 struct dhcpcd_ctx *ctx = arg; 246 ssize_t len; 247 size_t msglen; 248 struct iovec iov[] = { { 249 .iov_base = &msglen, 250 .iov_len = sizeof(msglen), 251 } }; 252 struct msghdr msg = { 253 .msg_iov = iov, 254 .msg_iovlen = __arraycount(iov), 255 }; 256 int fd; 257 struct fd_list *fdl; 258 259 if (events & ELE_HANGUP) { 260 hangup: 261 eloop_exit(ctx->eloop, EXIT_SUCCESS); 262 return; 263 } 264 265 if (!(events & ELE_READ)) 266 logerrx("%s: unexpected event 0x%04x", __func__, events); 267 268 fd = ctx->ps_control->fd; 269 len = recvmsg(fd, &msg, MSG_WAITALL); 270 if (len == 0) 271 goto hangup; 272 if (len != sizeof(msglen)) { 273 logerr("%s: recvmsg len %zd", __func__, len); 274 goto err; 275 } 276 277 if (ps_bufalloc(ctx, msglen) == -1) { 278 logerr("%s: realloc", __func__); 279 goto err; 280 } 281 282 iov->iov_base = ctx->ps_buf; 283 iov->iov_len = msglen; 284 len = recvmsg(fd, &msg, MSG_WAITALL); 285 if (len == 0) 286 goto hangup; 287 if ((size_t)len != msglen) { 288 logerr("%s: recvmsg", __func__); 289 goto err; 290 } 291 292 /* Send to our listeners */ 293 TAILQ_FOREACH(fdl, &ctx->control_fds, next) { 294 if (!(fdl->flags & FD_LISTEN)) 295 continue; 296 if (control_queue(fdl, ctx->ps_buf, msglen) == -1) 297 logerr("%s: control_queue", __func__); 298 } 299 300 return; 301 302 err: 303 eloop_exit(ctx->eloop, EXIT_FAILURE); 304 } 305 306 pid_t 307 ps_ctl_start(struct dhcpcd_ctx *ctx) 308 { 309 struct ps_id id = { 310 .psi_ifindex = 0, 311 .psi_cmd = PS_CTL, 312 }; 313 struct ps_process *psp; 314 int work_fd[2], listen_fd[2]; 315 pid_t pid; 316 317 if_closesockets(ctx); 318 319 if (xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, work_fd) == 320 -1 || 321 xsocketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, listen_fd) == 322 -1) 323 return -1; 324 #ifdef PRIVSEP_RIGHTS 325 if (ps_rights_limit_fdpair(work_fd) == -1 || 326 ps_rights_limit_fdpair(listen_fd) == -1) 327 return -1; 328 #endif 329 330 psp = ctx->ps_ctl = ps_newprocess(ctx, &id); 331 strlcpy(psp->psp_name, "control proxy", sizeof(psp->psp_name)); 332 pid = ps_startprocess(psp, ps_ctl_recvmsg, ps_ctl_dodispatch, 333 ps_ctl_startcb, PSF_DROPPRIVS); 334 335 if (pid == -1) 336 return -1; 337 else if (pid != 0) { 338 psp->psp_work_fd = work_fd[0]; 339 close(work_fd[1]); 340 close(listen_fd[1]); 341 ctx->ps_control = control_new(ctx, listen_fd[0], FD_LISTEN); 342 if (ctx->ps_control == NULL) 343 return -1; 344 return pid; 345 } 346 347 close(work_fd[0]); 348 close(listen_fd[0]); 349 350 psp->psp_work_fd = work_fd[1]; 351 if (eloop_event_add(ctx->eloop, psp->psp_work_fd, ELE_READ, ps_ctl_recv, 352 ctx) == -1) 353 return -1; 354 355 ctx->ps_control = control_new(ctx, listen_fd[1], 0); 356 if (ctx->ps_control == NULL) 357 return -1; 358 if (eloop_event_add(ctx->eloop, ctx->ps_control->fd, ELE_READ, 359 ps_ctl_listen, ctx) == -1) 360 return -1; 361 362 ps_entersandbox("stdio inet", NULL); 363 return 0; 364 } 365 366 int 367 ps_ctl_stop(struct dhcpcd_ctx *ctx) 368 { 369 return ps_stopprocess(ctx->ps_ctl); 370 } 371 372 ssize_t 373 ps_ctl_sendmsg(struct fd_list *fd, const struct msghdr *msg) 374 { 375 struct dhcpcd_ctx *ctx = fd->ctx; 376 uint16_t cmd; 377 unsigned long flags = (unsigned long)fd->id; 378 379 if (fd->flags & FD_CONTROL) 380 cmd = PS_CTL_CONTROL; 381 else if (fd->flags & FD_READ) 382 cmd = PS_CTL_READ; 383 else 384 cmd = PS_CTL; 385 return ps_sendmsg(ctx, PS_CTL_FD(ctx), cmd, flags, msg); 386 } 387