CXLipse is a standalone reproduction package for a QEMU CXL Type-3
Set Feature out-of-bounds write that can be driven from a guest into
host-side QEMU process memory corruption. In the verified local setup, the
exploit replaces the QEMU process with /bin/sh and proves VM escape by
comparing /bin/ls output from the escaped shell with /bin/ls output on the
host.
The verified vulnerable QEMU target is commit
de5d8bfd6105d3dd3ae668df9762df244a6d1506
(v11.0.0-1713-gde5d8bfd61 before local reproduction patches).
This repository is intended for authorized vulnerability reproduction, patch validation, and defensive research in a local lab.
poc/poc_cxl_vm_escape.c- guest-side PoC source.poc/prepare_buildroot_rootfs.sh- builds the PoC and installs it into a Buildroot ext2 rootfs.poc/run_qemu_cxl_buildroot.sh- launches the tested q35/KVM/CXL Type-3 target configuration.poc/run_reproducer.sh- automates the guest run and verifies escape by exact hostlscomparison.tools/update_constants.py- rewrites exploit constants from a QEMU[CXL_DEBUG]layout line andexecv@plt.scripts/build_qemu_buildroot.sh- downloads and builds the pinned QEMU and Buildroot target used by the reproducer.patches/qemu-cxl-layout-debug.patch- optional QEMU patch that prints the layout values needed bytools/update_constants.py.docs/technical-report.md- root cause, impact, and reproduction details.artifacts/minimal_poc_output.txt- sanitized output from a verified run.
- x86_64 Linux host with KVM available.
- A vulnerable QEMU checkout and build. The verified local target was commit
de5d8bfd6105d3dd3ae668df9762df244a6d1506. - Buildroot kernel and ext2 rootfs under the QEMU tree, or equivalent paths
supplied through
BZIMAGEandROOTFS. gcc,debugfs,expect,objdump,perl, andsetarch.- Privileged guest access to the CXL PCI resource used by the PoC. The default
is
/sys/bus/pci/devices/0000:35:00.0/resource2.
cd /path/to/CXLipse
scripts/build_qemu_buildroot.sh
make reproduceFor a different QEMU checkout:
make prepare QEMU_SRC=/path/to/qemu
make reproduce QEMU_SRC=/path/to/qemuThe successful reproducer ends with:
VM_ESCAPE_CONFIRMED: escaped shell ls matched host ls
Host lab
|
| make prepare
v
+-----------------------------+
| Buildroot ext2 rootfs |
| /root/poc_cxl_vm_escape |
| serial root shell enabled |
+-------------+---------------+
|
| make reproduce
v
+-----------------------------+ MMIO mailbox writes
| QEMU q35/KVM guest | -----------------------------+
| CXL Type-3 device exposed | |
+-------------+---------------+ |
| v
| guest PoC opens resource2 +-----------------------------+
| and sends CXL mailbox commands | QEMU CXL Type-3 model |
| | cmd_features_set_feature() |
| +-------------+---------------+
| |
| Set Feature #1: place fake dispatch objects |
| Set Feature #2: corrupt dc.host_dc_as/regions |
| v
| +-----------------------------+
| | Out-of-bounds memcpy past |
| | rank_sparing_wr_attrs |
| +-------------+---------------+
| |
| Clear Poison triggers address_space_write() |
+------------------------------------------------+
|
v
+-----------------------------+
| attacker-shaped dispatch |
| calls execv("/bin/sh", ...) |
+-------------+---------------+
|
v
+-----------------------------+
| escaped host shell |
| reproduce: compare host ls |
+-----------------------------+
The host-code-execution proof intentionally uses concrete QEMU process
addresses. If you rebuild QEMU or change the launch configuration, update the
constants in poc/poc_cxl_vm_escape.c.
- Apply the debug patch to the QEMU tree:
cd /path/to/qemu
git apply /path/to/CXLipse/patches/qemu-cxl-layout-debug.patch- Rebuild QEMU and boot once with
poc/run_qemu_cxl_buildroot.sh. The QEMU stderr log should contain a line like:
[CXL_DEBUG] ct3d=0x... rank_sparing_wr_attrs=0x... host_dc_as=0x... regions[1]=0x...
- Update the PoC constants from that log:
cd /path/to/CXLipse
tools/update_constants.py artifacts/qemu_stderr.log \
--source poc/poc_cxl_vm_escape.c \
--qemu-bin /path/to/qemu/build/qemu-system-x86_64The helper also computes execv@plt from the QEMU binary, assuming the default
setarch -R PIE base 0x555555554000.
The bootstrap script downloads and builds a local target under work/:
cd /path/to/CXLipse
scripts/build_qemu_buildroot.shBy default it:
- clones QEMU from
https://gitlab.com/qemu-project/qemu.git; - checks out
de5d8bfd6105d3dd3ae668df9762df244a6d1506; - applies
patches/qemu-cxl-layout-debug.patch; - configures QEMU with
--enable-asan --target-list=x86_64-softmmu; - downloads Buildroot
2024.02.3; - builds
qemu_x86_64_defconfigwith an ext2 rootfs and automatic serial root shell for reproducibility; - installs
/root/poc_cxl_vm_escapeinto the rootfs.
Useful overrides:
WORKDIR=/tmp/cxlipse-build JOBS=16 scripts/build_qemu_buildroot.sh
scripts/build_qemu_buildroot.sh --skip-layout-patch
scripts/build_qemu_buildroot.sh --qemu-dir /path/to/qemu --skip-qemuAfter the build, if the layout constants differ, run one reproducer attempt to
capture artifacts/qemu_stderr.log, update constants with
tools/update_constants.py, then rerun make prepare reproduce.
make prepare modifies the Buildroot rootfs used for testing:
- installs
/root/poc_cxl_vm_escape; - optionally enables an automatic serial root shell so the expect-based reproducer can run unattended.
This rootfs change is only test harness setup. It is not part of the QEMU CXL memory-corruption bug.
make reproduce starts QEMU, runs the guest PoC, drives the escaped host
shell, and compares:
/bin/ls -1A --color=neverfrom the escaped shell against the same command run by the host-side reproducer. The VM escape is considered proven only if those outputs match exactly.
cmd_features_set_feature() derives the copy size from the guest-controlled
mailbox payload length:
bytes_to_copy = len_in - sizeof(CXLSetFeatureInHeader);Some feature paths, such as patrol scrub and ECS, validate
hdr->offset + bytes_to_copy against the destination structure size before
copying. Several PPR and memory sparing paths do not. The rank sparing path is
the one used by this PoC:
memcpy((uint8_t *)&ct3d->rank_sparing_wr_attrs + hdr->offset,
mem_sparing_write_attrs, bytes_to_copy);Both hdr->offset and bytes_to_copy are guest-controlled, so the guest can
write beyond rank_sparing_wr_attrs into adjacent CXLType3Dev
dynamic-capacity state. The PoC corrupts dc.host_dc_as and dc.regions[],
then triggers address_space_write() through attacker-shaped QEMU memory
dispatch structures.
Apply the same bounds-check pattern used by patrol scrub and ECS to every PPR
and memory sparing Set Feature path before each memcpy(). Each check should
use the exact destination field size.