xref: /freebsd/usr.sbin/bhyve/slirp/slirp-helper.c (revision 0e62ebd20172f67283bac9526c2aaeaffeb41b45)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2023, 2025 Mark Johnston <markj@FreeBSD.org>
5  *
6  * This software was developed by the University of Cambridge Computer
7  * Laboratory (Department of Computer Science and Technology) under Innovate
8  * UK project 105694, "Digital Security by Design (DSbD) Technology Platform
9  * Prototype".
10  */
11 
12 /*
13  * A helper process which lets bhyve's libslirp-based network backend work
14  * outside bhyve's Capsicum sandbox.  We are started with a SOCK_SEQPACKET
15  * socket through which we pass and receive packets from the guest's frontend.
16  *
17  * At initialization time, we receive an nvlist over the socket which describes
18  * the desired slirp configuration.
19  */
20 
21 #include <sys/nv.h>
22 #include <sys/socket.h>
23 
24 #include <assert.h>
25 #include <capsicum_helpers.h>
26 #include <dlfcn.h>
27 #include <err.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <poll.h>
31 #include <pwd.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <time.h>
36 #include <unistd.h>
37 
38 #include "config.h"
39 #include "libslirp.h"
40 
41 #define	SLIRP_MTU	2048
42 
43 struct slirp_priv {
44 	Slirp *slirp;		/* libslirp handle */
45 	int sock;		/* data and control socket */
46 	int wakeup[2];		/* used to wake up the pollfd thread */
47 	struct pollfd *pollfds;
48 	size_t npollfds;
49 	size_t lastpollfd;
50 };
51 
52 typedef int (*slirp_add_hostxfwd_p_t)(Slirp *,
53     const struct sockaddr *, socklen_t, const struct sockaddr *, socklen_t,
54     int);
55 typedef void (*slirp_cleanup_p_t)(Slirp *);
56 typedef void (*slirp_input_p_t)(Slirp *, const uint8_t *, int);
57 typedef Slirp *(*slirp_new_p_t)(const SlirpConfig *, const SlirpCb *, void *);
58 typedef void (*slirp_pollfds_fill_p_t)(Slirp *, uint32_t *timeout,
59     SlirpAddPollCb, void *);
60 typedef void (*slirp_pollfds_poll_p_t)(Slirp *, int, SlirpGetREventsCb, void *);
61 
62 /* Function pointer table, initialized by libslirp_init(). */
63 static slirp_add_hostxfwd_p_t slirp_add_hostxfwd_p;
64 static slirp_cleanup_p_t slirp_cleanup_p;
65 static slirp_input_p_t slirp_input_p;
66 static slirp_new_p_t slirp_new_p;
67 static slirp_pollfds_fill_p_t slirp_pollfds_fill_p;
68 static slirp_pollfds_poll_p_t slirp_pollfds_poll_p;
69 
70 static int64_t
slirp_cb_clock_get_ns(void * param __unused)71 slirp_cb_clock_get_ns(void *param __unused)
72 {
73 	struct timespec ts;
74 	int error;
75 
76 	error = clock_gettime(CLOCK_MONOTONIC, &ts);
77 	assert(error == 0);
78 	return ((int64_t)(ts.tv_sec * 1000000000L + ts.tv_nsec));
79 }
80 
81 static void
slirp_cb_notify(void * param)82 slirp_cb_notify(void *param)
83 {
84 	struct slirp_priv *priv;
85 
86 	/* Wake up the poll thread.  We assume that priv->mtx is held here. */
87 	priv = param;
88 	(void)write(priv->wakeup[1], "M", 1);
89 }
90 
91 static void
slirp_cb_register_poll_fd(int fd,void * param __unused)92 slirp_cb_register_poll_fd(int fd, void *param __unused)
93 {
94 	const int one = 1;
95 
96 	(void)setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof(int));
97 }
98 
99 static ssize_t
slirp_cb_send_packet(const void * buf,size_t len,void * param)100 slirp_cb_send_packet(const void *buf, size_t len, void *param)
101 {
102 	struct slirp_priv *priv;
103 	ssize_t n;
104 
105 	priv = param;
106 
107 	assert(len <= SLIRP_MTU);
108 	n = send(priv->sock, buf, len, MSG_EOR);
109 	if (n < 0) {
110 		warn("slirp_cb_send_packet: send");
111 		return (n);
112 	}
113 	assert((size_t)n == len);
114 
115 	return (n);
116 }
117 
118 static void
slirp_cb_unregister_poll_fd(int fd __unused,void * opaque __unused)119 slirp_cb_unregister_poll_fd(int fd __unused, void *opaque __unused)
120 {
121 }
122 
123 /* Callbacks invoked from within libslirp. */
124 static const struct SlirpCb slirp_cbs = {
125 	.clock_get_ns = slirp_cb_clock_get_ns,
126 	.notify = slirp_cb_notify,
127 	.register_poll_fd = slirp_cb_register_poll_fd,
128 	.send_packet = slirp_cb_send_packet,
129 	.unregister_poll_fd = slirp_cb_unregister_poll_fd,
130 };
131 
132 static int
slirpev2pollev(int events)133 slirpev2pollev(int events)
134 {
135 	int ret;
136 
137 	ret = 0;
138 	if (events & SLIRP_POLL_IN)
139 		ret |= POLLIN;
140 	if (events & SLIRP_POLL_OUT)
141 		ret |= POLLOUT;
142 	if (events & SLIRP_POLL_PRI)
143 		ret |= POLLPRI;
144 	if (events & SLIRP_POLL_ERR)
145 		ret |= POLLERR;
146 	if (events & SLIRP_POLL_HUP)
147 		ret |= POLLHUP;
148 	return (ret);
149 }
150 
151 static int
pollev2slirpev(int events)152 pollev2slirpev(int events)
153 {
154 	int ret;
155 
156 	ret = 0;
157 	if (events & POLLIN)
158 		ret |= SLIRP_POLL_IN;
159 	if (events & POLLOUT)
160 		ret |= SLIRP_POLL_OUT;
161 	if (events & POLLPRI)
162 		ret |= SLIRP_POLL_PRI;
163 	if (events & POLLERR)
164 		ret |= SLIRP_POLL_ERR;
165 	if (events & POLLHUP)
166 		ret |= SLIRP_POLL_HUP;
167 	return (ret);
168 }
169 
170 static int
slirp_addpoll(struct slirp_priv * priv,int fd,int events)171 slirp_addpoll(struct slirp_priv *priv, int fd, int events)
172 {
173 	struct pollfd *pollfd, *pollfds;
174 	size_t i;
175 
176 	for (i = priv->lastpollfd + 1; i < priv->npollfds; i++)
177 		if (priv->pollfds[i].fd == -1)
178 			break;
179 	if (i == priv->npollfds) {
180 		const size_t POLLFD_GROW = 4;
181 
182 		priv->npollfds += POLLFD_GROW;
183 		pollfds = realloc(priv->pollfds,
184 		    sizeof(*pollfds) * priv->npollfds);
185 		if (pollfds == NULL)
186 			return (-1);
187 		for (i = priv->npollfds - POLLFD_GROW; i < priv->npollfds; i++)
188 			pollfds[i].fd = -1;
189 		priv->pollfds = pollfds;
190 
191 		i = priv->npollfds - POLLFD_GROW;
192 	}
193 	pollfd = &priv->pollfds[i];
194 	pollfd->fd = fd;
195 	pollfd->events = slirpev2pollev(events);
196 	pollfd->revents = 0;
197 	priv->lastpollfd = i;
198 
199 	return ((int)i);
200 }
201 
202 static int
slirp_addpoll_cb(int fd,int events,void * param)203 slirp_addpoll_cb(int fd, int events, void *param)
204 {
205 	struct slirp_priv *priv;
206 
207 	priv = param;
208 	return (slirp_addpoll(priv, fd, events));
209 }
210 
211 static int
slirp_poll_revents(int idx,void * param)212 slirp_poll_revents(int idx, void *param)
213 {
214 	struct slirp_priv *priv;
215 	struct pollfd *pollfd;
216 	short revents;
217 
218 	priv = param;
219 	assert(idx >= 0);
220 	assert((unsigned int)idx < priv->npollfds);
221 	pollfd = &priv->pollfds[idx];
222 	assert(pollfd->fd != -1);
223 
224 	/* The kernel may report POLLHUP even if we didn't ask for it. */
225 	revents = pollfd->revents;
226 	if ((pollfd->events & POLLHUP) == 0)
227 		revents &= ~POLLHUP;
228 	return (pollev2slirpev(revents));
229 }
230 
231 /*
232  * Main loop.  Poll libslirp's descriptors plus a couple of our own.
233  */
234 static void
slirp_pollfd_loop(struct slirp_priv * priv)235 slirp_pollfd_loop(struct slirp_priv *priv)
236 {
237 	struct pollfd *pollfds;
238 	size_t npollfds;
239 	uint32_t timeout;
240 	int error;
241 
242 	for (;;) {
243 		int input, wakeup;
244 
245 		for (size_t i = 0; i < priv->npollfds; i++)
246 			priv->pollfds[i].fd = -1;
247 		priv->lastpollfd = -1;
248 
249 		/* Register for notifications from slirp_cb_notify(). */
250 		wakeup = slirp_addpoll(priv, priv->wakeup[0], POLLIN);
251 		/* Register for input from our parent process. */
252 		input = slirp_addpoll(priv, priv->sock, POLLIN | POLLRDHUP);
253 
254 		timeout = UINT32_MAX;
255 		slirp_pollfds_fill_p(priv->slirp, &timeout, slirp_addpoll_cb,
256 		    priv);
257 
258 		pollfds = priv->pollfds;
259 		npollfds = priv->npollfds;
260 		error = poll(pollfds, npollfds, timeout);
261 		if (error == -1 && errno != EINTR)
262 			err(1, "poll");
263 		slirp_pollfds_poll_p(priv->slirp, error == -1,
264 		    slirp_poll_revents, priv);
265 
266 		/*
267 		 * If we were woken up by the notify callback, mask the
268 		 * interrupt.
269 		 */
270 		if ((pollfds[wakeup].revents & POLLIN) != 0) {
271 			ssize_t n;
272 
273 			do {
274 				uint8_t b;
275 
276 				n = read(priv->wakeup[0], &b, 1);
277 			} while (n == 1);
278 			if (n != -1 || errno != EAGAIN)
279 				err(1, "read");
280 		}
281 
282 		/*
283 		 * If new packets arrived from our parent, feed them to
284 		 * libslirp.
285 		 */
286 		if ((pollfds[input].revents & (POLLHUP | POLLRDHUP)) != 0)
287 			errx(1, "parent process closed connection");
288 		if ((pollfds[input].revents & POLLIN) != 0) {
289 			ssize_t n;
290 
291 			do {
292 				uint8_t buf[SLIRP_MTU];
293 
294 				n = recv(priv->sock, buf, sizeof(buf),
295 				    MSG_DONTWAIT);
296 				if (n < 0) {
297 					if (errno == EWOULDBLOCK)
298 						break;
299 					err(1, "recv");
300 				}
301 				slirp_input_p(priv->slirp, buf, (int)n);
302 			} while (n >= 0);
303 		}
304 	}
305 }
306 
307 static int
parse_addr(char * addr,struct sockaddr_in * sinp)308 parse_addr(char *addr, struct sockaddr_in *sinp)
309 {
310 	char *port;
311 	int error, porti;
312 
313 	memset(sinp, 0, sizeof(*sinp));
314 	sinp->sin_family = AF_INET;
315 	sinp->sin_len = sizeof(struct sockaddr_in);
316 
317 	port = strchr(addr, ':');
318 	if (port == NULL)
319 		return (EINVAL);
320 	*port++ = '\0';
321 
322 	if (strlen(addr) > 0) {
323 		error = inet_pton(AF_INET, addr, &sinp->sin_addr);
324 		if (error != 1)
325 			return (error == 0 ? EPFNOSUPPORT : errno);
326 	} else {
327 		sinp->sin_addr.s_addr = htonl(INADDR_ANY);
328 	}
329 
330 	porti = strlen(port) > 0 ? atoi(port) : 0;
331 	if (porti < 0 || porti > UINT16_MAX)
332 		return (EINVAL);
333 	sinp->sin_port = htons(porti);
334 
335 	return (0);
336 }
337 
338 static int
parse_hostfwd_rule(const char * descr,int * is_udp,struct sockaddr * hostaddr,struct sockaddr * guestaddr)339 parse_hostfwd_rule(const char *descr, int *is_udp, struct sockaddr *hostaddr,
340     struct sockaddr *guestaddr)
341 {
342 	struct sockaddr_in *hostaddrp, *guestaddrp;
343 	const char *proto;
344 	char *p, *host, *guest;
345 	int error;
346 
347 	error = 0;
348 	*is_udp = 0;
349 
350 	p = strdup(descr);
351 	if (p == NULL)
352 		return (ENOMEM);
353 
354 	host = strchr(p, ':');
355 	if (host == NULL) {
356 		error = EINVAL;
357 		goto out;
358 	}
359 	*host++ = '\0';
360 
361 	proto = p;
362 	*is_udp = strcmp(proto, "udp") == 0;
363 
364 	guest = strchr(host, '-');
365 	if (guest == NULL) {
366 		error = EINVAL;
367 		goto out;
368 	}
369 	*guest++ = '\0';
370 
371 	hostaddrp = (struct sockaddr_in *)(void *)hostaddr;
372 	error = parse_addr(host, hostaddrp);
373 	if (error != 0)
374 		goto out;
375 
376 	guestaddrp = (struct sockaddr_in *)(void *)guestaddr;
377 	error = parse_addr(guest, guestaddrp);
378 	if (error != 0)
379 		goto out;
380 
381 out:
382 	free(p);
383 	return (error);
384 }
385 
386 static void
config_one_hostfwd(Slirp * slirp,const char * rule)387 config_one_hostfwd(Slirp *slirp, const char *rule)
388 {
389 	struct sockaddr hostaddr, guestaddr;
390 	int error, is_udp;
391 
392 	error = parse_hostfwd_rule(rule, &is_udp, &hostaddr, &guestaddr);
393 	if (error != 0)
394 		errx(1, "unable to parse hostfwd rule '%s': %s", rule,
395 		    strerror(error));
396 
397 	error = slirp_add_hostxfwd_p(slirp, &hostaddr, hostaddr.sa_len,
398 	    &guestaddr, guestaddr.sa_len, is_udp ? SLIRP_HOSTFWD_UDP : 0);
399 	if (error != 0)
400 		errx(1, "Unable to add hostfwd rule '%s': %s", rule,
401 		    strerror(errno));
402 }
403 
404 /*
405  * Drop privileges to the "nobody" user.  Ideally we'd chroot to somewhere like
406  * /var/empty but libslirp might need to access /etc/resolv.conf.
407  */
408 static void
drop_privs(void)409 drop_privs(void)
410 {
411 	struct passwd *pw;
412 
413 	if (geteuid() != 0)
414 		return;
415 
416 	pw = getpwnam("nobody");
417 	if (pw == NULL)
418 		err(1, "getpwnam(nobody) failed");
419 	if (initgroups(pw->pw_name, pw->pw_gid) != 0)
420 		err(1, "initgroups");
421 	if (setgid(pw->pw_gid) != 0)
422 		err(1, "setgid");
423 	if (setuid(pw->pw_uid) != 0)
424 		err(1, "setuid");
425 }
426 
427 static void
libslirp_init(void)428 libslirp_init(void)
429 {
430 	void *handle;
431 
432 	handle = dlopen("libslirp.so.0", RTLD_LAZY);
433 	if (handle == NULL)
434 		errx(1, "unable to open libslirp.so.0: %s", dlerror());
435 
436 #define IMPORT_SYM(sym) do {					\
437 	sym##_p = (sym##_p_t)dlsym(handle, #sym);		\
438 	if (sym##_p == NULL)					\
439 		errx(1, "failed to resolve %s", #sym);		\
440 } while (0)
441 	IMPORT_SYM(slirp_add_hostxfwd);
442 	IMPORT_SYM(slirp_cleanup);
443 	IMPORT_SYM(slirp_input);
444 	IMPORT_SYM(slirp_new);
445 	IMPORT_SYM(slirp_pollfds_fill);
446 	IMPORT_SYM(slirp_pollfds_poll);
447 #undef IMPORT_SYM
448 }
449 
450 static void
usage(void)451 usage(void)
452 {
453 	fprintf(stderr, "Usage: slirp-helper -S <socket>\n");
454 	exit(1);
455 }
456 
457 int
main(int argc,char ** argv)458 main(int argc, char **argv)
459 {
460 	struct slirp_priv priv;
461 	SlirpConfig slirpconfig;
462 	Slirp *slirp;
463 	nvlist_t *config;
464 	const char *hostfwd, *vmname;
465 	int ch, fd, sd;
466 	bool restricted;
467 
468 	sd = -1;
469 	while ((ch = getopt(argc, argv, "S:")) != -1) {
470 		switch (ch) {
471 		case 'S':
472 			sd = atoi(optarg);
473 			if (fcntl(sd, F_GETFD) == -1)
474 				err(1, "invalid socket %s", optarg);
475 			break;
476 		default:
477 			usage();
478 			/* NOTREACHED */
479 		}
480 	}
481 	argc -= optind;
482 	argv += optind;
483 
484 	if (sd == -1)
485 		usage();
486 
487 	/*
488 	 * Clean the fd space: point stdio to /dev/null and keep our socket.
489 	 */
490 	fd = open("/dev/null", O_RDWR);
491 	if (fd == -1)
492 		err(1, "open(/dev/null)");
493 	if (dup2(fd, STDIN_FILENO) == -1)
494 		err(1, "dup2(stdin)");
495 	if (dup2(fd, STDOUT_FILENO) == -1)
496 		err(1, "dup2(stdout)");
497 	if (dup2(fd, STDERR_FILENO) == -1)
498 		err(1, "dup2(stderr)");
499 	if (dup2(sd, 3) == -1)
500 		err(1, "dup2(slirp socket)");
501 	sd = 3;
502 	closefrom(sd + 1);
503 
504 	memset(&priv, 0, sizeof(priv));
505 	priv.sock = sd;
506 	if (pipe2(priv.wakeup, O_CLOEXEC | O_NONBLOCK) != 0)
507 		err(1, "pipe2");
508 
509 	/*
510 	 * Apply the configuration we received from bhyve.
511 	 */
512 	config = nvlist_recv(sd, 0);
513 	if (config == NULL)
514 		err(1, "nvlist_recv");
515 	vmname = get_config_value_node(config, "vmname");
516 	if (vmname != NULL)
517 		setproctitle("%s", vmname);
518 	restricted = !get_config_bool_node_default(config, "open", false);
519 
520 	slirpconfig = (SlirpConfig){
521 		.version = 4,
522 		.if_mtu = SLIRP_MTU,
523 		.restricted = restricted,
524 		.in_enabled = true,
525 		.vnetwork.s_addr = htonl(0x0a000200),	/* 10.0.2.0/24 */
526 		.vnetmask.s_addr = htonl(0xffffff00),	/* 255.255.255.0 */
527 		.vdhcp_start.s_addr = htonl(0x0a00020f),/* 10.0.2.15 */
528 		.vhost.s_addr = htonl(0x0a000202),	/* 10.0.2.2 */
529 		.vnameserver.s_addr = htonl(0x0a000203),/* 10.0.2.3 */
530 		.enable_emu = false,
531 	};
532 	libslirp_init();
533 	slirp = slirp_new_p(&slirpconfig, &slirp_cbs, &priv);
534 
535 	hostfwd = get_config_value_node(config, "hostfwd");
536 	if (hostfwd != NULL) {
537 		char *rules, *tofree;
538 		const char *rule;
539 
540 		tofree = rules = strdup(hostfwd);
541 		if (rules == NULL)
542 			err(1, "strdup");
543 		while ((rule = strsep(&rules, ";")) != NULL)
544 			config_one_hostfwd(slirp, rule);
545 		free(tofree);
546 	}
547 
548 	priv.slirp = slirp;
549 
550 	/*
551 	 * In restricted mode, we can enter a Capsicum sandbox without losing
552 	 * functionality.
553 	 */
554 	if (restricted && caph_enter() != 0)
555 		err(1, "caph_enter");
556 
557 	/*
558 	 * Drop root privileges if we have them.
559 	 */
560 	drop_privs();
561 
562 	/*
563 	 * Enter our main loop.  If bhyve goes away, we should observe a hangup
564 	 * on the socket and exit.
565 	 */
566 	slirp_pollfd_loop(&priv);
567 	/* NOTREACHED */
568 
569 	return (1);
570 }
571