Home | History | Annotate | Line # | Download | only in src
      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