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

critical

How buffer overflow happens in C tar header parsing and how to fix it

A critical buffer overflow vulnerability was discovered in `microtar/microtar.c` where the `raw_to_header()` and `header_to_raw()` functions used unbounded `strcpy()` and `sprintf()` calls to copy tar header fields. Malicious tar files with non-null-terminated name fields could overflow destination buffers, potentially leading to code execution. The fix replaces all unsafe string operations with bounded alternatives: `memcpy()` with explicit null-termination and `snprintf()` instead of `sprintf(

critical

How buffer overflow happens in C ieee80211_input() and how to fix it

A critical buffer overflow vulnerability was discovered in `src/firmware/src/net/ieee80211.c` at line 1584, where the `ieee80211_input()` function processed raw 802.11 data frames without verifying that the incoming frame was large enough to contain a valid `ieee80211_frame` header. An attacker within wireless range could craft undersized or malformed frames to trigger memory corruption, potentially leading to remote code execution on the firmware. The fix adds a single, targeted bounds check th

high

How buffer overflow from unsafe string copy functions happens in C network interface code and how to fix it

A high-severity buffer overflow vulnerability was discovered in `generic/eth-impl.c`, where unsafe `strncpy()` and `sprintf()` calls could write beyond buffer boundaries when handling network interface names and device filenames. The fix replaced these dangerous functions with bounded `snprintf()` calls that guarantee null-termination and prevent memory corruption.

critical

How buffer overflow in FuzzIxml.c sprintf() happens in C and how to fix it

A critical buffer overflow vulnerability was discovered in `fuzzer/FuzzIxml.c` where `sprintf()` wrote a PID-formatted filename into a fixed 256-byte stack buffer without any bounds checking. The fix replaces `sprintf()` with `snprintf()`, explicitly passing the buffer size to prevent any overflow. While exploitation in this specific fuzzer context requires local access, the pattern is a textbook example of CWE-120 that developers should recognize and eliminate everywhere it appears.

critical

How buffer overflow happens in C HTML parsing and how to fix it

A critical buffer overflow vulnerability in `include/html_parse.h` allowed attackers to overflow buffers by providing malicious HTML input exceeding buffer capacity. The fix adds proper bounds checking before memcpy() operations to prevent memory corruption and potential code execution.

critical

How buffer overflow in memcpy() happens in Node.js N-API bindings and how to fix it

A critical buffer overflow vulnerability was discovered in the GetBufferAsVector() function in examples_nodejs/src/zupt_napi.cpp, where memcpy() copied data from JavaScript Uint8Array buffers without proper bounds validation. This vulnerability could allow attackers to trigger memory corruption by providing maliciously crafted input arrays to the native Node.js module, potentially leading to crashes or arbitrary code execution.