CVE: CVE-2021-4206

Tested Versions:

  • QEMU < v6.0.0

Product URL(s):

Description of the vulnerability

Technical Details

QXL, the QEMU QXL video accelerator, is a para-virtualized framebuffer device for the SPICE protocol. It is the default video device when we create a VM from virt-manager. It exposes the RAMs and I/O ports to let guest communicate with it.

00:01.0 VGA compatible controller: Red Hat, Inc. QXL paravirtual graphic card (rev 04) (prog-if 00 [VGA controller])
        Subsystem: Red Hat, Inc. QEMU Virtual Machine
        Flags: fast devsel, IRQ 21
        Memory at f4000000 (32-bit, non-prefetchable) [size=64M]
        Memory at f8000000 (32-bit, non-prefetchable) [size=64M]
        Memory at fcc14000 (32-bit, non-prefetchable) [size=8K]
        I/O ports at c040 [size=32]
        Expansion ROM at 000c0000 [disabled] [size=128K]
        Kernel driver in use: qxl
        Kernel modules: qxl

On its RAMs, QXL implements different rings for different purposes. The space cursor points to the device RAMs, which means the guest controls its content. In cursor_ring, the guest can push a cursor command to tell the video driver how to render a cursor or where to place the cursor. After we push a command and notify the device to handle the command, the function qxl_cursor will be called. It will use cursor->header.width and cursor->header.height to allocate enough space for forward use.

static QEMUCursor *qxl_cursor(PCIQXLDevice *qxl, QXLCursor *cursor,
                              uint32_t group_id)
    QEMUCursor *c;
    uint8_t *and_mask, *xor_mask;
    size_t size;

    c = cursor_alloc(cursor->header.width, cursor->header.height);
    c->hot_x = cursor->header.hot_spot_x;
    c->hot_y = cursor->header.hot_spot_y;
    switch (cursor->header.type) {

        size = sizeof(uint32_t) * cursor->header.width * cursor->header.height;
        qxl_unpack_chunks(c->data, size, qxl, &cursor->chunk, group_id);

As I said before, the guest controls width and height when it enters the function cursor_alloc. The vulnerability happens in code [1]. if we set width and height to 0x8000. After the multiplication, it causes integer overflow and makes datasize become zero. the following allocation will not give enough space buffer.

QEMUCursor *cursor_alloc(int width, int height)
    QEMUCursor *c;
    int datasize = width * height * sizeof(uint32_t); //code [1]

    c = g_malloc0(sizeof(QEMUCursor) + datasize);
    c->width  = width;
    c->height = height;
    c->refcount = 1;
    return c;

In function qxl_unpack_chunks, wrong allocation at cursor_alloc cause it believes buffer dest has size space for store data. Return from cursor_alloc, and it recalculates size with type size_t, so it will multiply the correct value and enter function qxl_unpack_chunks for store guest data. Later calling memcpy will cause heap overflow.

static void qxl_unpack_chunks(void *dest, size_t size, PCIQXLDevice *qxl,
                              QXLDataChunk *chunk, uint32_t group_id)
    uint32_t max_chunks = 32;
    size_t offset = 0;
    size_t bytes;

    for (;;) {
        bytes = MIN(size - offset, chunk->data_size);
        memcpy(dest + offset, chunk->data, bytes);
        offset += bytes;
        if (offset == size) {
        chunk = qxl_phys2virt(qxl, chunk->next_chunk, group_id);
        if (!chunk) {
        if (max_chunks == 0) {


  • The attacker needs to run as a high-privileged user
  • VM need a QXL Video Device and a VNC Server Graphics

Proof Of Concept

Use virt-manager to create a VM which has a QXL Video Device and a VNC Server Graphics

  • Host OS : Ubuntu

  • Guest OS : Ubuntu

  • Run gcc poc.c -o poc and sudo ./poc

  • The VM will crash


  • Since the PoC must be run at high-privileged on the guest OS, Do not run untrusted code or driver in guest OS.


  • 2021-12-28 Vendor disclosure
  • 2022-03-28 Vendor patched