Skip to content

[Bug] Malicious virtio backend can trigger guest-kernel memory corruption through unvalidated used-ring ids and lengths in multiple RT-Thread virtio drivers #11326

@XueDugu

Description

@XueDugu

RT-Thread Version

master at commit 2b58dec. Also on released tags v5.0.0, v5.0.2, v5.1.0, v5.2.0, v5.2.1, and v5.2.2.

Hardware Type/Architectures

Any RT-Thread guest platform enabling virtio drivers

Develop Toolchain

GCC

Describe the bug

Summary

Multiple RT-Thread virtio guest drivers unconditionally trust device-controlled used-ring metadata and use it directly as local array indices or derived copy lengths without any validation. A malicious or compromised virtio backend can inject forged completion entries across the guest/host trust boundary and trigger out-of-bounds memory accesses inside the guest kernel.

This is not a crash-only robustness issue. Because the virtio backend controls the timing and content of queue completion metadata, a compromised host-side backend has a direct and reliable host-to-guest kernel memory corruption primitive against any RT-Thread guest running these drivers.


Affected Components

Driver File Vulnerable Function Nature of Bug
virtio-input components/drivers/virtio/virtio_input.c virtio_input_isr() Backend-controlled id indexes fixed guest arrays
virtio-console components/drivers/virtio/virtio_console.c virtio_console_isr() Backend-controlled id indexes info[id]
virtio-gpu components/drivers/virtio/virtio_gpu.c virtio_gpu_isr() Backend-controlled id indexes info[id]
virtio-blk components/drivers/virtio/virtio_blk.c virtio_blk_isr() Backend-controlled id indexes info[id]
virtio-net components/drivers/virtio/virtio_net.c virtio_net_rx() Backend-controlled len underflows and drives OOB copy

Details

Pattern 1: Unvalidated Used-Ring ID as Array Index

In virtio_input_isr(), the driver reads event_queue->used->ring[..].id directly from shared virtqueue memory and immediately uses it to index fixed-size guest arrays:

  • virtio_input.c:300
  • virtio_input.c:305
  • virtio_input.c:306

The destination arrays are fixed-size, defined in:

  • virtio_input.h:21
  • virtio_input.h:120
  • virtio_input.h:123

There is no check that id < queue->num, and no check that id < ARRAY_SIZE(recv_events) or id < ARRAY_SIZE(bcst_events). A backend-supplied id of any out-of-range value directly corrupts guest kernel memory.

The identical bug pattern is present in:

  • virtio_console_isr() — backend-controlled id used to access virtio_console_dev->info[id]
  • virtio_gpu_isr() — backend-controlled id used to access virtio_gpu_dev->info[id]
  • virtio_blk_isr() — backend-controlled id used to access virtio_blk_dev->info[id]

Pattern 2: Used-Length Underflow in virtio-net RX Path

In virtio_net_rx() (virtio_net.c:69–82), the driver derives a payload copy length as:

id  = (used.id + 1) % queue_rx->num;
len = used.len - VIRTIO_NET_HDR_SIZE;

If the backend returns used.len < VIRTIO_NET_HDR_SIZE, the subtraction underflows in unsigned arithmetic, producing a very large len. This underflowed value is then only clamped against VIRTIO_NET_PAYLOAD_MAX_SIZE — not against the actual RX descriptor size (VIRTIO_NET_MSS, defined in virtio_net.h:79–81).

The driver then executes:

rt_memcpy(p->payload, (void *)queue_rx->desc[id].addr - PV_OFFSET, len);

with the attacker-controlled len, creating a guest-kernel out-of-bounds read primitive in the network RX path.

Why This Is High Severity

The virtio used ring is shared memory between the guest and the backend. In any deployment where the virtio backend is not in the same trust domain as the guest — including cloud virtualization, paravirtualized containers, or any scenario with a potentially compromised host — the backend's control over completion metadata constitutes a real cross-trust-boundary attack surface. There is no hardware enforcement preventing a malicious backend from forging id or len values in shared ring memory.


Steps to Reproduce

