How to Trigger libc::system on QEMU Host from Guest: CXL Type-3 Mailbox Exploit

The CXL Type-3 mailbox escape PoC demonstrates how a guest virtual machine can execute arbitrary commands in the QEMU host process by exploiting an out-of-bounds read vulnerability in the mailbox handling code to forge a callback that invokes libc's system() function.

This guide explains how the bikini/exploitarium repository implements a guest-to-host escape that triggers libc::system on the QEMU host from a guest. The exploit targets a memory-corruption flaw in the CXL Type-3 mailbox implementation, allowing arbitrary code execution through careful manipulation of mailbox commands and memory leaks.

Understanding the CXL Type-3 Mailbox Architecture

The CXL Type-3 device mailbox provides the attack surface for this exploit. Understanding its layout and vulnerabilities is essential to trigger libc::system on the QEMU host from the guest.

Mailbox Register Layout

The mailbox is exposed to the guest via PCI configuration registers. According to the source in stage2.c【/cache/repos/github.com/bikini/exploitarium/main/qemu-cxl-type3-mailbox-escape-poc/stage2.c#L15-L22】, the guest can directly read and write mailbox registers including MAILBOX, MBOX_CTRL, MBOX_CMD, MBOX_STS, and MBOX_PAYLOAD through I/O ports. This direct access allows the guest to send commands that the QEMU host processes in its privileged context.

The GET_LOG Out-of-Bounds Read Vulnerability

The host's GET_LOG handler contains a critical security flaw. While it validates the requested range against internal log buffer sizes, it fails to properly bound the destination pointer. The leak_rel() function in stage2.c【/cache/repos/github.com/bikini/exploitarium/main/qemu-cxl-type3-mailbox-escape-poc/stage2.c#L48-L56】demonstrates how to exploit this by supplying a crafted offset calculated as GET_LOG_OOB_BASE_OFFSET + (rel>>2). This enables arbitrary host memory leakage, including the QEMU PIE base and the CXLType3Dev structure.

Leaking Host Memory to Resolve libc Addresses

To trigger libc::system on the QEMU host from the guest, the exploit must first defeat ASLR by leaking critical addresses from host memory.

Leaking QEMU Base Address

The exploit begins by leaking the cmd_logs_get_log function address through the out-of-bounds read vulnerability. This leak reveals the QEMU base address in the host process. By adding the known offset of memmove@plt, the exploit obtains a usable PLT entry (memmove_plt) for further calculations.

Calculating libc system() Address

With the QEMU base known, the exploit performs a second OOB read to obtain the GOT entry for __libc_start_main. This reveals the libc base address in the host process. According to the address calculations in _start() around lines 90-110 of stage2.c【/cache/repos/github.com/bikini/exploitarium/main/qemu-cxl-type3-mailbox-escape-poc/stage2.c#L90-L110】, the exploit adds the static offset SYSTEM_OFF (0x58750) to the libc base to compute the absolute address of system() in the host's libc.

The resolution logic follows this sequence:

/* Resolve libc `system` address */
U64 libc_start = leak_rel(0x100);          // leak __libc_start_main
U64 libc_base  = u64_sub(libc_start, LIBC_START_MAIN_OFF);
U64 system_fn  = u64_add(libc_base, SYSTEM_OFF);

Forging Callbacks to Execute system()

Once the system() address is resolved, the exploit constructs a fake object to hijack control flow and trigger libc::system on the QEMU host from the guest.

Constructing Fake MemoryRegionOps

The exploit builds a fake MemoryRegionOps object whose write pointer is set to the resolved system() address. This forged object is placed inside a fake CXLType3Dev dynamic-capacity region using the SET_FEATURE mailbox command (CMD_SET_FEAT). The payload construction occurs in forge_payload() and is transmitted via send_rank() to implant the malicious callback structure in host memory.

Triggering the Callback and system() Execution

