mirror of
https://github.com/v12-security/pocs.git
synced 2026-05-16 11:07:43 +00:00
132 lines
4.7 KiB
Markdown
132 lines
4.7 KiB
Markdown
# QEMUtiny
|
|
|
|
## Abstract
|
|
|
|
QEMUtiny is a memory corruption vulnerability in QEMU's implementation of CXL Type-3
|
|
device emulation, reported against QEMU master `007b29752e` and confirmed
|
|
working against `5e61afe` (May 11, 2026).
|
|
|
|
QEMUtiny was discovered autonomously with [V12](https://v12.sh) by Aaron Esau of the
|
|
[V12 security team](https://x.com/v12sec). The PoC was prepared by [@xia0o0o0o](https://xia0.sh/).
|
|
|
|
> Want to find issues like this in your own code? Try V12 at https://v12.sh.
|
|
|
|
The PoC chains two CXL mailbox bugs in `hw/cxl/cxl-mailbox-utils.c`: an
|
|
out-of-bounds read in `GET_LOG`, followed by an out-of-bounds write in
|
|
`SET_FEATURE`.
|
|
|
|
1. **OOB read:** `cmd_logs_get_log()` treats the CEL log offset as an array
|
|
index in the `memmove()` source expression even though the CXL mailbox
|
|
offset is in bytes.
|
|
2. **OOB write:** `cmd_features_set_feature()` accepts byte offsets into
|
|
several small feature write-attribute structures without checking that
|
|
`offset + bytes_to_copy` stays inside the selected structure.
|
|
|
|
We reported the bugs upstream. Maintainers state CXL support is currently for at non-virtualization use cases, so we feel comfortable release the PoC publicly.
|
|
|
|
The included `poc.c` is a working exploit that drives the emulated CXL mailbox from the guest through the device BAR. It depends on offsets for the specific QEMU build and host libc layout.
|
|
The exploit can be weaponized to work reliably across many QEMU versions using the OOB read to scan memory. However this is out of scope for this PoC.
|
|
|
|
## "QEMUtiny"?
|
|
|
|
QEMU + Mutiny.
|
|
|
|
## Building
|
|
|
|
```
|
|
gcc -O2 -Wall -Wextra -o exp poc.c
|
|
```
|
|
|
|
The reproducer must be run as root inside the guest because it writes PCI config
|
|
space and mmaps the CXL device BAR through sysfs.
|
|
|
|
```
|
|
sudo ./exp
|
|
```
|
|
|
|
One-line version:
|
|
|
|
```
|
|
git clone https://github.com/v12-security/pocs.git && cd pocs/qemu && gcc -O2 -Wall -Wextra -o exp poc.c && sudo ./exp
|
|
```
|
|
|
|
## Test Setup
|
|
|
|
Use `./run_qemu_shell.sh`. Then in the guest, use `/exp`
|
|
|
|
|
|
`poc.c` assumes the CXL Type-3 device appears in the guest at:
|
|
|
|
```
|
|
/sys/bus/pci/devices/0000:35:00.0
|
|
```
|
|
|
|
and that BAR2 is exposed as:
|
|
|
|
```
|
|
/sys/bus/pci/devices/0000:35:00.0/resource2
|
|
```
|
|
|
|
If your guest enumerates the device at a different BDF, update the two sysfs
|
|
paths in `main()`.
|
|
|
|
## How It Works
|
|
|
|
1. **Mailbox access.** The guest enables PCI memory decoding for the CXL device,
|
|
maps BAR2, and sends CXL mailbox commands by writing the mailbox payload,
|
|
command, and control registers directly.
|
|
|
|
2. **CEL out-of-bounds read.** `cmd_logs_get_log()` checks the requested CEL
|
|
range as if `offset` were a byte offset, but then performs pointer arithmetic
|
|
on `cci->cel_log` as a `struct cel_log *`. `poc.c` uses
|
|
`GET_LOG_OOB_BASE_OFFSET` to land just past the CEL buffer and read adjacent
|
|
QEMU CXL state.
|
|
|
|
3. **QEMU address discovery.** The out-of-bounds CEL read leaks a CXL mailbox
|
|
command handler pointer and the `CXLType3Dev` heap address. The handler
|
|
pointer gives the QEMU PIE base for this build.
|
|
|
|
4. **Rank sparing overflow.** The demo sends `SET_FEATURE / RANK_SPARING` with
|
|
a non-zero feature offset and a large payload. The rank sparing case copies
|
|
into `ct3d->rank_sparing_wr_attrs + hdr->offset` without bounding the copy to
|
|
`sizeof(ct3d->rank_sparing_wr_attrs)`, so the payload continues into later
|
|
`CXLType3Dev` fields.
|
|
|
|
5. **Fake memory dispatch state.** The overflowed payload plants enough fake
|
|
`FlatView`, dispatch, section, `MemoryRegion`, and `MemoryRegionOps` state
|
|
for the sanitize path to call a controlled `MemoryRegionOps.write` callback.
|
|
|
|
6. **Callback trigger.** `MEDIA_OPERATIONS / SANITIZE` starts a background
|
|
operation. When the sanitize worker reaches `address_space_set()`, it walks
|
|
the corrupted dispatch state and invokes the forged write callback. The demo
|
|
first uses this to call `memmove()` and leak libc, then repoints the callback
|
|
to `system("/bin/bash")`.
|
|
|
|
## Affected Code Paths
|
|
|
|
The missing `SET_FEATURE` bounds check affects the PPR paths and the sparing
|
|
write-attribute paths:
|
|
|
|
- `soft_ppr_wr_attrs`
|
|
- `hard_ppr_wr_attrs`
|
|
- `cacheline_sparing_wr_attrs`
|
|
- `row_sparing_wr_attrs`
|
|
- `bank_sparing_wr_attrs`
|
|
- `rank_sparing_wr_attrs`
|
|
|
|
`patrol_scrub_wr_attrs` already has the intended style of bounds check.
|
|
|
|
## Affected Versions
|
|
|
|
The full QEMUtiny chain uses two bugs.
|
|
|
|
- **OOB read:** the vulnerable `GET_LOG` path was introduced by
|
|
`056172691b` (`hw/cxl/device: Add log commands (8.2.9.4) + CEL`), first
|
|
released in QEMU `v7.1.0`.
|
|
- **OOB write:** the vulnerable PPR and memory sparing `SET_FEATURE` paths were
|
|
introduced by `5e5a86bab8` and `da5cafdc4d`, released in QEMU v11.0.0
|
|
on March 18, 2026.
|
|
|
|
## Credit
|
|
|
|
Found with V12 by Aaron Esau of the V12 security team.
|