# DF-4644 — fb.c adapter/vidsw/vidcdevsw array lifecycle is not consistently
# serialized (UAF window on vid_realloc_array + register index race)

- File: `sys/dev/video/fb/fb.c:76-122, 150-181, 183-200, 230-245, 285-291`
- Severity: Low (race window real, trigger platform-gated)
- Status: **untested** — triggering requires ≥2 video adapters registering/
  unregistering concurrently (second card at attach, or a loadable fb driver
  module); the stock x86 guest has exactly one built-in `vga0` and FB_INSTALL_CDEV
  is not compiled (LINT64-only), so the race cannot be driven on the audit guest.

## The defects

1. **Array realloc frees outside the serialization token.**
   `vid_realloc_array()` (fb.c:76-122) allocates new arrays, copies, then
   `kfree(adapter)`/`kfree(vidsw)`/`kfree(vidcdevsw)` (fb.c:104-108) and swaps the
   global pointers (fb.c:110-114) — all under `crit_enter()` only, which is
   per-CPU and does not stop other CPUs. It is called from `vid_register()`
   (fb.c:162) *before* `lwkt_gettoken(&vga_token)` (fb.c:168).
2. **Tokenless readers.** `vid_find_adapter()` (fb.c:235-243) scans `adapter[i]`
   with no token; `vid_get_adapter()` (fb.c:288-290) reads `adapter[index]` with
   no token; `vid_allocate()` (fb.c:253-261) scans/updates under `crit_enter()`
   only. A concurrent realloc on another CPU frees the array underneath them →
   use-after-free reads. Note `vid_unregister()` *does* take the token
   (fb.c:186-198) and writes `adapter[va_index] = NULL` (fb.c:196) — a write that
   can land in the **freed** old array if realloc swapped it in between →
   use-after-free write window.
3. **Concurrent registration index collision.** Two CPUs in `vid_register()` both
   scan for a free slot (fb.c:157-160) before either holds the token (fb.c:168);
   both can claim the same `index` and both write `adapter[index]`/`vidsw[index]`
   (fb.c:172-173). The loser's adapter keeps a `va_index` owned by the winner →
   later `fb_commonioctl()` dispatch `(*vidsw[adp->va_index]->get_info)(...)`
   (fb.c:706, 714, 723) calls one driver's switch on another driver's
   `video_adapter_t` → type confusion.

On the stock x86 guest only one adapter ever registers (`vga0`, built-in,
boot-time), so `vid_realloc_array()` never even runs (adapters stays 1). The race
is reachable only on multi-adapter or module-load configurations.

## Supporting guest probe (dispatch sanity, not a race PoC)

`fb_dispatch_probe.c` + `probe_run.log` in this directory were run as root on the
audit guest against `/dev/ttyv0` to validate the *surrounding* dispatch analysis:
FBIO_ADPINFO returns every field kernel-written with `va_reserved` fully zeroed
(by the ioctl layer's IOC_OUT bzero, sys/kern/sys_generic.c:685-690) — no
uninitialized-leak class beyond the known DF-1786 pointer fields — and
FBIO_MODEINFO rejects out-of-range mode ids (999, -5, 0x7fffffff) with ENODEV,
confirming `vga_get_info`'s `get_mode_param()` bounds (vga.c:527-537) hold at
runtime.

## Fix

Serialize the whole array lifecycle with `vga_token`: take the token in
`vid_register`/`vid_realloc_array`/`vid_find_adapter`/`vid_get_adapter`/
`vid_allocate` (or a dedicated lock), and re-scan the free slot after acquiring
it.
