xref: /dragonfly/contrib/dhcpcd/src/privsep-bsd.c (revision 556932ec)
1 /* SPDX-License-Identifier: BSD-2-Clause */
2 /*
3  * Privilege Separation for dhcpcd, BSD driver
4  * Copyright (c) 2006-2023 Roy Marples <roy@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 
153 	/* ioctl filtering is done in ps_root_doioctldom */
154 
155 	if (len < IFNAMSIZ + 1) {
156 		errno = EINVAL;
157 		return -1;
158 	}
159 
160 	strlcpy(ifr.ifr_name, p, IFNAMSIZ);
161 	len -= IFNAMSIZ;
162 	memmove(data, p + IFNAMSIZ, len);
163 	ifr.ifr_data = data;
164 
165 	return ps_root_doioctldom(ctx, PF_INET, req, &ifr, sizeof(ifr));
166 }
167 #endif
168 
169 #ifdef HAVE_PLEDGE
170 static ssize_t
171 ps_root_doifignoregroup(struct dhcpcd_ctx *ctx, void *data, size_t len)
172 {
173 
174 	if (len == 0 || ((const char *)data)[len - 1] != '\0') {
175 		errno = EINVAL;
176 		return -1;
177 	}
178 
179 	return if_ignoregroup(ctx->pf_inet_fd, data);
180 }
181 #endif
182 
183 #ifdef HAVE_CAPSICUM
184 static ssize_t
185 ps_root_dosysctl(unsigned long flags,
186     void *data, size_t len, void **rdata, size_t *rlen)
187 {
188 	char *p = data, *e = p + len;
189 	int name[10];
190 	unsigned int namelen;
191 	void *oldp;
192 	size_t *oldlenp, oldlen, nlen;
193 	void *newp;
194 	size_t newlen;
195 	int err;
196 
197 	if (sizeof(namelen) >= len) {
198 		errno = EINVAL;
199 		return -1;
200 	}
201 	memcpy(&namelen, p, sizeof(namelen));
202 	p += sizeof(namelen);
203 	nlen = sizeof(*name) * namelen;
204 	if (namelen > __arraycount(name)) {
205 		errno = ENOBUFS;
206 		return -1;
207 	}
208 	if (p + nlen > e) {
209 		errno = EINVAL;
210 		return -1;
211 	}
212 	memcpy(name, p, nlen);
213 	p += nlen;
214 	if (p + sizeof(oldlen) > e) {
215 		errno = EINVAL;
216 		return -1;
217 	}
218 	memcpy(&oldlen, p, sizeof(oldlen));
219 	p += sizeof(oldlen);
220 	if (p + sizeof(newlen) > e) {
221 		errno = EINVAL;
222 		return -1;
223 	}
224 	memcpy(&newlen, p, sizeof(newlen));
225 	p += sizeof(newlen);
226 	if (p + newlen > e) {
227 		errno = EINVAL;
228 		return -1;
229 	}
230 	newp = newlen ? p : NULL;
231 
232 	if (flags & PS_SYSCTL_OLEN) {
233 		*rlen = sizeof(oldlen) + oldlen;
234 		*rdata = malloc(*rlen);
235 		if (*rdata == NULL)
236 			return -1;
237 		oldlenp = (size_t *)*rdata;
238 		*oldlenp = oldlen;
239 		if (flags & PS_SYSCTL_ODATA)
240 			oldp = (char *)*rdata + sizeof(oldlen);
241 		else
242 			oldp = NULL;
243 	} else {
244 		oldlenp = NULL;
245 		oldp = NULL;
246 	}
247 
248 	err = sysctl(name, namelen, oldp, oldlenp, newp, newlen);
249 	return err;
250 }
251 #endif
252 
253 ssize_t
254 ps_root_os(struct dhcpcd_ctx *ctx, struct ps_msghdr *psm, struct msghdr *msg,
255     void **rdata, size_t *rlen, bool *free_rdata)
256 {
257 	struct iovec *iov = msg->msg_iov;
258 	void *data = iov->iov_base;
259 	size_t len = iov->iov_len;
260 	ssize_t err;
261 
262 	switch (psm->ps_cmd) {
263 	case PS_IOCTLLINK:
264 		err = ps_root_doioctldom(ctx, PF_LINK, psm->ps_flags, data, len);
265 		break;
266 	case PS_IOCTL6:
267 		err = ps_root_doioctldom(ctx, PF_INET6, psm->ps_flags, data, len);
268 		break;
269 	case PS_ROUTE:
270 		return ps_root_doroute(ctx, data, len);
271 #if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE)
272 	case PS_IOCTLINDIRECT:
273 		err = ps_root_doindirectioctl(ctx, psm->ps_flags, data, len);
274 		break;
275 #endif
276 #ifdef HAVE_PLEDGE
277 	case PS_IFIGNOREGRP:
278 		return ps_root_doifignoregroup(ctx, data, len);
279 #endif
280 #ifdef HAVE_CAPSICUM
281 	case PS_SYSCTL:
282 		*free_rdata = true;
283 		return ps_root_dosysctl(psm->ps_flags, data, len, rdata, rlen);
284 #else
285 	UNUSED(free_rdata);
286 #endif
287 	default:
288 		errno = ENOTSUP;
289 		return -1;
290 	}
291 
292 	if (err != -1) {
293 		*rdata = data;
294 		*rlen = len;
295 	}
296 	return err;
297 }
298 
299 static ssize_t
300 ps_root_ioctldom(struct dhcpcd_ctx *ctx, uint16_t domain, unsigned long request,
301     void *data, size_t len)
302 {
303 
304 	if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), domain,
305 	    request, data, len) == -1)
306 		return -1;
307 	return ps_root_readerror(ctx, data, len);
308 }
309 
310 ssize_t
311 ps_root_ioctllink(struct dhcpcd_ctx *ctx, unsigned long request,
312     void *data, size_t len)
313 {
314 
315 	return ps_root_ioctldom(ctx, PS_IOCTLLINK, request, data, len);
316 }
317 
318 ssize_t
319 ps_root_ioctl6(struct dhcpcd_ctx *ctx, unsigned long request,
320     void *data, size_t len)
321 {
322 
323 	return ps_root_ioctldom(ctx, PS_IOCTL6, request, data, len);
324 }
325 
326 ssize_t
327 ps_root_route(struct dhcpcd_ctx *ctx, void *data, size_t len)
328 {
329 
330 	if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), PS_ROUTE, 0, data, len) == -1)
331 		return -1;
332 	return ps_root_readerror(ctx, data, len);
333 }
334 
335 #if defined(HAVE_CAPSICUM) || defined(HAVE_PLEDGE)
336 ssize_t
337 ps_root_indirectioctl(struct dhcpcd_ctx *ctx, unsigned long request,
338     const char *ifname, void *data, size_t len)
339 {
340 	char buf[PS_BUFLEN];
341 
342 	if (IFNAMSIZ + len > sizeof(buf)) {
343 		errno = ENOBUFS;
344 		return -1;
345 	}
346 
347 	strlcpy(buf, ifname, IFNAMSIZ);
348 	memcpy(buf + IFNAMSIZ, data, len);
349 	if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), PS_IOCTLINDIRECT,
350 	    request, buf, IFNAMSIZ + len) == -1)
351 		return -1;
352 	return ps_root_readerror(ctx, data, len);
353 }
354 #endif
355 
356 #ifdef HAVE_PLEDGE
357 ssize_t
358 ps_root_ifignoregroup(struct dhcpcd_ctx *ctx, const char *ifname)
359 {
360 
361 	if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), PS_IFIGNOREGRP, 0,
362 	    ifname, strlen(ifname) + 1) == -1)
363 		return -1;
364 	return ps_root_readerror(ctx, NULL, 0);
365 }
366 #endif
367 
368 #ifdef HAVE_CAPSICUM
369 ssize_t
370 ps_root_sysctl(struct dhcpcd_ctx *ctx,
371     const int *name, unsigned int namelen,
372     void *oldp, size_t *oldlenp, const void *newp, size_t newlen)
373 {
374 	char buf[PS_BUFLEN], *p = buf;
375 	unsigned long flags = 0;
376 	size_t olen = (oldp && oldlenp) ? *oldlenp : 0, nolen;
377 
378 	if (sizeof(namelen) + (sizeof(*name) * namelen) +
379 	    sizeof(oldlenp) +
380 	    sizeof(newlen) + newlen > sizeof(buf))
381 	{
382 		errno = ENOBUFS;
383 		return -1;
384 	}
385 
386 	if (oldlenp)
387 		flags |= PS_SYSCTL_OLEN;
388 	if (oldp)
389 		flags |= PS_SYSCTL_ODATA;
390 	memcpy(p, &namelen, sizeof(namelen));
391 	p += sizeof(namelen);
392 	memcpy(p, name, sizeof(*name) * namelen);
393 	p += sizeof(*name) * namelen;
394 	memcpy(p, &olen, sizeof(olen));
395 	p += sizeof(olen);
396 	memcpy(p, &newlen, sizeof(newlen));
397 	p += sizeof(newlen);
398 	if (newlen) {
399 		memcpy(p, newp, newlen);
400 		p += newlen;
401 	}
402 
403 	if (ps_sendcmd(ctx, PS_ROOT_FD(ctx), PS_SYSCTL,
404 	    flags, buf, (size_t)(p - buf)) == -1)
405 		return -1;
406 
407 	if (ps_root_readerror(ctx, buf, sizeof(buf)) == -1)
408 		return -1;
409 
410 	p = buf;
411 	memcpy(&nolen, p, sizeof(nolen));
412 	p += sizeof(nolen);
413 	if (oldlenp) {
414 		*oldlenp = nolen;
415 		if (oldp && nolen <= olen)
416 			memcpy(oldp, p, nolen);
417 	}
418 
419 	return 0;
420 }
421 #endif
422