hw/display/cirrus_vga: negative shift exponent in 24bpp transparent pattern color-expand 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.91 (v11.0.0-rc1-25-g3c46691f16-dirty)`
## Description of problem
A guest-triggerable undefined-behavior issue exists in QEMU's Cirrus VGA blitter emulation and can crash the host QEMU process in sanitizer-enabled builds.
The issue is in the 24bpp transparent pattern color-expand path. In `hw/display/cirrus_vga.c`, writing `CIRRUS_MMIO_BLTSTATUS` (offset `0x40`) reaches the BLT start logic through:
```c
case CIRRUS_MMIO_BLTSTATUS:
cirrus_vga_write_gr(s, 0x31, value);
break;
```
`gr[0x31]` is handled by `cirrus_write_bitblt()`, which invokes `cirrus_bitblt_start()` when the `CIRRUS_BLT_START` bit changes from 0 to 1:
```c
static void cirrus_write_bitblt(CirrusVGAState * s, unsigned reg_value)
{
unsigned old_value;
old_value = s->vga.gr[0x31];
s->vga.gr[0x31] = reg_value;
if (((old_value & CIRRUS_BLT_RESET) != 0) &&
((reg_value & CIRRUS_BLT_RESET) == 0)) {
cirrus_bitblt_reset(s);
} else if (((old_value & CIRRUS_BLT_START) == 0) &&
((reg_value & CIRRUS_BLT_START) != 0)) {
cirrus_bitblt_start(s);
}
}
```
Inside `cirrus_bitblt_start()`, when the guest programs BLT state for `PATTERNCOPY | COLOREXPAND | TRANSPARENTCOMP` with 24bpp, the code selects the 24bpp transparent pattern color-expand path:
```c
s->cirrus_rop =
cirrus_colorexpand_pattern_transp[rop_to_index[blt_rop]]
[s->cirrus_blt_pixelwidth - 1];
```
In `hw/display/cirrus_vga_rop2.h`, the 24bpp implementation derives a shift count directly from guest-controlled BLT state:
```c
#if DEPTH == 24
int dstskipleft = s->vga.gr[0x2f] & 0x1f;
int srcskipleft = dstskipleft / 3;
#else
int srcskipleft = s->vga.gr[0x2f] & 0x07;
int dstskipleft = srcskipleft * (DEPTH / 8);
#endif
...
bitpos = 7 - srcskipleft;
...
if ((bits >> bitpos) & 1) {
PUTPIXEL(s, addr, col);
}
```
With `gr[0x2f] = 0x1e`, `dstskipleft = 30`, `srcskipleft = 10`, and `bitpos = -3`. The subsequent right shift uses a negative exponent and triggers UBSan:
```text
runtime error: shift exponent -3 is negative
```
From a security perspective, guest-controlled device state reaches an unsafe host-side shift operation without validating the resulting shift count. In sanitizer-enabled builds this is a guest-triggerable host crash/DoS. In non-sanitized builds it still indicates insufficient hardening in a guest-reachable device emulation path.
We initially discovered it through fuzzing in version 10.2.50, and it appears to still exist in version 10.2.91.
Affected source paths:
- `hw/display/cirrus_vga.c`
- `hw/display/cirrus_vga_rop2.h`
## Steps to reproduce
1. Build QEMU with UBSan enabled.
2. Start QEMU with:
```bash
./qemu-system-i386 \
-machine q35,accel=qtest \
-nodefaults \
-display none \
-serial none \
-monitor none \
-device cirrus-vga,addr=03.0,blitter=on \
-qtest stdio <<'EOF'
outl 0xcf8 0x80001814
outl 0xcfc 0x10000000
outl 0xcf8 0x80001804
outl 0xcfc 0x00000007
outb 0x3c4 0x07
outb 0x3c5 0x05
writeb 0x10000108 0x1f
writeb 0x10000109 0x00
writeb 0x1000010a 0x00
writeb 0x1000010b 0x00
writeb 0x1000010c 0x20
writeb 0x1000010d 0x00
writeb 0x1000010e 0x20
writeb 0x1000010f 0x00
writeb 0x10000110 0x00
writeb 0x10000111 0x01
writeb 0x10000112 0x00
writeb 0x10000114 0x00
writeb 0x10000115 0x00
writeb 0x10000116 0x00
writeb 0x10000117 0x1e
writeb 0x10000118 0xe8
writeb 0x1000011a 0x00
writeb 0x1000011b 0x00
writeb 0x10000140 0x00
writeb 0x10000140 0x02
EOF
```
3. Observe that QEMU reaches the Cirrus BLT start path and triggers UBSan with a negative shift exponent in `hw/display/cirrus_vga_rop2.h`.
## Additional information
Simplified fuzzing log:
```text
../../third_party/qemu/hw/display/cirrus_vga_rop2.h:216:23: runtime error: shift exponent -1 is negative
#0 cirrus_colorexpand_pattern_transp_0_24
#1 cirrus_bitblt_common_patterncopy
#2 cirrus_bitblt_videotovideo_patterncopy
#3 cirrus_bitblt_videotovideo
#4 cirrus_bitblt_start
#5 cirrus_vga_write_gr
#6 cirrus_mmio_blt_write
#7 memory_region_write_accessor
#8 access_with_adjusted_size
#9 memory_region_dispatch_write
```
The core issue is that the shift count is derived from guest-controlled BLT register state (`gr[0x2f]`) without checking that it is valid for the 24bpp transparent pattern color-expand path before executing the shift.
<!--The line below ensures that proper tags are added to the issue.
Please do not remove it.-->
issue