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:

  1. The guest submits a legacy virtio-rng writable descriptor with len=0x70000000.
  2. virtqueue_get_avail_bytes() totals that length.
  3. virtio_rng_process() forwards the resulting size to rng_backend_request_entropy().
  4. rng_backend_request_entropy() calls g_malloc(req->size) with 1879048192.
  5. 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/qemu at any vulnerable version up to and including v11.0.2, or an equivalent tree that still contains the same virtio-rng allocation 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_PATH so qemu-system-x86_64 can resolve librutabaga_gfx_ffi.so.0.
  • Run the exploit and control serially, not in parallel, because both scripts rebuild the same virtio_rng_bomb.elf artifact.

Reproduction Steps

  1. Download the support files required to build the guest payload:
  2. Download the harness files:
  3. Place all downloaded files in the same directory inside the vulnerable QEMU checkout.
  4. Run the exploit wrapper:
    python3 verification_test_CVE-2016-2858.py
  5. Observe that the script reports [End-to-End][DEFECT CONFIRMED], test_result.log records rc=-5, and qemu.stderr.log contains the GLib allocation failure.
  6. Run the control script afterward:
    python3 control-safe-request.py
  7. Observe that the control reports [End-to-End][CONTROL PASS], the guest reaches post-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-notify

Impact

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.