CVE: CVE-2021-4206
Tested Versions:
- QEMU < v6.0.0
Product URL(s): https://www.qemu.org/
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) {
...
case SPICE_CURSOR_TYPE_ALPHA:
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) {
return;
}
chunk = qxl_phys2virt(qxl, chunk->next_chunk, group_id);
if (!chunk) {
return;
}
max_chunks--;
if (max_chunks == 0) {
return;
}
}
}
Requirement
- 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
andsudo ./poc
-
The VM will crash
Mitigations
- Since the PoC must be run at high-privileged on the guest OS, Do not run untrusted code or driver in guest OS.
Timeline:
- 2021-12-28 Vendor disclosure
- 2022-03-28 Vendor patched