Commit 418396be80 for qemu.org
commit 418396be8013386a81f8d8d89ac0effcf03a64b6
Author: Warisjeet Singh <sinxx198@gmail.com>
Date: Mon Aug 24 12:47:26 2026 -0400
hw/display/vga: fix text-mode OOB write after a graphics surface switch
vga_draw_text() decides whether the console surface needs a resize from
its geometry cache, but none of the cache terms observe the graphics
renderer having replaced the console surface in between:
- last_width/last_height are shared with vga_draw_graphic(), which
stores them in pixels while the text path stores characters;
- last_depth stays 0 for legacy (non-VBE) graphics modes, because
vga_get_bpp() only reports a depth when VBE is enabled, so the
"s->last_depth" term that normally forces a resize after a graphics
frame does not fire.
So a graphics frame that shrinks the console surface (e.g. 80x25
pixels) followed by a text frame with matching character geometry
(80x25 chars) skips the resize, and the glyph loop then paints
width*cw x height*cheight pixels into the smaller surface, out of
bounds, with guest-controlled (DAC palette) values, on every display
refresh.
Separate the geometry cache per renderer: text paths (vga_draw_text,
vga_update_text, and the text handling in vga_invalidate_display /
vga_common_reset) now only manipulate last_text_{width,height}, in
characters; last_{width,height} become graphics-only, in pixels.
Additionally, make the text path compare the pixel size it is about
to paint against the console surface's actual dimensions. The
surface check is the load-bearing term: caches in either unit cannot
see the other renderer swapping the surface, the surface can.
Fixes: CVE-2026-77913
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4215
Cc: qemu-stable@nongnu.org
Signed-off-by: Warisjeet Singh (sin99xx) <sinxx198@gmail.com>
Message-ID: <vga-v3-20260824.sinxx198@gmail.com>
diff --git a/hw/display/vga.c b/hw/display/vga.c
index da0c331486..cb0e28b79b 100644
--- a/hw/display/vga.c
+++ b/hw/display/vga.c
@@ -1241,7 +1241,10 @@ static void vga_draw_text(VGACommonState *s, int full_update)
return;
}
- if (width != s->last_width || height != s->last_height ||
+ if (surface == NULL ||
+ surface_width(surface) != width * cw ||
+ surface_height(surface) != height * cheight ||
+ width != s->last_text_width || height != s->last_text_height ||
cw != s->last_cw || cheight != s->last_ch || s->last_depth) {
s->last_scr_width = width * cw;
s->last_scr_height = height * cheight;
@@ -1249,8 +1252,8 @@ static void vga_draw_text(VGACommonState *s, int full_update)
surface = qemu_console_surface(s->con);
qemu_console_text_resize(s->con, width, height);
s->last_depth = 0;
- s->last_width = width;
- s->last_height = height;
+ s->last_text_width = width;
+ s->last_text_height = height;
s->last_ch = cheight;
s->last_cw = cw;
full_update = 1;
@@ -1845,6 +1848,8 @@ static void vga_invalidate_display(void *opaque)
s->last_width = -1;
s->last_height = -1;
+ s->last_text_width = -1;
+ s->last_text_height = -1;
}
void vga_common_reset(VGACommonState *s)
@@ -1887,6 +1892,8 @@ void vga_common_reset(VGACommonState *s)
s->last_ch = 0;
s->last_width = 0;
s->last_height = 0;
+ s->last_text_width = 0;
+ s->last_text_height = 0;
s->last_scr_width = 0;
s->last_scr_height = 0;
s->cursor_start = 0;
@@ -1938,8 +1945,8 @@ static void vga_update_text(void *opaque, uint32_t *chardata)
s->graphic_mode = graphic_mode;
full_update = 1;
}
- if (s->last_width == -1) {
- s->last_width = 0;
+ if (s->last_text_width == -1) {
+ s->last_text_width = 0;
full_update = 1;
}
@@ -1978,15 +1985,15 @@ static void vga_update_text(void *opaque, uint32_t *chardata)
break;
}
- if (width != s->last_width || height != s->last_height ||
+ if (width != s->last_text_width || height != s->last_text_height ||
cw != s->last_cw || cheight != s->last_ch) {
s->last_scr_width = width * cw;
s->last_scr_height = height * cheight;
qemu_console_resize(s->con, s->last_scr_width, s->last_scr_height);
qemu_console_text_resize(s->con, width, height);
s->last_depth = 0;
- s->last_width = width;
- s->last_height = height;
+ s->last_text_width = width;
+ s->last_text_height = height;
s->last_ch = cheight;
s->last_cw = cw;
full_update = 1;
@@ -2071,22 +2078,22 @@ static void vga_update_text(void *opaque, uint32_t *chardata)
}
/* Display a message */
- s->last_width = 60;
- s->last_height = height = 3;
+ s->last_text_width = 60;
+ s->last_text_height = height = 3;
qemu_console_text_set_cursor(s->con, -1, -1);
- qemu_console_text_resize(s->con, s->last_width, height);
+ qemu_console_text_resize(s->con, s->last_text_width, height);
- for (dst = chardata, i = 0; i < s->last_width * height; i ++)
+ for (dst = chardata, i = 0; i < s->last_text_width * height; i ++)
*dst++ = ' ';
size = strlen(msg_buffer);
- width = (s->last_width - size) / 2;
- dst = chardata + s->last_width + width;
+ width = (s->last_text_width - size) / 2;
+ dst = chardata + s->last_text_width + width;
for (i = 0; i < size; i ++)
*dst++ = ATTR2CHTYPE(msg_buffer[i], QEMU_COLOR_BLUE,
QEMU_COLOR_BLACK, 1);
- qemu_console_text_update(s->con, 0, 0, s->last_width, height);
+ qemu_console_text_update(s->con, 0, 0, s->last_text_width, height);
}
static uint64_t vga_mem_read(void *opaque, hwaddr addr,
diff --git a/hw/display/vga_int.h b/hw/display/vga_int.h
index 5664317ecd..ca69ae9815 100644
--- a/hw/display/vga_int.h
+++ b/hw/display/vga_int.h
@@ -122,7 +122,8 @@ typedef struct VGACommonState {
uint32_t plane_updated;
uint32_t last_line_offset;
uint8_t last_cw, last_ch;
- uint32_t last_width, last_height; /* in chars or pixels */
+ uint32_t last_width, last_height; /* in pixels (graphics renderer) */
+ uint32_t last_text_width, last_text_height; /* in chars (text renderer) */
uint32_t last_scr_width, last_scr_height; /* in pixels */
uint32_t last_depth; /* in bits */
bool last_byteswap;