Back to Blog
critical SEVERITY7 min read

Guest-Controlled Buffer Overflow in virtio.c: How Four Unsafe memcpy Calls Threatened Host Memory

A critical buffer overflow vulnerability was discovered in `machine.virt/system/libs/arch_virt/src/virtio.c`, where four `memcpy` calls used length values sourced directly from guest-controlled virtio queue descriptor rings without validating them against the destination buffer size. An attacker operating a malicious guest VM could supply an oversized length (e.g., `0xFFFFFFFF`) to corrupt adjacent host heap memory, including function pointers and heap metadata. The fix introduces an explicit bo

O
By Orbis AppSec
Published June 1, 2026Reviewed June 3, 2026

Answer Summary

This is a CWE-120 buffer overflow vulnerability in C code within the virtio driver (`virtio.c`) where four `memcpy` calls used length values directly from guest-controlled virtio queue descriptor rings without validation. A malicious guest VM could supply enormous length values (e.g., `0xFFFFFFFF`) to overflow host heap buffers, corrupting function pointers and heap metadata. The fix adds explicit bounds checking before each `memcpy` to ensure guest-supplied lengths don't exceed the destination buffer size, preventing memory corruption attacks from guest to host.

Vulnerability at a Glance

cweCWE-120 (Buffer Copy without Checking Size of Input)
fixAdded explicit bounds validation comparing guest length against buffer size before each memcpy()
riskMalicious guest VM can corrupt host memory, potentially achieving hypervisor escape
languageC
root causeFour memcpy() calls used unchecked guest-supplied length values from virtio descriptor rings
vulnerabilityGuest-controlled buffer overflow in virtio driver

Guest-Controlled Buffer Overflow in virtio.c: How Four Unsafe memcpy Calls Threatened Host Memory

Introduction

The machine.virt/system/libs/arch_virt/src/virtio.c file sits at a uniquely sensitive boundary in any virtualization stack: it is the code that runs on the host while processing data structures written by the guest. In this environment, trust assumptions are inverted — the guest is an untrusted entity, and every value it places in a shared descriptor ring must be treated as potentially adversarial input.

A critical vulnerability was found at line 359 of this file, where four memcpy calls consumed req_len, resp_len, read_len, and write_len values that originated directly from virtio queue descriptor ring entries. None of these lengths were validated against the size of their destination buffers before the copy. For developers building or auditing hypervisor or emulator code, this is a textbook example of why every externally sourced length parameter must be clamped before use in a memory operation.


The Vulnerability Explained

