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