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;