How Virtio Descriptors Work (and Why They're Dangerous)

The virtio protocol allows a guest VM to communicate with virtual devices by placing descriptors into a shared memory ring. Each descriptor contains a pointer to a buffer and a length field that tells the host how many bytes to read or write. The critical detail: the guest writes this length field. There is nothing in the hardware or protocol that prevents a malicious guest from writing 0xFFFFFFFF as the length.

In virtio_net_read(), the vulnerable code path looked like this (before the fix):

// VULNERABLE: payload_len is derived from the guest-controlled descriptor ring
int read_len = payload_len;
if ((uint32_t)read_len > size)
{
    read_len = (int)size;
}
// memcpy proceeds — but payload_len itself was never checked against
// VIRTIO_NET_RX_BUF_SIZE before this point

The check (uint32_t)read_len > size guards against overflowing the caller-supplied buf parameter, but it does not prevent payload_len from exceeding the internal receive buffer (VIRTIO_NET_RX_BUF_SIZE). If payload_len was set to a value larger than VIRTIO_NET_RX_BUF_SIZE - VIRTIO_NET_HDR_SIZE, the subsequent memcpy would write past the end of the allocated network receive buffer.

The Four Dangerous Length Variables

The vulnerability manifested in four places across the file, each following the same pattern:

Variable Operation Risk
req_len Copy request data from guest Guest-controlled write length
resp_len Copy response data to guest Guest-controlled read length
read_len Read payload from RX buffer Derived from guest descriptor
write_len Write payload to TX buffer Derived from guest descriptor

In every case, the length was used in a memcpy without first verifying it fit within the bounds of the host-side buffer.

A Concrete Attack Scenario

Consider an attacker who has compromised a guest VM — or who is deliberately running a malicious guest. They craft a virtio network descriptor with:

descriptor.len = 0xFFFFFFFF  // ~4 GB

When virtio_net_read() processes this descriptor on the host:

  1. payload_len is set to 0xFFFFFFFF (or a large value derived from it).
  2. The check against size may or may not trigger depending on the caller's buffer size.
  3. Even if read_len is clamped to size, the internal buffer copy using payload_len is not clamped.
  4. memcpy writes far beyond the end of the VIRTIO_NET_RX_BUF_SIZE allocation on the host heap.

The heap region immediately following the network buffer could contain:

  • Heap metadata (chunk headers, free list pointers) — overwriting these enables heap layout manipulation.
  • Function pointers stored in adjacent objects — overwriting these can redirect host execution.
  • Other device state structures — corrupting these can destabilize the entire VM host process.

This is a guest-to-host escape vector. A sufficiently sophisticated attacker could leverage heap corruption to achieve arbitrary code execution on the hypervisor host, breaking the fundamental isolation guarantee of the virtualization layer.


The Fix

The fix introduces a bounds check that clamps payload_len to the maximum value that can safely fit in the receive buffer, before read_len is derived from it.

Before (Vulnerable)

// payload_len comes from guest-controlled descriptor — never validated
// against VIRTIO_NET_RX_BUF_SIZE

int read_len = payload_len;
if ((uint32_t)read_len > size)
{
    read_len = (int)size;
}
// memcpy with read_len — but payload_len itself could be > RX buffer

After (Fixed)

// NEW: Clamp payload_len to the safe maximum BEFORE deriving read_len
if ((uint32_t)payload_len > VIRTIO_NET_RX_BUF_SIZE - VIRTIO_NET_HDR_SIZE)
{
    payload_len = (int)(VIRTIO_NET_RX_BUF_SIZE - VIRTIO_NET_HDR_SIZE);
}

int read_len = payload_len;
if ((uint32_t)read_len > size)
{
    read_len = (int)size;
}

Why This Fix Works

The new check enforces the invariant that payload_len can never exceed the usable portion of the RX buffer (VIRTIO_NET_RX_BUF_SIZE - VIRTIO_NET_HDR_SIZE). The subtraction of VIRTIO_NET_HDR_SIZE is important — it accounts for the virtio network header that occupies the beginning of the buffer, ensuring the payload copy target is correctly bounded.

Key properties of the fix:

  1. It runs before read_len is assigned — so all downstream uses of read_len inherit the safe value.
  2. It uses a named constant (VIRTIO_NET_RX_BUF_SIZE) rather than a magic number — making the intent clear and the bound easy to audit.
  3. It uses a clamping pattern rather than an error return — appropriate here because truncating an oversized packet is safer than aborting the entire virtio processing loop.
  4. The cast to (int) is explicit — avoiding implicit conversion surprises when the result is assigned back to the signed payload_len.

The same pattern was applied to the three other vulnerable length variables (req_len, resp_len, write_len), each clamped to their respective buffer size constants before use in memcpy.


Prevention & Best Practices

1. Treat All Guest-Provided Values as Untrusted Input

In hypervisor and emulator code, the guest is equivalent to an attacker. Every value read from shared memory (MMIO regions, virtio rings, shared pages) must be validated before use. This includes:

  • Length fields in descriptors
  • Offset fields in requests
  • Count fields in ring indices

2. Validate at the Boundary, Not the Use Site

The fix correctly validates payload_len at the point where it is first used as a copy length, not buried deep in a helper function. Boundary validation should happen as close to the trust boundary (the virtio descriptor read) as possible.

3. Use Safe Copy Patterns in C

For memory copy operations involving externally sourced lengths, always follow this pattern:

// Pattern: clamp → copy, never copy → check
size_t safe_len = MIN(guest_provided_len, sizeof(destination_buffer));
memcpy(destination_buffer, source, safe_len);

Avoid the reverse pattern (copy first, check after) — it's always wrong.

4. Consider AddressSanitizer and Fuzzing for Virtio Code

  • AddressSanitizer (ASan): Compile with -fsanitize=address during development. ASan would have caught this overflow the first time a test sent an oversized descriptor.
  • Virtio fuzzing: Tools like virtio-fuzzer or custom libFuzzer harnesses can generate malformed descriptor rings and surface exactly this class of bug.
  • Static analysis: Coverity, CodeQL, and similar tools have checkers specifically for memcpy with unvalidated length parameters.

5. Relevant Standards and References

  • CWE-119: Improper Restriction of Operations within the Bounds of a Memory Buffer
  • CWE-122: Heap-based Buffer Overflow
  • CWE-20: Improper Input Validation
  • OWASP: Buffer Overflow
  • QEMU Security Guidelines: Virtio device emulation security requirements

Key Takeaways

  • Guest-controlled virtio descriptor lengths are attacker-controlled inputsreq_len, resp_len, read_len, and write_len in virtio.c must never be used in memcpy without first being clamped to the host-side buffer size.
  • The missing check was specifically payload_len > VIRTIO_NET_RX_BUF_SIZE - VIRTIO_NET_HDR_SIZE — the existing check against size protected the caller's buffer but not the internal RX buffer, leaving a gap that the fix closes.
  • Heap corruption in hypervisor code is a guest-to-host escape vector — overflowing a network receive buffer on the host heap can corrupt function pointers or heap metadata, potentially giving a malicious guest code execution on the host.
  • Clamping is the right mitigation pattern here — truncating an oversized packet is semantically correct for network I/O and avoids introducing error-handling complexity into the virtio processing loop.
  • All four length variables needed fixing, not just one — when you find one unvalidated length in a virtio device implementation, audit every other descriptor-derived length in the same file immediately.

Conclusion

This vulnerability in machine.virt/system/libs/arch_virt/src/virtio.c is a precise illustration of why virtualization security requires treating the guest as a fully adversarial actor. The four memcpy calls that consumed req_len, resp_len, read_len, and write_len without bounds-checking were each a potential guest-to-host escape, exploitable by any guest VM that could write to its virtio descriptor ring — which is every guest VM, by design.

The fix is elegantly minimal: four bounds checks, each clamping a guest-derived length to its corresponding host buffer size constant before any memory operation proceeds. This is the pattern every virtio device implementation should follow. If you maintain hypervisor or emulator code, now is a good time to audit your own virtio device handlers for the same pattern.

This fix was identified and automated by OrbisAI Security.

Frequently Asked Questions

What is a guest-controlled buffer overflow in a hypervisor?

A guest-controlled buffer overflow occurs when a virtual machine (guest) can supply input that causes the hypervisor (host) to write beyond the bounds of an allocated buffer, potentially corrupting host memory and escaping VM isolation.

How do you prevent buffer overflows in C virtio drivers?

Always validate guest-supplied length values against the actual buffer size before performing memory operations. Use bounds-checked functions, add explicit size comparisons, and never trust data from the guest VM without validation.

What CWE is guest-controlled buffer overflow?

This falls under CWE-120 (Buffer Copy without Checking Size of Input) and relates to CWE-119 (Improper Restriction of Operations within the Bounds of a Memory Buffer), both critical memory safety issues.

Is using memcpy_s() enough to prevent buffer overflows?

While safer alternatives like memcpy_s() help, the fundamental issue is validating the length parameter itself. You must verify that guest-supplied lengths don't exceed your buffer size before calling any copy function, regardless of which variant you use.

Can static analysis detect guest-controlled buffer overflows?

Yes, modern static analysis tools can track data flow from untrusted sources (like virtio descriptor rings) to dangerous sinks (like memcpy), identifying missing bounds checks. Taint analysis is particularly effective for detecting these trust boundary violations.

View the Security Fix

Check out the pull request that fixed this vulnerability

View PR #209

Related Articles

high

How heap buffer overflow happens in C JMA archive extraction and how to fix it

A heap buffer overflow vulnerability in `jma/jma.cpp` allowed a crafted JMA ROM archive to trigger out-of-bounds memory writes during file extraction. The flaw existed at line 446, where `memcpy` was called with `first_chunk_offset` and `copy_amount` values derived directly from archive header metadata without any validation that those values stayed within the bounds of either the source or destination buffer. The fix adds a pre-copy bounds check that rejects malformed archives before the danger

critical

How unsafe buffer copying happens in C credential storage and how to fix it

A critical vulnerability in `lib/server.c` allowed attackers to trigger out-of-bounds memory reads when copying credentials via unsafe `memcpy()` calls. By replacing `memcpy()` with bounds-safe `strlcpy()`, the fix ensures credentials are safely stored without buffer overruns or null-termination issues.

critical

How buffer overflow happens in C Bluetooth device handling and how to fix it

A critical buffer overflow vulnerability in `src/wiiuse.c` allowed attackers within Bluetooth range to trigger heap corruption by sending specially crafted HID packets with oversized length values. The fix adds strict bounds checking to validate that data lengths don't exceed buffer capacity before performing memory operations, preventing exploitation by malicious or intercepted Bluetooth devices.

critical

How buffer overflow happens in C patches.c sprintf macros and how to fix it

A critical buffer overflow vulnerability was discovered in `src/patches.c` where the `_EPRINT_I`, `_EPRINT_F`, and `_EPRINT_COEF` macros used `sprintf()` to write formatted AMY event data into a fixed-size buffer without any bounds checking. By replacing every `sprintf()` call with `snprintf()` and tracking remaining buffer space using a `s_entry` base pointer, the fix ensures that formatting 22 event fields — even at maximum values — can never write beyond the buffer boundary.

critical

How buffer overflow happens in C dcraw_lz.c nikon_3700() and how to fix it

A critical buffer overflow vulnerability was discovered in `lightcrafts/coprocesses/dcraw/dcraw_lz.c` at line 1334, where the `nikon_3700()` function used `strcpy()` to copy camera make and model strings into fixed 64-byte buffers without any bounds checking. A crafted RAW image file with oversized make/model metadata could trigger a heap or stack corruption, potentially enabling arbitrary code execution. The fix replaces both `strcpy()` calls with `strncpy()` and explicit null-termination, enfo

critical

How buffer overflow in modxo_queue.c memcpy happens in C embedded systems and how to fix it

A critical buffer overflow vulnerability was discovered in `modxo/modxo_queue.c`, where two `memcpy` operations in the `modxo_queue_insert` and `modxo_queue_remove` functions used `queue->item_size` as the copy length without validating it against the destination buffer's bounds. If `item_size` was corrupted or maliciously set to an oversized value, both the enqueue (line 49) and dequeue (line 61) operations could overflow adjacent heap or stack memory on the embedded target. The fix adds bounds