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