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
test_putc(void * arg,int ch)69 test_putc(void *arg, int ch)
70 {
71 char c = ch;
72
73 write(1, &c, 1);
74 }
75
76 int
test_getc(void * arg)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
test_poll(void * arg)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
test_open_internal(void * arg,const char * filename,void ** h_return,struct test_file * tf,int depth)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
test_open(void * arg,const char * filename,void ** h_return)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
test_close(void * arg,void * h)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
test_isdir(void * arg,void * h)285 test_isdir(void *arg, void *h)
286 {
287 struct test_file *tf = h;
288
289 return (tf->tf_isdir);
290 }
291
292 int
test_read(void * arg,void * h,void * dst,size_t size,size_t * resid_return)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
test_readdir(void * arg,void * h,uint32_t * fileno_return,uint8_t * type_return,size_t * namelen_return,char * name)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
test_seek(void * arg,void * h,uint64_t offset,int whence)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
test_stat(void * arg,void * h,struct stat * stp)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
test_diskread(void * arg,int unit,uint64_t offset,void * dst,size_t size,size_t * resid_return)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
test_diskwrite(void * arg,int unit,uint64_t offset,void * src,size_t size,size_t * resid_return)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
test_diskioctl(void * arg,int unit,u_long cmd,void * data)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
test_copyin(void * arg,const void * from,uint64_t to,size_t size)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
test_copyout(void * arg,uint64_t from,void * to,size_t size)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
test_setreg(void * arg,int r,uint64_t v)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
test_setmsr(void * arg,int r,uint64_t v)468 test_setmsr(void *arg, int r, uint64_t v)
469 {
470 }
471
472 void
test_setcr(void * arg,int r,uint64_t v)473 test_setcr(void *arg, int r, uint64_t v)
474 {
475 }
476
477 void
test_setgdt(void * arg,uint64_t v,size_t sz)478 test_setgdt(void *arg, uint64_t v, size_t sz)
479 {
480 }
481
482 void
test_exec(void * arg,uint64_t pc)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
test_delay(void * arg,int usec)494 test_delay(void *arg, int usec)
495 {
496
497 usleep(usec);
498 }
499
500 void
test_exit(void * arg,int v)501 test_exit(void *arg, int v)
502 {
503
504 tcsetattr(0, TCSAFLUSH, &oldterm);
505 exit(v);
506 }
507
508 void
test_getmem(void * arg,uint64_t * lowmem,uint64_t * highmem)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 *
test_getenv(void * arg,int idx)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
usage()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
main(int argc,char ** argv,char ** environment)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