PoC 1: virtio_input_isr() Out-of-Bounds Index

  1. Build an RT-Thread guest with virtio-input enabled
  2. Use a custom virtio input backend, instrumented hypervisor, or modified QEMU device model
  3. After guest queue initialization, write a forged used-ring completion into the input event queue:
    • id = 64 (or any value >= VIRTIO_INPUT_EVENT_QUEUE_SIZE)
    • len = sizeof(struct virtio_input_event)
  4. Trigger the virtio interrupt
  5. The guest enters virtio_input_isr() and uses the forged id to index recv_events[id] and bcst_events[id], causing an out-of-bounds guest-kernel memory access

The same malformed-used-ring-id technique reaches the equivalent info[id] accesses in virtio_console, virtio_gpu, and virtio_blk.

PoC 2: virtio_net_rx() Used-Length Underflow

  1. Build an RT-Thread guest with virtio-net enabled
  2. In the virtio net backend, return a forged RX used-ring entry:
    • used.id = 0
    • used.len = 0 (or any value < VIRTIO_NET_HDR_SIZE)
  3. Trigger the RX interrupt
  4. The guest computes len = used.len - VIRTIO_NET_HDR_SIZEunsigned underflow
  5. The resulting oversized len drives rt_memcpy(), producing an out-of-bounds read from the RX descriptor area

PoC 3: virtio_console_isr() Control Queue ID Corruption

  1. Build an RT-Thread guest with virtio-console enabled
  2. In the control RX queue backend, return:
    • id >= VIRTIO_CONSOLE_QUEUE_SIZE
    • len = sizeof(struct virtio_console_control)
  3. Trigger the interrupt
  4. The guest reaches virtio_console_dev->info[id].rx_ctrlout-of-bounds guest-kernel access

Expected Behavior

RT-Thread virtio guest drivers must treat all used-ring metadata — id, len, and derived values — as untrusted device input from outside the guest trust boundary. Every completion entry must be validated before any local array access or memory copy operation. Invalid id or len values must result in a logged error and safe discard or reset, never in out-of-bounds guest-kernel memory access.


Fix Suggestion

Validate Used-Ring ID Before Any Array Access

In virtio_input.c, virtio_console.c, virtio_gpu.c, and virtio_blk.c, add bounds checks before every info[id] or event-array dereference:

if (id >= queue->num || id >= ARRAY_SIZE(driver_local_array)) {
    LOG_E("virtio: malformed used-ring id %u, discarding", id);
    continue;  /* or break/reset as appropriate */
}

Fix Used-Length Underflow in virtio_net_rx()

Before computing len, validate used.len:

if (used.len < VIRTIO_NET_HDR_SIZE) {
    LOG_E("virtio-net: used.len %u < header size, discarding RX entry", used.len);
    continue;
}

len = used.len - VIRTIO_NET_HDR_SIZE;

if (len > VIRTIO_NET_MSS) {
    LOG_E("virtio-net: derived payload len %u > MSS, discarding", len);
    continue;
}

Centralize Validation in a Shared Helper

Add a shared validation routine in components/drivers/virtio/virtio.c or a common header so all virtio drivers use one centralized path for used-ring id and len validation, preventing future recurrence across the driver family.


Impact

This is a host-to-guest kernel memory corruption vulnerability affecting all RT-Thread guests that enable any of the five identified virtio drivers.

Impact includes:

  • Out-of-bounds write in guest kernel driver state via forged used-ring id (virtio-input, virtio-console, virtio-gpu, virtio-blk)
  • Out-of-bounds read in guest kernel RX path via forged used-ring len underflow (virtio-net)
  • Denial of service / guest kernel crash
  • Corruption of guest kernel driver control structures
  • In adversarial deployments: reliable cross-trust-boundary host-to-guest kernel memory corruption primitive

Who is impacted:

  • Any RT-Thread deployment running as a virtio guest (QEMU, KVM, cloud VM, or paravirtualized environment)
  • Deployments where the virtio backend is in a separate trust domain from the guest

Kindly let me know if you intend to request a CVE ID upon confirmation of the vulnerability.

Other additional context

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions