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

> Learn how to trigger libc system on QEMU host from guest using a CXL Type-3 mailbox exploit. Execute arbitrary commands by leveraging an out-of-bounds read vulnerability. Proof of concept available.

- Repository: [bikini/exploitarium](https://github.com/bikini/exploitarium)
- Tags: exploit-guide
- Published: 2026-09-07

---

**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`](https://github.com/bikini/exploitarium/blob/main/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`](https://github.com/bikini/exploitarium/blob/main/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`](https://github.com/bikini/exploitarium/blob/main/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:

```c
/* 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`](https://github.com/bikini/exploitarium/blob/main/stage2.c) lines 66-68【/cache/repos/github.com/bikini/exploitarium/main/qemu-cxl-type3-mailbox-escape-poc/stage2.c#L66-L68】:

```c
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`](https://github.com/bikini/exploitarium/blob/main/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`](https://github.com/bikini/exploitarium/blob/main/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`](https://github.com/bikini/exploitarium/blob/main/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`](https://github.com/bikini/exploitarium/blob/main/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`](https://github.com/bikini/exploitarium/blob/main/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`](https://github.com/bikini/exploitarium/blob/main/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`](https://github.com/bikini/exploitarium/blob/main/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`](https://github.com/bikini/exploitarium/blob/main/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.