Issuing a MEDIA_OPERATIONS command (CMD_MEDIA_OP) causes the QEMU core to invoke the forged write callback. The callback executes with a marker command supplied by the guest. As defined in stage2.c lines 66-68【/cache/repos/github.com/bikini/exploitarium/main/qemu-cxl-type3-mailbox-escape-poc/stage2.c#L66-L68】:

static const char host_cmd[] = "id>/tmp/qemu_cxl_escape_marker";

When the callback runs, it first leaks libc as a sanity check, then calls system() with this marker command. The serial console log confirms successful execution with a "fake system call" message, and the host filesystem contains the marker file /tmp/qemu_cxl_escape_marker as proof of code execution.

Complete Exploit Chain Implementation

The full exploitation chain implemented in stage2.c follows this sequence:

  1. Memory Leak: Use leak_rel() to read arbitrary host memory via the GET_LOG vulnerability.
  2. Address Calculation: Compute QEMU base and libc system() address from leaks.
  3. Payload Forging: Create fake MemoryRegionOps with write pointing to system().
  4. Callback Trigger: Send CMD_MEDIA_OP to execute the forged callback.

The implementation uses freestanding 32-bit C code that runs as the guest payload. The run.sh script in the repository automates the QEMU launch with the required CXL Type-3 configuration, while boot.asm provides the 16-bit bootloader that loads the exploit payload from the floppy image.

Summary

  • The exploit abuses an out-of-bounds read in GET_LOG handlers to leak arbitrary host memory from the guest.
  • Address resolution calculates the absolute address of libc::system by combining leaked QEMU and libc base addresses with static offsets like SYSTEM_OFF (0x58750).
  • Callback forgery creates a fake MemoryRegionOps structure with its write pointer redirected to the resolved system() function.
  • Command execution triggers via CMD_MEDIA_OP, causing the host to execute system("id>/tmp/qemu_cxl_escape_marker") and create the marker file.
  • All exploit logic resides in stage2.c within the bikini/exploitarium repository, demonstrating a complete guest-to-host escape chain.

Frequently Asked Questions

How does the guest read arbitrary host memory without crashing the hypervisor?

The exploit leverages a missing bounds check in the GET_LOG mailbox command handler. While the handler validates the source range against log buffer sizes, it fails to validate the destination pointer. By supplying a carefully crafted offset calculated as GET_LOG_OOB_BASE_OFFSET + (rel>>2), the guest can read from any host memory address relative to the log buffer base. This is implemented in the leak_rel() function in stage2.c without causing crashes because the read operation stays within valid mapped memory regions of the QEMU process.

What specific QEMU configuration is required to reproduce this exploit?

The target system must expose a CXL Type-3 device with mailbox support enabled. The run.sh script in the repository configures QEMU with the necessary PCI device parameters to instantiate the vulnerable CXLType3Dev structure. The guest must have I/O port access to the mailbox registers defined at lines 15-22 of stage2.c, including MBOX_PAYLOAD and MBOX_CTRL, to send crafted commands to the host's mailbox handler.

Why is the SYSTEM_OFF offset hardcoded to 0x58750?

The offset 0x58750 represents the static distance between __libc_start_main and system() in the specific libc version targeted by this proof-of-concept. The exploit first leaks the address of __libc_start_main from the GOT, then subtracts LIBC_START_MAIN_OFF to find the libc base, and finally adds SYSTEM_OFF to locate system(). This calculation, performed in _start() around lines 90-110 of stage2.c, assumes a known libc version; different distributions may require offset adjustments based on their specific libc binaries.

Can this technique be adapted to execute commands other than the marker "id" command?

Yes. The exploit defines the executed command in the host_cmd array at lines 66-68 of stage2.c. Currently set to "id>/tmp/qemu_cxl_escape_marker", this string can be modified to execute any shell command available in the host's environment. The fake_write_call function passes this string pointer directly to the forged system() callback, enabling arbitrary command execution with the privileges of the QEMU host process.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →