# DF-4158 — vga_save_font/vga_load_font have no internal bounds on (ch, count, fontsize) + guest proof the user-reachable vga.c surface is bounds-checked

File: `sys/dev/video/fb/vga.c` (pass 2, GLM 5.3)

## What (the hardening gap)

`vga_save_font()` / `vga_load_font()` (`vga.c:1189-1233`, `1241-1286`)
validate `page` (0..7) but perform **zero** validation of the glyph index
`ch`, glyph `count`, or their combination with `fontsize`:

```c
if (fontsize == 32) {
    bcopy_toio(data, (uintptr_t)segment + ch*32, fontsize*count);
} else {
    for (c = ch; count > 0; ++c, --count) {
        bcopy_toio(data, (uintptr_t)segment + c*32, fontsize);
```

Every caller today is kernel-internal and clamped (syscons: ch=0,count=256;
`scmouse.c:888` passes the *user-influenced* `sc->mouse_char` as `ch`, which
`MOUSE_MOUSECHAR` pre-clamps to ≤ (unsigned char)-1-4, `scmouse.c:881`), so
this is currently a latent API hazard: one future caller that forwards an
unchecked ch/count gets an OOB write through `ch*32` (int overflow →
negative → sign-extended `uintptr_t`) into arbitrary KVA, or a OOB read of
the `data` source. Filed as Info/defense-in-depth.

## Guest verification (negative-space proof for the rest of the file)

`vgafuzz.c` exercises every *user-reachable* vga.c sink reachable through
syscons (`/dev/ttyvN` → `sc_vid_ioctl` → `vga_dev_ioctl`/`fb_commonioctl`)
with hostile scalars. Results on the live guest (run.log):

- FBIO_SETMODE with {-2^31, -1, 29, 64, 256, 65535, 2^30, 2^31-1} — all
  rejected (mode walk via `get_mode_param()` → NULL → EINVAL, `vga.c:533`,
  `vga.c:893`).
- FBIO_MODEINFO with vi_mode {-1, 29, INT_MAX} — rejected.
- FBIO_GET/SETPALETTE, FBIOGET/FBIOPUTCMAP with index=-1/300/INT_MAX,
  count=-1/INT_MAX/2^30, index+count>256 — all rejected by the
  `get_palette`/`set_palette` gate (`vga.c:1853, 1885`).
- FBIO_SETDISPSTART INT_MAX/INT_MIN and FBIO_SETLINEWIDTH INT_MAX/-1/0 are
  *accepted* but land only in CRTC register bytes and the `va_line_width`
  int (`vga.c:750-752`, `786-800`) — display corruption by the console
  user on themselves, no kernel-memory sink (checked all consumers:
  `planar_fill`/`genfbread` use window_size/maxsize, not line_width
  multiplies that reach memory ops unclamped).
- Guest stayed up after the full battery (dmesg tail clean).
- Permission model: /dev/ttyv0..N are 0600 root:wheel (console-login users
  get the tty chowned — the historical syscons model);
  **/dev/vga0 does not exist** (FB_INSTALL_CDEV is not compiled into
  X86_64_GENERIC; when compiled it is 02660 root:wheel, `vga_isa.c:156`).
- State save/restore is NOT user-reachable at all: no FBIO_*STATE ioctls
  exist; `vga_save_state/load_state` are reached only from
  `isavga_suspend/resume` with kernel-sized buffers (`vga_isa.c:190-207`),
  and `vga_save_state` itself enforces `size < sizeof(adp_state_t)` →
  EINVAL (`vga.c:1432`).

## Fix

`fix.diff`: validate `ch`/`count` against the 512-glyph (0x4000/32) font
page in both functions. Applies cleanly with `git apply`.
