git: b7390f6f8412 - stable/13 - bhyveload: hold /boot and do relative lookups for the loader

From: Kyle Evans <kevans_at_FreeBSD.org>
Date: Mon, 22 Jan 2024 17:30:02 UTC
The branch stable/13 has been updated by kevans:

URL: https://cgit.FreeBSD.org/src/commit/?id=b7390f6f8412fe46413b7fba6d3e74709d8e21e2

commit b7390f6f8412fe46413b7fba6d3e74709d8e21e2
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2024-01-03 22:17:59 +0000
Commit:     Kyle Evans <kevans@FreeBSD.org>
CommitDate: 2024-01-22 17:17:48 +0000

    bhyveload: hold /boot and do relative lookups for the loader
    
    The next change will push bhyveload into capability mode right after we
    allocate vcpu state, before we've setup or entered the loader, to limit
    the surface area that a rogue loader script can touch.
    
    With an explicit -l loader, we don't need to preopen /boot because
    changing interpreters isn't allowed.  We'll just dlopen() entirely in
    advance in that case to eliminate some complexity.
    
    Reviewed by:    allanjude (earlier version), markj
    
    (cherry picked from commit bf7c4fcbbb05ff99afde0744d013feeb35d77191)
    (cherry picked from commit 67082f077f39d9c7b7bd561c14622e6f3ef23681)
---
 usr.sbin/bhyveload/bhyveload.c | 73 +++++++++++++++++++++++++++++-------------
 1 file changed, 51 insertions(+), 22 deletions(-)

diff --git a/usr.sbin/bhyveload/bhyveload.c b/usr.sbin/bhyveload/bhyveload.c
index 797f8b88dad2..c11b1814cfc7 100644
--- a/usr.sbin/bhyveload/bhyveload.c
+++ b/usr.sbin/bhyveload/bhyveload.c
@@ -94,11 +94,9 @@ static int ndisks;
 static int consin_fd, consout_fd;
 static int hostbase_fd = -1;
 
-static int need_reinit;
-
 static void *loader_hdl;
 static char *loader;
-static int explicit_loader;
+static int explicit_loader_fd = -1;
 static jmp_buf jb;
 
 static char *vmname, *progname;
@@ -615,7 +613,7 @@ cb_swap_interpreter(void *arg __unused, const char *interp_req)
 	 * not try to pivot to a different loader on them.
 	 */
 	free(loader);
-	if (explicit_loader == 1) {
+	if (explicit_loader_fd != -1) {
 		perror("requested loader interpreter does not match guest userboot");
 		cb_exit(NULL, 1);
 	}
@@ -624,9 +622,8 @@ cb_swap_interpreter(void *arg __unused, const char *interp_req)
 		cb_exit(NULL, 1);
 	}
 
-	if (asprintf(&loader, "/boot/userboot_%s.so", interp_req) == -1)
+	if (asprintf(&loader, "userboot_%s.so", interp_req) == -1)
 		err(EX_OSERR, "malloc");
-	need_reinit = 1;
 	longjmp(jb, 1);
 }
 
@@ -741,13 +738,38 @@ hostbase_open(const char *base)
 		err(EX_OSERR, "open");
 }
 
+static void
+loader_open(int bootfd)
+{
+	int fd;
+
+	if (loader == NULL) {
+		loader = strdup("userboot.so");
+		if (loader == NULL)
+			err(EX_OSERR, "malloc");
+	}
+
+	assert(bootfd >= 0 || explicit_loader_fd >= 0);
+	if (explicit_loader_fd >= 0)
+		fd = explicit_loader_fd;
+	else
+		fd = openat(bootfd, loader, O_RDONLY | O_RESOLVE_BENEATH);
+	if (fd == -1)
+		err(EX_OSERR, "openat");
+
+	loader_hdl = fdlopen(fd, RTLD_LOCAL);
+	if (!loader_hdl)
+		errx(EX_OSERR, "dlopen: %s", dlerror());
+}
+
 int
 main(int argc, char** argv)
 {
 	void (*func)(struct loader_callbacks *, void *, int, int);
 	uint64_t mem_size;
-	int opt, error, memflags;
+	int bootfd, opt, error, memflags, need_reinit;
 
+	bootfd = -1;
 	progname = basename(argv[0]);
 
 	memflags = 0;
@@ -784,7 +806,9 @@ main(int argc, char** argv)
 			loader = strdup(optarg);
 			if (loader == NULL)
 				err(EX_OSERR, "malloc");
-			explicit_loader = 1;
+			explicit_loader_fd = open(loader, O_RDONLY);
+			if (explicit_loader_fd == -1)
+				err(EX_OSERR, "%s", loader);
 			break;
 
 		case 'm':
@@ -827,12 +851,29 @@ main(int argc, char** argv)
 		exit(1);
 	}
 
+	/*
+	 * If we weren't given an explicit loader to use, we need to support the
+	 * guest requesting a different one.
+	 */
+	if (explicit_loader_fd == -1) {
+		bootfd = open("/boot", O_DIRECTORY | O_PATH);
+		if (bootfd == -1) {
+			perror("open");
+			exit(1);
+		}
+	}
+
 	/*
 	 * setjmp in the case the guest wants to swap out interpreter,
 	 * cb_swap_interpreter will swap out loader as appropriate and set
 	 * need_reinit so that we end up in a clean state once again.
 	 */
-	setjmp(jb);
+	if (setjmp(jb) != 0) {
+		dlclose(loader_hdl);
+		loader_hdl = NULL;
+
+		need_reinit = 1;
+	}
 
 	if (need_reinit) {
 		error = vm_reinit(ctx);
@@ -849,19 +890,7 @@ main(int argc, char** argv)
 		exit(1);
 	}
 
-	if (loader == NULL) {
-		loader = strdup("/boot/userboot.so");
-		if (loader == NULL)
-			err(EX_OSERR, "malloc");
-	}
-	if (loader_hdl != NULL)
-		dlclose(loader_hdl);
-	loader_hdl = dlopen(loader, RTLD_LOCAL);
-	if (!loader_hdl) {
-		printf("%s\n", dlerror());
-		free(loader);
-		return (1);
-	}
+	loader_open(bootfd);
 	func = dlsym(loader_hdl, "loader_main");
 	if (!func) {
 		printf("%s\n", dlerror());