git: 48598b1670ce - releng/13.2 - bhyveload: use a dirfd to support -h

From: Gordon Tetlow <gordon_at_FreeBSD.org>
Date: Wed, 14 Feb 2024 06:06:00 UTC
The branch releng/13.2 has been updated by gordon:

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

commit 48598b1670ce542419420f969d5a5bd47167eb17
Author:     Kyle Evans <kevans@FreeBSD.org>
AuthorDate: 2024-01-03 22:17:59 +0000
Commit:     Gordon Tetlow <gordon@FreeBSD.org>
CommitDate: 2024-02-14 05:43:15 +0000

    bhyveload: use a dirfd to support -h
    
    Don't allow lookups from the loader scripts, which in rare cases may be
    in guest control depending on the setup, to leave the specified host
    root.  Open the root dir and strictly do RESOLVE_BENEATH lookups from
    there.
    
    cb_open() has been restructured a bit to work nicely with this, using
    fdopendir() in the directory case and just using the fd we already
    opened in the regular file case.
    
    hostbase_open() was split out to provide an obvious place to apply
    rights(4) if that's something we care to do.
    
    Reviewed by:    allanjude (earlier version), markj
    Approved by:    so
    Security:       FreeBSD-SA-24:01.bhyveload
    Security:       CVE-2024-25940
    
    (cherry picked from commit 6779d44bd878e3cf4723f7386b11da6508ab5431)
    (cherry picked from commit 78345dbd7a004e0a6d1b717e7dbc758ae67ca293)
---
 usr.sbin/bhyveload/bhyveload.c | 85 ++++++++++++++++++++++++++++--------------
 1 file changed, 58 insertions(+), 27 deletions(-)

diff --git a/usr.sbin/bhyveload/bhyveload.c b/usr.sbin/bhyveload/bhyveload.c
index 1a24b5f0044a..4f15771b514d 100644
--- a/usr.sbin/bhyveload/bhyveload.c
+++ b/usr.sbin/bhyveload/bhyveload.c
@@ -67,6 +67,7 @@ __FBSDID("$FreeBSD$");
 #include <machine/specialreg.h>
 #include <machine/vmm.h>
 
+#include <assert.h>
 #include <dirent.h>
 #include <dlfcn.h>
 #include <errno.h>
@@ -93,11 +94,11 @@ __FBSDID("$FreeBSD$");
 
 #define	NDISKS	32
 
-static char *host_base;
 static struct termios term, oldterm;
 static int disk_fd[NDISKS];
 static int ndisks;
 static int consin_fd, consout_fd;
+static int hostbase_fd = -1;
 
 static int need_reinit;
 
@@ -163,42 +164,61 @@ static int
 cb_open(void *arg, const char *filename, void **hp)
 {
 	struct cb_file *cf;
-	char path[PATH_MAX];
+	struct stat sb;
+	int fd, flags;
 
-	if (!host_base)
+	cf = NULL;
+	fd = -1;
+	flags = O_RDONLY | O_RESOLVE_BENEATH;
+	if (hostbase_fd == -1)
 		return (ENOENT);
 
-	strlcpy(path, host_base, PATH_MAX);
-	if (path[strlen(path) - 1] == '/')
-		path[strlen(path) - 1] = 0;
-	strlcat(path, filename, PATH_MAX);
-	cf = malloc(sizeof(struct cb_file));
-	if (stat(path, &cf->cf_stat) < 0) {
-		free(cf);
+	/* Absolute paths are relative to our hostbase, chop off leading /. */
+	if (filename[0] == '/')
+		filename++;
+
+	/* Lookup of /, use . instead. */
+	if (filename[0] == '\0')
+		filename = ".";
+
+	if (fstatat(hostbase_fd, filename, &sb, AT_RESOLVE_BENEATH) < 0)
 		return (errno);
+
+	if (!S_ISDIR(sb.st_mode) && !S_ISREG(sb.st_mode))
+		return (EINVAL);
+
+	if (S_ISDIR(sb.st_mode))
+		flags |= O_DIRECTORY;
+
+	/* May be opening the root dir */
+	fd = openat(hostbase_fd, filename, flags);
+	if (fd < 0)
+		return (errno);
+
+	cf = malloc(sizeof(struct cb_file));
+	if (cf == NULL) {
+		close(fd);
+		return (ENOMEM);
 	}
 
+	cf->cf_stat = sb;
 	cf->cf_size = cf->cf_stat.st_size;
+
 	if (S_ISDIR(cf->cf_stat.st_mode)) {
 		cf->cf_isdir = 1;
-		cf->cf_u.dir = opendir(path);
-		if (!cf->cf_u.dir)
-			goto out;
-		*hp = cf;
-		return (0);
-	}
-	if (S_ISREG(cf->cf_stat.st_mode)) {
+		cf->cf_u.dir = fdopendir(fd);
+		if (cf->cf_u.dir == NULL) {
+			close(fd);
+			free(cf);
+			return (ENOMEM);
+		}
+	} else {
+		assert(S_ISREG(cf->cf_stat.st_mode));
 		cf->cf_isdir = 0;
-		cf->cf_u.fd = open(path, O_RDONLY);
-		if (cf->cf_u.fd < 0)
-			goto out;
-		*hp = cf;
-		return (0);
+		cf->cf_u.fd = fd;
 	}
-
-out:
-	free(cf);
-	return (EINVAL);
+	*hp = cf;
+	return (0);
 }
 
 static int
@@ -712,6 +732,17 @@ usage(void)
 	exit(1);
 }
 
+static void
+hostbase_open(const char *base)
+{
+
+	if (hostbase_fd != -1)
+		close(hostbase_fd);
+	hostbase_fd = open(base, O_DIRECTORY | O_PATH);
+	if (hostbase_fd == -1)
+		err(EX_OSERR, "open");
+}
+
 int
 main(int argc, char** argv)
 {
@@ -746,7 +777,7 @@ main(int argc, char** argv)
 			break;
 
 		case 'h':
-			host_base = optarg;
+			hostbase_open(optarg);
 			break;
 
 		case 'l':