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
fb51ddb20d 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:		fb51ddb20d ("bhyve: increase fbuf display resolution limit")
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D60680
This commit is contained in:
Mark Johnston committed 2026-10-13 23:17:33 +00:00
1 parent 468babfa68
commit 2e4ec07874
3 files changed
+42 -20

No files matched your search

+3
View File
@@ -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 1920
#define GC_ROWS_MAX 1200
#endif /* _BHYVEGC_H_ */
+36 -17
View File
@@ -32,15 +32,16 @@
#include <machine/vmm.h>
#include <machine/vmm_snapshot.h>
#include <vmmapi.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"
@@ -72,9 +73,8 @@ static int fbuf_debug = 1;
#define DMEMSZ 128
#define FB_SIZE (16*MB)
#define COLS_MAX 1920
#define ROWS_MAX 1200
_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
@@ -93,6 +93,8 @@ struct pci_fbuf_softc {
uint8_t reserved[116];
} __packed memregs;
pthread_mutex_t mtx;
/* rfb server */
char *rfb_host;
char *rfb_password;
@@ -134,8 +136,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;
@@ -165,6 +167,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
@@ -178,13 +181,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) {
@@ -204,6 +207,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",
@@ -316,7 +320,7 @@ pci_fbuf_parse_config(struct pci_fbuf_softc *sc, nvlist_t *nvl)
value = get_config_value_node(nvl, "w");
if (value != NULL) {
sc->memregs.width = atoi(value);
if (sc->memregs.width > COLS_MAX) {
if (sc->memregs.width > GC_COLS_MAX) {
EPRINTLN("fbuf: width %d too large", sc->memregs.width);
return (-1);
}
@@ -327,7 +331,7 @@ pci_fbuf_parse_config(struct pci_fbuf_softc *sc, nvlist_t *nvl)
value = get_config_value_node(nvl, "h");
if (value != NULL) {
sc->memregs.height = atoi(value);
if (sc->memregs.height > ROWS_MAX) {
if (sc->memregs.height > GC_ROWS_MAX) {
EPRINTLN("fbuf: height %d too large",
sc->memregs.height);
return (-1);
@@ -347,21 +351,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);
}
}
@@ -380,6 +395,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);
@@ -438,8 +455,10 @@ pci_fbuf_init(struct pci_devinst *pi, nvlist_t *nvl)
error = rfb_init(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);
}
+3 -3
View File
@@ -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