git: ac6f9513b210 - main - bhyve: Fix handling of framebuffer resizing

From: Mark Johnston <markj_at_FreeBSD.org>
Date: Wed, 14 Oct 2026 16:02:22 UTC
The branch main has been updated by markj:

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

commit ac6f9513b210097410dcd6f44e9523f990ba7b92
Author:     Mark Johnston <markj@FreeBSD.org>
AuthorDate: 2026-10-13 19:59:10 +0000
Commit:     Mark Johnston <markj@FreeBSD.org>
CommitDate: 2026-10-14 16:00:47 +0000

    bhyve: Fix handling of framebuffer resizing
    
    The rfb code maintains an array of CRCs for 32x32 tiles in the
    framebuffer.  This array is sized statically based on the rfb code's
    notion of the maximum framebuffer size, which changed in commit
    fb51ddb20d57 without updating the rfb code.
    
    Morever, writes to the framebuffer BAR can modify the recorded height
    and width of the framebuffer.  These values were not validated against
    the maximum at all, unlike the parameters set via bhyve config.
    
    Fix these problems:
    - Define the maximum width and height in a shared header file so that
      they do not get out of sync again.
    - In pci_fbuf_render(), don't resize the console if the new dimensions
      are too large.
    - Add a mutex to synchronize framebuffer BAR I/O with the rfb thread.
    
    Approved by:    so
    Security:       FreeBSD-SA-26:72.bhyve
    Security:       CVE-2026-101311
    Fixes:          fb51ddb20d57 ("bhyve: increase fbuf display resolution limit")
    Sponsored by:   The FreeBSD Foundation
    Differential Revision:  https://reviews.freebsd.org/D60680
---
 usr.sbin/bhyve/bhyvegc.h  |  3 +++
 usr.sbin/bhyve/pci_fbuf.c | 59 ++++++++++++++++++++++++++++++++---------------
 usr.sbin/bhyve/rfb.c      |  6 ++---
 3 files changed, 46 insertions(+), 22 deletions(-)

diff --git a/usr.sbin/bhyve/bhyvegc.h b/usr.sbin/bhyve/bhyvegc.h
index 5344473a05bd..7b2d43d99e32 100644
--- a/usr.sbin/bhyve/bhyvegc.h
+++ b/usr.sbin/bhyve/bhyvegc.h
@@ -43,4 +43,7 @@ void bhyvegc_set_fbaddr(struct bhyvegc *gc, void *fbaddr);
 void bhyvegc_resize(struct bhyvegc *gc, int width, int height);
 struct bhyvegc_image *bhyvegc_get_image(struct bhyvegc *gc);
 
+#define	GC_COLS_MAX	3840
+#define	GC_ROWS_MAX	2160
+
 #endif /* _BHYVEGC_H_ */
diff --git a/usr.sbin/bhyve/pci_fbuf.c b/usr.sbin/bhyve/pci_fbuf.c
index 011bb606d708..68bbfe255705 100644
--- a/usr.sbin/bhyve/pci_fbuf.c
+++ b/usr.sbin/bhyve/pci_fbuf.c
@@ -31,18 +31,20 @@
 #include <sys/socket.h>
 #include <sys/un.h>
 
-#include <dev/vmm/vmm_mem.h>
 #include <machine/vmm.h>
 #include <machine/vmm_snapshot.h>
-#include <vmmapi.h>
 
+#include <dev/vmm/vmm_mem.h>
+
+#include <errno.h>
+#include <pthread.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-
-#include <errno.h>
 #include <unistd.h>
 
+#include <vmmapi.h>
+
 #include "bhyvegc.h"
 #include "bhyverun.h"
 #include "config.h"
@@ -74,9 +76,8 @@ static int fbuf_debug = 1;
 #define	DMEMSZ	128
 
 #define	FB_SIZE		(32*MB)
-
-#define COLS_MAX	3840
-#define ROWS_MAX	2160
+_Static_assert(FB_SIZE >= GC_ROWS_MAX * GC_COLS_MAX * sizeof(uint32_t),
+    "Framebuffer size is too small for maximum resolution");
 
 #define COLS_DEFAULT	1024
 #define ROWS_DEFAULT	768
@@ -95,6 +96,8 @@ struct pci_fbuf_softc {
 		uint8_t  reserved[116];
 	} __packed memregs;
 
+	pthread_mutex_t mtx;
+
 	/* rfb server */
 	sa_family_t rfb_family;
 	char      *rfb_host;
@@ -137,8 +140,8 @@ pci_fbuf_write(struct pci_devinst *pi, int baridx, uint64_t offset, int size,
 		return;
 	}
 
+	pthread_mutex_lock(&sc->mtx);
 	p = (uint8_t *)&sc->memregs + offset;
-
 	switch (size) {
 	case 1:
 		*p = value;
@@ -168,6 +171,7 @@ pci_fbuf_write(struct pci_devinst *pi, int baridx, uint64_t offset, int size,
 		DPRINTF(DEBUG_INFO, ("switching to VESA mode"));
 		sc->gc_image->vgamode = 0;
 	}
+	pthread_mutex_unlock(&sc->mtx);
 }
 
 static uint64_t
