Guest-controlled virtio-rng request length reaches unchecked heap allocation and crashes QEMU
Host environment
Operating system: OS/kernel version: Architecture: QEMU flavor: QEMU version: QEMU command line:
Severity: Medium
Advisory Details
Title: Guest-controlled virtio-rng request length reaches unchecked heap allocation and crashes QEMU
Description:
Summary
A guest that can directly program the virtio-rng virtqueue can submit an oversized writable descriptor length, causing QEMU to allocate a host heap buffer of the same attacker-controlled size without an independent per-request upper bound. On the highest currently affected upstream release tag, v11.0.2, a single request with len=0x70000000 triggers a GLib allocation failure and terminates the host qemu-system-x86_64 process. This is a guest-to-host denial of service against the VM instance backed by that QEMU process.
Details
The vulnerable path spans the virtio-rng frontend in hw/virtio/virtio-rng.c and the shared RNG backend in backends/rng.c.
At the highest affected upstream release tag, v11.0.2 (peeled commit e545d8bb9d63e9dd61542b88463183314cff9482), QEMU computes the total request size from guest-provided writable descriptor lengths and forwards that value into the backend allocation path:
static size_t get_request_size(VirtQueue *vq, unsigned quota)
{
unsigned int in, out;
virtqueue_get_avail_bytes(vq, &in, &out, quota, 0);
return in;
}
...
size = get_request_size(vrng->vq, quota);
size = MIN(vrng->quota_remaining, size);
if (size) {
rng_backend_request_entropy(vrng->rng, size, chr_read, vrng);
}The backend then copies that size directly into the request object and allocates req->data with no backend-independent ceiling:
if (k->request_entropy) {
req = g_malloc(sizeof(*req));
req->offset = 0;
req->size = size;
req->receive_entropy = receive_entropy;
req->opaque = opaque;
req->data = g_malloc(req->size);
}This matters because the frontend-side quota logic is not a real safety boundary for a single request. The same affected release sets the default virtio-rng rate limit to INT64_MAX, with an inline comment saying it is “unlimited for all practical purposes”:
DEFINE_PROP_UINT64("max-bytes", VirtIORNG, conf.max_bytes, INT64_MAX),In practice, the reproduced exploit chain is:
- The guest submits a legacy
virtio-rngwritable descriptor withlen=0x70000000. virtqueue_get_avail_bytes()totals that length.virtio_rng_process()forwards the resultingsizetorng_backend_request_entropy().rng_backend_request_entropy()callsg_malloc(req->size)with1879048192.- GLib aborts on the oversized allocation and the QEMU process exits.
This is not a source-only pattern match and does not rely on local patching or internal test hooks. The PoC launches the real build/qemu-system-x86_64 binary, boots a minimal multiboot guest, and drives the documented guest-visible virtio-rng-pci device path end to end. A matched control run changes only the requested descriptor length to 0x1000, keeps the same device/backend path, and exits cleanly.
PoC
Prerequisites
- A checkout of
qemu/qemuat any vulnerable version up to and includingv11.0.2, or an equivalent tree that still contains the samevirtio-rngallocation path. - Standard QEMU build prerequisites sufficient to build
qemu-system-x86_64. - A local environment that can run the built binary with
-accel tcg. - If the local build depends on the repo-bundled Rutabaga library, set
LD_LIBRARY_PATHsoqemu-system-x86_64can resolvelibrutabaga_gfx_ffi.so.0. - Run the exploit and control serially, not in parallel, because both scripts rebuild the same
virtio_rng_bomb.elfartifact.
Reproduction Steps
- Download the support files required to build the guest payload:
- Download the harness files:
- verification_test.py
- verification_test_CVE-2016-2858.py
- Optional control: control-safe-request.py
- Place all downloaded files in the same directory inside the vulnerable QEMU checkout.
- Run the exploit wrapper:
python3 verification_test_CVE-2016-2858.py - Observe that the script reports
[End-to-End][DEFECT CONFIRMED],test_result.logrecordsrc=-5, andqemu.stderr.logcontains the GLib allocation failure. - Run the control script afterward:
python3 control-safe-request.py - Observe that the control reports
[End-to-End][CONTROL PASS], the guest reachespost-notify, and the same QEMU configuration remains alive for the safe-length request.
Log of Evidence
$ python3 verification_test_CVE-2016-2858.py
[End-to-End][DEFECT CONFIRMED] guest-controlled virtio-rng descriptor len=0x70000000 reached host allocation and terminated QEMU with a GLib OOM abort (rc=-5)
$ sed -n '1,20p' test_result.log
Mode: End-to-End
Case: exploit
Expectation: crash
Command: verification_test_CVE-2016-2858.py --case-name exploit --request-len 0x70000000 --expect crash --legacy-log-names
[Test Input] guest-crafted legacy virtio-rng writable descriptor len=0x70000000
[Interface] real qemu-system-x86_64 CLI booting a multiboot guest that directly programs the virtio-rng queue
[Processing] hw/virtio/virtio-rng.c:get_request_size() -> rng_backend_request_entropy()
[Sink] backends/rng.c:g_malloc(req->size) before backend fulfillment
Return code: -5
Guest log:
guest: pci scan
guest: slot=0x4 io=0xc000
guest: queue size=8
QEMU stderr:
qemu-system-x86_64: GLib: ../../../glib/gmem.c:134: failed to allocate 1879048192 bytes
$ python3 control-safe-request.py
[End-to-End][CONTROL PASS] safe virtio-rng descriptor len=0x1000 kept QEMU alive; guest exited cleanly
$ sed -n '1,20p' control-safe-request.result.log
Mode: End-to-End
Case: control-safe-request
Expectation: alive
Command: control-safe-request.py --case-name control-safe-request --request-len 0x1000 --expect alive
[Test Input] guest-crafted legacy virtio-rng writable descriptor len=0x1000
[Interface] real qemu-system-x86_64 CLI booting a multiboot guest that directly programs the virtio-rng queue
[Processing] hw/virtio/virtio-rng.c:get_request_size() -> rng_backend_request_entropy()
[Sink] backends/rng.c:g_malloc(req->size) before backend fulfillment
Return code: 1
Guest log:
guest: pci scan
guest: slot=0x4 io=0xc000
guest: queue size=8
guest: notify len=0x1000
guest: post-notifyImpact
This is a guest-triggerable denial of service in the QEMU device model. Any deployment that exposes virtio-rng to an untrusted tenant guest is affected if the guest can directly program the virtqueue instead of being constrained to a conservative in-guest driver behavior. Successful exploitation terminates the host QEMU process backing that VM, immediately interrupting the guest workload and any dependent orchestration around it. The current evidence demonstrates reliable host-process termination; it does not require claiming code execution on the host.
Affected products
- Ecosystem: GitHub
- Package name: qemu/qemu
- Affected versions: <= 11.0.2
- Patched versions:
Severity
- Severity: Medium
- Vector string: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:C/C:N/I:N/A:H
Weaknesses
- CWE: CWE-789: Memory Allocation with Excessive Size Value
Occurrences
| Permalink | Description |
|---|---|
| https://github.com/qemu/qemu/blob/e545d8bb9d63e9dd61542b88463183314cff9482/hw/virtio/virtio-rng.c#L36-L41 | get_request_size() sums guest-provided writable descriptor lengths through virtqueue_get_avail_bytes() and returns that total as the requested entropy size. |
| https://github.com/qemu/qemu/blob/e545d8bb9d63e9dd61542b88463183314cff9482/hw/virtio/virtio-rng.c#L95-L123 | virtio_rng_process() forwards the guest-derived request size into rng_backend_request_entropy() with only quota-based clamping and no dedicated single-request upper bound. |
| https://github.com/qemu/qemu/blob/e545d8bb9d63e9dd61542b88463183314cff9482/hw/virtio/virtio-rng.c#L253-L260 | The device property table sets max-bytes to INT64_MAX by default, which leaves the quota path effectively unlimited for practical per-request safety. |
| https://github.com/qemu/qemu/blob/e545d8bb9d63e9dd61542b88463183314cff9482/backends/rng.c#L26-L33 | rng_backend_request_entropy() copies the attacker-influenced size into req->size and allocates req->data with g_malloc(req->size) without an independent hard cap. |