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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 451 usage(void) 452 { 453 fprintf(stderr, "Usage: slirp-helper -S <socket>\n"); 454 exit(1); 455 } 456 457 int 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