@samitouri / QOSamiQemu / commits / 418396be80

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>

Warisjeet Singh committed Aug 24, 2026 at 12:47 UTC 418396be8013386a81f8d8d89ac0effcf03a64b6
2 files changed +24 -16
hw/display/vga.c
+22 -15
@@ -1241,7 +1241,10 @@ static void vga_draw_text(VGACommonState *s, int full_update)
1241 return;
1242 }
1243
1244 - if (width != s->last_width || height != s->last_height ||
1244 + if (surface == NULL ||
1245 + surface_width(surface) != width * cw ||
1246 + surface_height(surface) != height * cheight ||
1247 + width != s->last_text_width || height != s->last_text_height ||
1248 cw != s->last_cw || cheight != s->last_ch || s->last_depth) {
1249 s->last_scr_width = width * cw;
1250 s->last_scr_height = height * cheight;
@@ -1249,8 +1252,8 @@ static void vga_draw_text(VGACommonState *s, int full_update)
1252 surface = qemu_console_surface(s->con);
1253 qemu_console_text_resize(s->con, width, height);
1254 s->last_depth = 0;
1252 - s->last_width = width;
1253 - s->last_height = height;
1255 + s->last_text_width = width;
1256 + s->last_text_height = height;
1257 s->last_ch = cheight;
1258 s->last_cw = cw;
1259 full_update = 1;
@@ -1845,6 +1848,8 @@ static void vga_invalidate_display(void *opaque)
1848
1849 s->last_width = -1;
1850 s->last_height = -1;
1851 + s->last_text_width = -1;
1852 + s->last_text_height = -1;
1853 }
1854
1855 void vga_common_reset(VGACommonState *s)
@@ -1887,6 +1892,8 @@ void vga_common_reset(VGACommonState *s)
1892 s->last_ch = 0;
1893 s->last_width = 0;
1894 s->last_height = 0;
1895 + s->last_text_width = 0;
1896 + s->last_text_height = 0;
1897 s->last_scr_width = 0;
1898 s->last_scr_height = 0;
1899 s->cursor_start = 0;
@@ -1938,8 +1945,8 @@ static void vga_update_text(void *opaque, uint32_t *chardata)
1945 s->graphic_mode = graphic_mode;
1946 full_update = 1;
1947 }
1941 - if (s->last_width == -1) {
1942 - s->last_width = 0;
1948 + if (s->last_text_width == -1) {
1949 + s->last_text_width = 0;
1950 full_update = 1;
1951 }
1952
@@ -1978,15 +1985,15 @@ static void vga_update_text(void *opaque, uint32_t *chardata)
1985 break;
1986 }
1987
1981 - if (width != s->last_width || height != s->last_height ||
1988 + if (width != s->last_text_width || height != s->last_text_height ||
1989 cw != s->last_cw || cheight != s->last_ch) {
1990 s->last_scr_width = width * cw;
1991 s->last_scr_height = height * cheight;
1992 qemu_console_resize(s->con, s->last_scr_width, s->last_scr_height);
1993 qemu_console_text_resize(s->con, width, height);
1994 s->last_depth = 0;
1988 - s->last_width = width;
1989 - s->last_height = height;
1995 + s->last_text_width = width;
1996 + s->last_text_height = height;
1997 s->last_ch = cheight;
1998 s->last_cw = cw;
1999 full_update = 1;
@@ -2071,22 +2078,22 @@ static void vga_update_text(void *opaque, uint32_t *chardata)
2078 }
2079
2080 /* Display a message */
2074 - s->last_width = 60;
2075 - s->last_height = height = 3;
2081 + s->last_text_width = 60;
2082 + s->last_text_height = height = 3;
2083 qemu_console_text_set_cursor(s->con, -1, -1);
2077 - qemu_console_text_resize(s->con, s->last_width, height);
2084 + qemu_console_text_resize(s->con, s->last_text_width, height);
2085
2079 - for (dst = chardata, i = 0; i < s->last_width * height; i ++)
2086 + for (dst = chardata, i = 0; i < s->last_text_width * height; i ++)
2087 *dst++ = ' ';
2088
2089 size = strlen(msg_buffer);
2083 - width = (s->last_width - size) / 2;
2084 - dst = chardata + s->last_width + width;
2090 + width = (s->last_text_width - size) / 2;
2091 + dst = chardata + s->last_text_width + width;
2092 for (i = 0; i < size; i ++)
2093 *dst++ = ATTR2CHTYPE(msg_buffer[i], QEMU_COLOR_BLUE,
2094 QEMU_COLOR_BLACK, 1);
2095
2089 - qemu_console_text_update(s->con, 0, 0, s->last_width, height);
2096 + qemu_console_text_update(s->con, 0, 0, s->last_text_width, height);
2097 }
2098
2099 static uint64_t vga_mem_read(void *opaque, hwaddr addr,
hw/display/vga_int.h
+2 -1
@@ -122,7 +122,8 @@ typedef struct VGACommonState {
122 uint32_t plane_updated;
123 uint32_t last_line_offset;
124 uint8_t last_cw, last_ch;
125 - uint32_t last_width, last_height; /* in chars or pixels */
125 + uint32_t last_width, last_height; /* in pixels (graphics renderer) */
126 + uint32_t last_text_width, last_text_height; /* in chars (text renderer) */
127 uint32_t last_scr_width, last_scr_height; /* in pixels */
128 uint32_t last_depth; /* in bits */
129 bool last_byteswap;