1 /*- 2 * Copyright (c) 2011 Google, Inc. 3 * Copyright (c) 2023-2024 Juniper Networks, Inc. 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 #include <sys/types.h> 29 #include <sys/disk.h> 30 #include <sys/ioctl.h> 31 #include <sys/stat.h> 32 #include <dirent.h> 33 #include <dlfcn.h> 34 #include <err.h> 35 #include <errno.h> 36 #include <fcntl.h> 37 #include <getopt.h> 38 #include <inttypes.h> 39 #include <libgen.h> 40 #include <limits.h> 41 #include <stdbool.h> 42 #include <stdio.h> 43 #include <stdlib.h> 44 #include <string.h> 45 #include <termios.h> 46 #include <unistd.h> 47 48 #include <userboot.h> 49 50 char **vars; 51 52 char *host_base = NULL; 53 struct termios term, oldterm; 54 char *image; 55 size_t image_size; 56 57 uint64_t regs[16]; 58 uint64_t pc; 59 int *disk_fd; 60 int disk_index = -1; 61 62 void test_exit(void *arg, int v); 63 64 /* 65 * Console i/o 66 */ 67 68 void 69 test_putc(void *arg, int ch) 70 { 71 char c = ch; 72 73 write(1, &c, 1); 74 } 75 76 int 77 test_getc(void *arg) 78 { 79 char c; 80 81 if (read(0, &c, 1) == 1) 82 return c; 83 return -1; 84 } 85 86 int 87 test_poll(void *arg) 88 { 89 int n; 90 91 if (ioctl(0, FIONREAD, &n) >= 0) 92 return (n > 0); 93 return (0); 94 } 95 96 /* 97 * Host filesystem i/o 98 */ 99 100 struct test_file { 101 int tf_isdir; 102 size_t tf_size; 103 struct stat tf_stat; 104 union { 105 int fd; 106 DIR *dir; 107 } tf_u; 108 }; 109 110 static int 111 test_open_internal(void *arg, const char *filename, void **h_return, 112 struct test_file *tf, int depth) 113 { 114 char path[PATH_MAX]; 115 char linkpath[PATH_MAX]; 116 char *component, *cp, *linkptr; 117 ssize_t slen; 118 int comp_fd, dir_fd, error; 119 char c; 120 bool openbase; 121 122 if (depth++ >= MAXSYMLINKS) 123 return (ELOOP); 124 125 openbase = false; 126 error = EINVAL; 127 if (tf == NULL) { 128 tf = calloc(1, sizeof(struct test_file)); 129 if (tf == NULL) 130 return (error); 131 openbase = true; 132 } else if (tf->tf_isdir) { 133 if (filename[0] == '/') { 134 closedir(tf->tf_u.dir); 135 openbase = true; 136 } 137 } else 138 return (error); 139 140 if (openbase) { 141 dir_fd = open(host_base, O_RDONLY); 142 if (dir_fd < 0) 143 goto out; 144 145 tf->tf_isdir = 1; 146 tf->tf_u.dir = fdopendir(dir_fd); 147 148 if (fstat(dir_fd, &tf->tf_stat) < 0) { 149 error = errno; 150 goto out; 151 } 152 tf->tf_size = tf->tf_stat.st_size; 153 } 154 155 strlcpy(path, filename, sizeof(path)); 156 cp = path; 157 while (*cp) { 158 /* 159 * The test file should be a directory at this point. 160 * If it is not, then the caller provided an invalid filename. 161 */ 162 if (!tf->tf_isdir) 163 goto out; 164 165 /* Trim leading slashes */ 166 while (*cp == '/') 167 cp++; 168 169 /* If we reached the end, we are done */ 170 if (*cp == '\0') 171 break; 172 173 /* Get the file descriptor for the directory */ 174 dir_fd = dirfd(tf->tf_u.dir); 175 176 /* Get the next component path */ 177 component = cp; 178 while ((c = *cp) != '\0' && c != '/') 179 cp++; 180 if (c == '/') 181 *cp++ = '\0'; 182 183 /* Get status of the component */ 184 if (fstatat(dir_fd, component, &tf->tf_stat, 185 AT_SYMLINK_NOFOLLOW) < 0) { 186 error = errno; 187 goto out; 188 } 189 tf->tf_size = tf->tf_stat.st_size; 190 191 /* 192 * Check that the path component is a directory, regular file, 193 * or a symlink. 194 */ 195 if (!S_ISDIR(tf->tf_stat.st_mode) && 196 !S_ISREG(tf->tf_stat.st_mode) && 197 !S_ISLNK(tf->tf_stat.st_mode)) 198 goto out; 199 200 /* For anything that is not a symlink, open it */ 201 if (!S_ISLNK(tf->tf_stat.st_mode)) { 202 comp_fd = openat(dir_fd, component, O_RDONLY); 203 if (comp_fd < 0) 204 goto out; 205 } 206 207 if (S_ISDIR(tf->tf_stat.st_mode)) { 208 /* Directory */ 209 210 /* close the parent directory */ 211 closedir(tf->tf_u.dir); 212 213 /* Open the directory from the component descriptor */ 214 tf->tf_isdir = 1; 215 tf->tf_u.dir = fdopendir(comp_fd); 216 if (!tf->tf_u.dir) 217 goto out; 218 } else if (S_ISREG(tf->tf_stat.st_mode)) { 219 /* Regular file */ 220 221 /* close the parent directory */ 222 closedir(tf->tf_u.dir); 223 224 /* Stash the component descriptor */ 225 tf->tf_isdir = 0; 226 tf->tf_u.fd = comp_fd; 227 } else if (S_ISLNK(tf->tf_stat.st_mode)) { 228 /* Symlink */ 229 230 /* Read what the symlink points to */ 231 slen = readlinkat(dir_fd, component, linkpath, 232 sizeof(linkpath)); 233 if (slen < 0) 234 goto out; 235 /* NUL-terminate the string */ 236 linkpath[(size_t)slen] = '\0'; 237 238 /* Open the thing that the symlink points to */ 239 error = test_open_internal(arg, linkpath, NULL, 240 tf, depth); 241 if (error != 0) 242 goto out; 243 } 244 } 245 246 /* Completed the entire path and have a good file/directory */ 247 if (h_return != NULL) 248 *h_return = tf; 249 return (0); 250 251 out: 252 /* Failure of some sort, clean up */ 253 if (tf->tf_isdir) 254 closedir(tf->tf_u.dir); 255 else 256 close(tf->tf_u.fd); 257 free(tf); 258 return (error); 259 } 260 261 int 262 test_open(void *arg, const char *filename, void **h_return) 263 { 264 if (host_base == NULL) 265 return (ENOENT); 266 267 return (test_open_internal(arg, filename, h_return, NULL, 0)); 268 } 269 270 int 271 test_close(void *arg, void *h) 272 { 273 struct test_file *tf = h; 274 275 if (tf->tf_isdir) 276 closedir(tf->tf_u.dir); 277 else 278 close(tf->tf_u.fd); 279 free(tf); 280 281 return (0); 282 } 283 284 int 285 test_isdir(void *arg, void *h) 286 { 287 struct test_file *tf = h; 288 289 return (tf->tf_isdir); 290 } 291 292 int 293 test_read(void *arg, void *h, void *dst, size_t size, size_t *resid_return) 294 { 295 struct test_file *tf = h; 296 ssize_t sz; 297 298 if (tf->tf_isdir) 299 return (EINVAL); 300 sz = read(tf->tf_u.fd, dst, size); 301 if (sz < 0) 302 return (EINVAL); 303 *resid_return = size - sz; 304 return (0); 305 } 306 307 int 308 test_readdir(void *arg, void *h, uint32_t *fileno_return, uint8_t *type_return, 309 size_t *namelen_return, char *name) 310 { 311 struct test_file *tf = h; 312 struct dirent *dp; 313 314 if (!tf->tf_isdir) 315 return (EINVAL); 316 317 dp = readdir(tf->tf_u.dir); 318 if (!dp) 319 return (ENOENT); 320 321 /* 322 * Note: d_namlen is in the range 0..255 and therefore less 323 * than PATH_MAX so we don't need to test before copying. 324 */ 325 *fileno_return = dp->d_fileno; 326 *type_return = dp->d_type; 327 *namelen_return = dp->d_namlen; 328 memcpy(name, dp->d_name, dp->d_namlen); 329 name[dp->d_namlen] = 0; 330 331 return (0); 332 } 333 334 int 335 test_seek(void *arg, void *h, uint64_t offset, int whence) 336 { 337 struct test_file *tf = h; 338 339 if (tf->tf_isdir) 340 return (EINVAL); 341 if (lseek(tf->tf_u.fd, offset, whence) < 0) 342 return (errno); 343 return (0); 344 } 345 346 int 347 test_stat(void *arg, void *h, struct stat *stp) 348 { 349 struct test_file *tf = h; 350 351 if (!stp) 352 return (-1); 353 memset(stp, 0, sizeof(struct stat)); 354 stp->st_mode = tf->tf_stat.st_mode; 355 stp->st_uid = tf->tf_stat.st_uid; 356 stp->st_gid = tf->tf_stat.st_gid; 357 stp->st_size = tf->tf_stat.st_size; 358 stp->st_ino = tf->tf_stat.st_ino; 359 stp->st_dev = tf->tf_stat.st_dev; 360 stp->st_mtime = tf->tf_stat.st_mtime; 361 return (0); 362 } 363 364 /* 365 * Disk image i/o 366 */ 367 368 int 369 test_diskread(void *arg, int unit, uint64_t offset, void *dst, size_t size, 370 size_t *resid_return) 371 { 372 ssize_t n; 373 374 if (unit > disk_index || disk_fd[unit] == -1) 375 return (EIO); 376 n = pread(disk_fd[unit], dst, size, offset); 377 if (n == 0) { 378 printf("%s: end of disk (%ju)\n", __func__, (intmax_t)offset); 379 return (EIO); 380 } 381 382 if (n < 0) 383 return (errno); 384 *resid_return = size - n; 385 return (0); 386 } 387 388 int 389 test_diskwrite(void *arg, int unit, uint64_t offset, void *src, size_t size, 390 size_t *resid_return) 391 { 392 ssize_t n; 393 394 if (unit > disk_index || disk_fd[unit] == -1) 395 return (EIO); 396 n = pwrite(disk_fd[unit], src, size, offset); 397 if (n < 0) 398 return (errno); 399 *resid_return = size - n; 400 return (0); 401 } 402 403 int 404 test_diskioctl(void *arg, int unit, u_long cmd, void *data) 405 { 406 struct stat sb; 407 408 if (unit > disk_index || disk_fd[unit] == -1) 409 return (EBADF); 410 switch (cmd) { 411 case DIOCGSECTORSIZE: 412 *(u_int *)data = 512; 413 break; 414 case DIOCGMEDIASIZE: 415 if (fstat(disk_fd[unit], &sb) == 0) 416 *(off_t *)data = sb.st_size; 417 else 418 return (ENOTTY); 419 break; 420 default: 421 return (ENOTTY); 422 } 423 return (0); 424 } 425 426 /* 427 * Guest virtual machine i/o 428 * 429 * Note: guest addresses are kernel virtual 430 */ 431 432 int 433 test_copyin(void *arg, const void *from, uint64_t to, size_t size) 434 { 435 436 to &= 0x7fffffff; 437 if (to > image_size) 438 return (EFAULT); 439 if (to + size > image_size) 440 size = image_size - to; 441 memcpy(&image[to], from, size); 442 return(0); 443 } 444 445 int 446 test_copyout(void *arg, uint64_t from, void *to, size_t size) 447 { 448 449 from &= 0x7fffffff; 450 if (from > image_size) 451 return (EFAULT); 452 if (from + size > image_size) 453 size = image_size - from; 454 memcpy(to, &image[from], size); 455 return(0); 456 } 457 458 void 459 test_setreg(void *arg, int r, uint64_t v) 460 { 461 462 if (r < 0 || r >= 16) 463 return; 464 regs[r] = v; 465 } 466 467 void 468 test_setmsr(void *arg, int r, uint64_t v) 469 { 470 } 471 472 void 473 test_setcr(void *arg, int r, uint64_t v) 474 { 475 } 476 477 void 478 test_setgdt(void *arg, uint64_t v, size_t sz) 479 { 480 } 481 482 void 483 test_exec(void *arg, uint64_t pc) 484 { 485 printf("Execute at 0x%"PRIx64"\n", pc); 486 test_exit(arg, 0); 487 } 488 489 /* 490 * Misc 491 */ 492 493 void 494 test_delay(void *arg, int usec) 495 { 496 497 usleep(usec); 498 } 499 500 void 501 test_exit(void *arg, int v) 502 { 503 504 tcsetattr(0, TCSAFLUSH, &oldterm); 505 exit(v); 506 } 507 508 void 509 test_getmem(void *arg, uint64_t *lowmem, uint64_t *highmem) 510 { 511 512 *lowmem = 128*1024*1024; 513 *highmem = 0; 514 } 515 516 char * 517 test_getenv(void *arg, int idx) 518 { 519 static char *myvars[] = { 520 "USERBOOT=1" 521 }; 522 static const int num_myvars = nitems(myvars); 523 524 if (idx < num_myvars) 525 return (myvars[idx]); 526 else 527 return (vars[idx - num_myvars]); 528 } 529 530 struct loader_callbacks cb = { 531 .putc = test_putc, 532 .getc = test_getc, 533 .poll = test_poll, 534 535 .open = test_open, 536 .close = test_close, 537 .isdir = test_isdir, 538 .read = test_read, 539 .readdir = test_readdir, 540 .seek = test_seek, 541 .stat = test_stat, 542 543 .diskread = test_diskread, 544 .diskwrite = test_diskwrite, 545 .diskioctl = test_diskioctl, 546 547 .copyin = test_copyin, 548 .copyout = test_copyout, 549 .setreg = test_setreg, 550 .setmsr = test_setmsr, 551 .setcr = test_setcr, 552 .setgdt = test_setgdt, 553 .exec = test_exec, 554 555 .delay = test_delay, 556 .exit = test_exit, 557 .getmem = test_getmem, 558 559 .getenv = test_getenv, 560 }; 561 562 void 563 usage() 564 { 565 566 printf("usage: [-b <userboot shared object>] [-d <disk image path>] [-h <host filesystem path>\n"); 567 exit(1); 568 } 569 570 int 571 main(int argc, char** argv, char ** environment) 572 { 573 void *h; 574 void (*func)(struct loader_callbacks *, void *, int, int) __dead2; 575 int opt; 576 const char *userboot_obj = "/boot/userboot.so"; 577 int oflag = O_RDONLY; 578 579 vars = environment; 580 581 while ((opt = getopt(argc, argv, "wb:d:h:")) != -1) { 582 switch (opt) { 583 case 'b': 584 userboot_obj = optarg; 585 break; 586 587 case 'd': 588 disk_index++; 589 disk_fd = reallocarray(disk_fd, disk_index + 1, 590 sizeof (int)); 591 disk_fd[disk_index] = open(optarg, oflag); 592 if (disk_fd[disk_index] < 0) 593 err(1, "Can't open disk image '%s'", optarg); 594 break; 595 596 case 'h': 597 host_base = optarg; 598 break; 599 600 case 'w': 601 oflag = O_RDWR; 602 break; 603 604 case '?': 605 usage(); 606 } 607 } 608 609 h = dlopen(userboot_obj, RTLD_LOCAL); 610 if (!h) { 611 printf("%s\n", dlerror()); 612 return (1); 613 } 614 func = dlsym(h, "loader_main"); 615 if (!func) { 616 printf("%s\n", dlerror()); 617 return (1); 618 } 619 620 image_size = 128*1024*1024; 621 image = malloc(image_size); 622 623 tcgetattr(0, &term); 624 oldterm = term; 625 term.c_iflag &= ~(ICRNL); 626 term.c_lflag &= ~(ICANON|ECHO); 627 tcsetattr(0, TCSAFLUSH, &term); 628 629 func(&cb, NULL, USERBOOT_VERSION_3, disk_index + 1); 630 } 631