1 /* SPDX-License-Identifier: BSD-2-Clause */ 2 /* 3 * Privilege Separation for dhcpcd, BSD driver 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 <sys/ioctl.h> 30 #include <sys/types.h> 31 #include <sys/sysctl.h> 32 33 /* Need these for filtering the ioctls */ 34 #include <arpa/inet.h> 35 #include <net/if.h> 36 #include <netinet/if_ether.h> 37 #include <netinet/in.h> 38 #include <netinet6/in6_var.h> 39 #include <netinet6/nd6.h> 40 #ifdef __NetBSD__ 41 #include <netinet/if_ether.h> 42 #include <net/if_vlanvar.h> /* Needs netinet/if_ether.h */ 43 #elif defined(__DragonFly__) 44 #include <net/vlan/if_vlan_var.h> 45 #else 46 #include <net/if_vlan_var.h> 47 #endif 48 #ifdef __DragonFly__ 49 # include <netproto/802_11/ieee80211_ioctl.h> 50 #else 51 # include <net80211/ieee80211.h> 52 # include <net80211/ieee80211_ioctl.h> 53 #endif 54 55 #include <errno.h> 56 #include <stdlib.h> 57 #include <string.h> 58 #include <unistd.h> 59 60 #include "dhcpcd.h" 61 #include "if.h" 62 #include "logerr.h" 63 #include "privsep.h" 64 65 static ssize_t 66 ps_root_doioctldom(struct dhcpcd_ctx *ctx, int domain, unsigned long req, void *data, size_t len) 67 { 68 #if defined(INET6) || (defined(SIOCALIFADDR) && defined(IFLR_ACTIVE)) 69 struct priv *priv = (struct priv *)ctx->priv; 70 #endif 71 int s; 72 73 switch(domain) { 74 #ifdef INET 75 case PF_INET: 76 s = ctx->pf_inet_fd; 77 break; 78 #endif 79 #ifdef INET6 80 case PF_INET6: 81 s = priv->pf_inet6_fd; 82 break; 83 #endif 84 #if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) /*NetBSD */ 85 case PF_LINK: 86 s = priv->pf_link_fd; 87 break; 88 #endif 89 default: 90 errno = EPFNOSUPPORT; 91 return -1; 92 } 93 94 /* Only allow these ioctls */ 95 switch(req) { 96 #ifdef SIOCGIFDATA 97 case SIOCGIFDATA: /* FALLTHROUGH */ 98 #endif 99 #ifdef SIOCG80211NWID 100 case SIOCG80211NWID: /* FALLTHROUGH */ 101 #endif 102 #ifdef SIOCGETVLAN 103 case SIOCGETVLAN: /* FALLTHROUGH */ 104 #endif 105 #ifdef SIOCIFAFATTACH 106 case SIOCIFAFATTACH: /* FALLTHROUGH */ 107 #endif 108 #ifdef SIOCSIFXFLAGS 109 case SIOCSIFXFLAGS: /* FALLTHROUGH */ 110 #endif 111 #ifdef SIOCSIFINFO_FLAGS 112 case SIOCSIFINFO_FLAGS: /* FALLTHROUGH */ 113 #endif 114 #ifdef SIOCSRTRFLUSH_IN6 115 case SIOCSRTRFLUSH_IN6: /* FALLTHROUGH */ 116 case SIOCSPFXFLUSH_IN6: /* FALLTHROUGH */ 117 #endif 118 #if defined(SIOCALIFADDR) && defined(IFLR_ACTIVE) 119 case SIOCALIFADDR: /* FALLTHROUGH */ 120 case SIOCDLIFADDR: /* FALLTHROUGH */ 121 #else 122 case SIOCSIFLLADDR: /* FALLTHROUGH */ 123 #endif 124 #ifdef SIOCSIFINFO_IN6 125 case SIOCSIFINFO_IN6: /* FALLTHROUGH */ 126 #endif 127 case SIOCAIFADDR_IN6: /* FALLTHROUGH */ 128 case SIOCDIFADDR_IN6: 129 break; 130 default: 131 errno = EPERM; 132 return -1; 133 } 134 135 return ioctl(s, req, data, len); 136 } 137 138 static ssize_t 139 ps_root_doroute(struct dhcpcd_ctx *ctx, void *data, size_t len) 140 { 141 142 return write(ctx->link_fd, data, len); 143 } 144 145 #if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE) 146 static ssize_t 147 ps_root_doindirectioctl(struct dhcpcd_ctx *ctx, 148 unsigned long req, void *data, size_t len) 149 { 150 char *p = data; 151 struct ifreq ifr = { .ifr_flags = 0 }; 152 size_t ifnamelen; 153 154 /* ioctl filtering is done in ps_root_doioctldom */ 155 156 if (len < sizeof(ifnamelen)) { 157 errno = EINVAL; 158 return -1; 159 } 160 memcpy(&ifnamelen, p, sizeof(ifnamelen)); 161 len -= sizeof(ifnamelen); 162 163 if (len < ifnamelen || ifnamelen > sizeof(ifr.ifr_name)) { 164 errno = EINVAL; 165 return -1; 166 } 167 168 memcpy(ifr.ifr_name, p, ifnamelen); 169 len -= ifnamelen; 170 171 if (len != 0) { 172 /* Ensure data is now aligned */ 173 memmove(data, p + ifnamelen, len); 174 ifr.ifr_data = data; 175 } 176 177 return ps_root_doioctldom(ctx, PF_INET, req, &ifr, sizeof(ifr)); 178 } 179 #endif 180 181 #ifdef HAVE_PLEDGE 182 static ssize_t 183 ps_root_doifignoregroup(struct dhcpcd_ctx *ctx, void *data, size_t len) 184 { 185 186 if (len == 0 || ((const char *)data)[len - 1] != '\0') { 187 errno = EINVAL; 188 return -1; 189 } 190 191 return if_ignoregroup(ctx->pf_inet_fd, data); 192 } 193 #endif 194 195 #ifdef HAVE_CAPSICUM 196 static ssize_t 197 ps_root_dosysctl(unsigned long flags, 198 void *data, size_t len, void **rdata, size_t *rlen) 199 { 200 char *p = data, *e = p + len; 201 int name[10]; 202 unsigned int namelen; 203 void *oldp; 204 size_t *oldlenp, oldlen, nlen; 205 void *newp; 206 size_t newlen; 207 int err; 208 209 if (sizeof(namelen) >= len) { 210 errno = EINVAL; 211 return -1; 212 } 213 memcpy(&namelen, p, sizeof(namelen)); 214 p += sizeof(namelen); 215 nlen = sizeof(*name) * namelen; 216 if (namelen > __arraycount(name)) { 217 errno = ENOBUFS; 218 return -1; 219 } 220 if (p + nlen > e) { 221 errno = EINVAL; 222 return -1; 223 } 224 memcpy(name, p, nlen); 225 p += nlen; 226 if (p + sizeof(oldlen) > e) { 227 errno = EINVAL; 228 return -1; 229 } 230 memcpy(&oldlen, p, sizeof(oldlen)); 231 p += sizeof(oldlen); 232 if (p + sizeof(newlen) > e) { 233 errno = EINVAL; 234 return -1; 235 } 236 memcpy(&newlen, p, sizeof(newlen)); 237 p += sizeof(newlen); 238 if (p + newlen > e) { 239 errno = EINVAL; 240 return -1; 241 } 242 newp = newlen ? p : NULL; 243 244 if (flags & PS_SYSCTL_OLEN) { 245 *rlen = sizeof(oldlen) + oldlen; 246 *rdata = malloc(*rlen); 247 if (*rdata == NULL) 248 return -1; 249 oldlenp = (size_t *)*rdata; 250 *oldlenp = oldlen; 251 if (flags & PS_SYSCTL_ODATA) 252 oldp = (char *)*rdata + sizeof(oldlen); 253 else 254 oldp = NULL; 255 } else { 256 oldlenp = NULL; 257 oldp = NULL; 258 } 259 260 err = sysctl(name, namelen, oldp, oldlenp, newp, newlen); 261 return err; 262 } 263 #endif 264 265 ssize_t 266 ps_root_os(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg, 267 void **rdata, size_t *rlen, bool *free_rdata) 268 { 269 struct iovec *iov = msg->msg_iov; 270 void *data = iov->iov_base; 271 size_t len = iov->iov_len; 272 ssize_t err; 273 274 switch (psm->ps_cmd) { 275 case PS_IOCTLLINK: 276 err = ps_root_doioctldom(ctx, PF_LINK, psm->ps_flags, data, len); 277 break; 278 case PS_IOCTL6: 279 err = ps_root_doioctldom(ctx, PF_INET6, psm->ps_flags, data, len); 280 break; 281 case PS_ROUTE: 282 return ps_root_doroute(ctx, data, len); 283 #if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE) 284 case PS_IOCTLINDIRECT: 285 err = ps_root_doindirectioctl(ctx, psm->ps_flags, data, len); 286 break; 287 #endif 288 #ifdef HAVE_PLEDGE 289 case PS_IFIGNOREGRP: 290 return ps_root_doifignoregroup(ctx, data, len); 291 #endif 292 #ifdef HAVE_CAPSICUM 293 case PS_SYSCTL: 294 *free_rdata = true; 295 return ps_root_dosysctl(psm->ps_flags, data, len, rdata, rlen); 296 #else 297 UNUSED(free_rdata); 298 #endif 299 default: 300 errno = ENOTSUP; 301 return -1; 302 } 303 304 if (err != -1) { 305 *rdata = data; 306 *rlen = len; 307 } 308 return err; 309 } 310 311 static ssize_t 312 ps_root_ioctldom(struct dhcpcd_ctx *ctx, uint16_t domain, unsigned long request, 313 void *data, size_t len) 314 { 315 316 if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), domain, 317 request, data, len) == -1) 318 return -1; 319 return ps_root_readerror(ctx, data, len); 320 } 321 322 ssize_t 323 ps_root_ioctllink(struct dhcpcd_ctx *ctx, unsigned long request, 324 void *data, size_t len) 325 { 326 327 return ps_root_ioctldom(ctx, PS_IOCTLLINK, request, data, len); 328 } 329 330 ssize_t 331 ps_root_ioctl6(struct dhcpcd_ctx *ctx, unsigned long request, 332 void *data, size_t len) 333 { 334 335 return ps_root_ioctldom(ctx, PS_IOCTL6, request, data, len); 336 } 337 338 ssize_t 339 ps_root_route(struct dhcpcd_ctx *ctx, void *data, size_t len) 340 { 341 342 if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), PS_ROUTE, 0, data, len) == -1) 343 return -1; 344 return ps_root_readerror(ctx, data, len); 345 } 346 347 #if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE) 348 ssize_t 349 ps_root_indirectioctl(struct dhcpcd_ctx *ctx, unsigned long request, 350 const char *ifname, void *data, size_t len) 351 { 352 size_t ifnamelen = strlen(ifname + 1); 353 354 struct iovec iov[] = { 355 { 356 .iov_base = &ifnamelen, 357 .iov_len = sizeof(ifnamelen), 358 }, 359 { 360 .iov_base = UNCONST(ifname), 361 .iov_len = ifnamelen, 362 }, 363 { 364 .iov_base = data, 365 .iov_len = len, 366 } 367 }; 368 struct msghdr msg = { 369 .msg_iov = iov, 370 .msg_iovlen = __arraycount(iov), 371 }; 372 373 if (ps_sendcmdmsg(ctx, PS_ROOT_FD(ctx), PS_IOCTLINDIRECT, 374 request, &msg) == -1) 375 return -1; 376 return ps_root_readerror(ctx, data, len); 377 } 378 #endif 379 380 #ifdef HAVE_PLEDGE 381 ssize_t 382 ps_root_ifignoregroup(struct dhcpcd_ctx *ctx, const char *ifname) 383 { 384 385 if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), PS_IFIGNOREGRP, 0, 386 ifname, strlen(ifname) + 1) == -1) 387 return -1; 388 return ps_root_readerror(ctx, NULL, 0); 389 } 390 #endif 391 392 #ifdef HAVE_CAPSICUM 393 ssize_t 394 ps_root_sysctl(struct dhcpcd_ctx *ctx, 395 const int *name, unsigned int namelen, 396 void *oldp, size_t *oldlenp, const void *newp, size_t newlen) 397 { 398 char buf[PS_BUFLEN], *p = buf; 399 unsigned long flags = 0; 400 size_t olen = (oldp && oldlenp) ? *oldlenp : 0, nolen; 401 402 if (sizeof(namelen) + (sizeof(*name) * namelen) + 403 sizeof(oldlenp) + 404 sizeof(newlen) + newlen > sizeof(buf)) 405 { 406 errno = ENOBUFS; 407 return -1; 408 } 409 410 if (oldlenp) 411 flags |= PS_SYSCTL_OLEN; 412 if (oldp) 413 flags |= PS_SYSCTL_ODATA; 414 memcpy(p, &namelen, sizeof(namelen)); 415 p += sizeof(namelen); 416 memcpy(p, name, sizeof(*name) * namelen); 417 p += sizeof(*name) * namelen; 418 memcpy(p, &olen, sizeof(olen)); 419 p += sizeof(olen); 420 memcpy(p, &newlen, sizeof(newlen)); 421 p += sizeof(newlen); 422 if (newlen) { 423 memcpy(p, newp, newlen); 424 p += newlen; 425 } 426 427 if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), PS_SYSCTL, 428 flags, buf, (size_t)(p - buf)) == -1) 429 return -1; 430 431 if (ps_root_readerror(ctx, buf, sizeof(buf)) == -1) 432 return -1; 433 434 p = buf; 435 memcpy(&nolen, p, sizeof(nolen)); 436 p += sizeof(nolen); 437 if (oldlenp) { 438 *oldlenp = nolen; 439 if (oldp && nolen <= olen) 440 memcpy(oldp, p, nolen); 441 } 442 443 return 0; 444 } 445 #endif 446