CVE: CVE-2021-4207
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 are 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 fetch 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);
Return from cursor_alloc
and it fetches cursor->header.width
and cursor->header.height
again to calculate size. Because cursor points to RAMS which means the cursor->header.width
and cursor->header.height
can be modify by guest anytime, we can race it to make them be a larger value after return from cursor_alloc
.
In function qxl_unpack_chunks
, due to the wrong size causing it to believe buffer dest
has size
space for store 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 -pthread -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