@@ -181,13 +185,13 @@ pci_fbuf_read(struct pci_devinst *pi, int baridx, uint64_t offset, int size)
 
 	sc = pi->pi_arg;
 
-
 	if (offset + size > DMEMSZ) {
 		printf("fbuf: read too large, offset %ld size %d\n",
 		       offset, size);
 		return (0);
 	}
 
+	pthread_mutex_lock(&sc->mtx);
 	p = (uint8_t *)&sc->memregs + offset;
 	value = 0;
 	switch (size) {
@@ -207,6 +211,7 @@ pci_fbuf_read(struct pci_devinst *pi, int baridx, uint64_t offset, int size)
 		printf("fbuf: read unknown size %d\n", size);
 		break;
 	}
+	pthread_mutex_unlock(&sc->mtx);
 
 	DPRINTF(DEBUG_VERBOSE,
 	    ("fbuf rd: offset 0x%lx, size: %d, value: 0x%lx",
@@ -340,9 +345,10 @@ pci_fbuf_parse_config(struct pci_fbuf_softc *sc, nvlist_t *nvl)
 	if (value != NULL)
 		sc->memregs.height = strtol(value, NULL, 10);
 
-	if (sc->memregs.width > COLS_MAX ||
-	    sc->memregs.height > ROWS_MAX) {
-		EPRINTLN("fbuf: max resolution is %ux%u", COLS_MAX, ROWS_MAX);
+	if (sc->memregs.width > GC_COLS_MAX ||
+	    sc->memregs.height > GC_ROWS_MAX) {
+		EPRINTLN("fbuf: max resolution is %ux%u",
+		    GC_COLS_MAX, GC_ROWS_MAX);
 		return (-1);
 	}
 	if (sc->memregs.width < COLS_MIN ||
@@ -363,21 +369,32 @@ static void
 pci_fbuf_render(struct bhyvegc *gc, void *arg)
 {
 	struct pci_fbuf_softc *sc;
+	uint32_t width, height;
 
 	sc = arg;
-
+	pthread_mutex_lock(&sc->mtx);
 	if (sc->vga_full && sc->gc_image->vgamode) {
 		/* TODO: mode switching to vga and vesa should use the special
 		 *      EFI-bhyve protocol port.
 		 */
+		pthread_mutex_unlock(&sc->mtx);
 		vga_render(gc, sc->vgasc);
 		return;
 	}
-	if (sc->gc_width != sc->memregs.width ||
-	    sc->gc_height != sc->memregs.height) {
-		bhyvegc_resize(gc, sc->memregs.width, sc->memregs.height);
-		sc->gc_width = sc->memregs.width;
-		sc->gc_height = sc->memregs.height;
+	width = sc->memregs.width;
+	height = sc->memregs.height;
+	if (sc->gc_width != width || sc->gc_height != height) {
+		if (width > GC_COLS_MAX || height > GC_ROWS_MAX) {
+			/* Ignore an invalid resolution. */
+			pthread_mutex_unlock(&sc->mtx);
+		} else {
+			sc->gc_width = width;
+			sc->gc_height = height;
+			pthread_mutex_unlock(&sc->mtx);
+			bhyvegc_resize(gc, width, height);
+		}
+	} else {
+		pthread_mutex_unlock(&sc->mtx);
 	}
 }
 
@@ -396,6 +413,8 @@ pci_fbuf_init(struct pci_devinst *pi, nvlist_t *nvl)
 
 	pi->pi_arg = sc;
 
+	pthread_mutex_init(&sc->mtx, NULL);
+
 	/* initialize config space */
 	pci_set_cfgdata16(pi, PCIR_DEVICE, 0x40FB);
 	pci_set_cfgdata16(pi, PCIR_VENDOR, 0xFB5D);
@@ -452,8 +471,10 @@ pci_fbuf_init(struct pci_devinst *pi, nvlist_t *nvl)
 	error = rfb_init(sc->rfb_family, sc->rfb_host, sc->rfb_port,
 	    sc->rfb_wait, sc->rfb_password);
 done:
-	if (error)
+	if (error) {
+		pthread_mutex_destroy(&sc->mtx);
 		free(sc);
+	}
 
 	return (error);
 }
diff --git a/usr.sbin/bhyve/rfb.c b/usr.sbin/bhyve/rfb.c
index c6c048d2b140..265c95ace0a9 100644
--- a/usr.sbin/bhyve/rfb.c
+++ b/usr.sbin/bhyve/rfb.c
@@ -192,9 +192,9 @@ struct rfb_pixfmt_msg {
 
 #define	RFB_CLIENTMSG_EXT_KEYEVENT	0
 
-#define	RFB_MAX_WIDTH			2000
-#define	RFB_MAX_HEIGHT			1200
-#define	RFB_ZLIB_BUFSZ			RFB_MAX_WIDTH*RFB_MAX_HEIGHT*4
+#define	RFB_MAX_WIDTH			GC_COLS_MAX
+#define	RFB_MAX_HEIGHT			GC_ROWS_MAX
+#define	RFB_ZLIB_BUFSZ			(RFB_MAX_WIDTH * RFB_MAX_HEIGHT * 4)
 
 #define PIXEL_RED_SHIFT		16
 #define PIXEL_GREEN_SHIFT	8