# DF-5198 VERDICT — fire_saver() unchecked set_video_mode() (DF-5114/DF-5197 twin)

**Status: not_reproduced (latent) · impact: none on stock kernel · Info/memcorrupt**

## The defect (certain)

`fire_saver()` (sys/dev/misc/syscons/fire/fire_saver.c:60) on the first blank
episode runs, inside `if (blanked <= 0)`:

- fire_saver.c:69 `set_video_mode(adp, M_VGA_CG320);` — return value ignored
- fire_saver.c:82 `load_palette(adp, fire_pal);`
- fire_saver.c:85 `vid = (u_char *) adp->va_window;`
- fire_saver.c:104 `memcpy(vid, buf, X_SIZE * Y_SIZE);` — 64000 bytes, every
  tick (scrn_timer at 25 Hz → syscons.c:2229 `(*current_saver)(sc, TRUE)` →
  scsplash_saver → splash(adp, TRUE)).

Caller contract: `scsplash_saver()` first calls `set_scrn_saver_mode(scp, -1,
NULL, 0)` (syscons.c:2559) — mode `-1` deliberately does **not** change the
video mode (syscons.c:2646-2648); the mode switch is the saver's job. On
unblank, syscons restores via `restore_scrn_saver_mode(scp, TRUE)` →
`set_mode()` + `load_palette(sc->palette)` (syscons.c:2691-2693), and
`fire_saver(adp, FALSE)` only resets `blanked` (fire_saver.c:105-107). That
unblank half is correct and was verified live (text console came back and
stayed functional after two blank cycles).

If `set_video_mode()` fails, `adp->va_window` still points at the 32 KB
text-mode aperture (0xB8000), so the 64000-byte `memcpy` writes ~32 KB past
the window into 0xC0000+ (video-BIOS shadow / adapter MMIO region — not
kernel RAM). Family impact note (from DF-5114): "latent out-of-window video
MMIO writes (not kernel RAM)". Same here; fire additionally re-blits the full
64000 bytes on **every** tick, so the overrun would be repeated at 25 Hz.

## Why the failure branch is unreachable on the stock kernel (traced)

`vga_set_mode()` (sys/dev/video/fb/vga.c:997) has exactly two failure paths:

1. `prologue(adp, V_ADP_MODECHANGE, ENODEV)` — vga.c:1003, macro vga.c:414:
   `if (!vga_init_done || !((adp)->va_flags & (flag))) return (err)`.
   `V_ADP_MODECHANGE` is set once during adapter init (vga.c:684) and never
   cleared at runtime (only read, vga.c:888/1003/2007). `vga_init_done` is
   only ever set at configure time.
2. `if (vga_get_info(adp, mode, &info)) return EINVAL;` — vga.c:1005-1006;
   `vga_get_info` (vga.c:880) rejects the mode iff `get_mode_param(mode) ==
   NULL` when `V_ADP_MODECHANGE` is set (vga.c:888-893).

`fire_initialise()` (fire_saver.c:112-125) already required
`get_mode_info(adp, M_VGA_CG320, &info) == 0` at module load on the same
adapter (`splash_adp` is installed from syscons' own `sc->adp` via
`splash_init(sc->adp, scsplash_callback, sc)` at syscons attach;
`splash_register` → `splash_test` → `fire_initialise(splash_adp)`,
splash.c:108-121, 83-87). Same adapter flag, same param table ⇒ deterministic
agreement: after the load-time gate passes, the blank-time mode set cannot
fail on a stock single-adapter system. Remaining theoretical triggers — an
adapter whose set_mode can fail asynchronously, or a multi-console setup
where `splash_adp` diverges from the `sc->adp` the saver actually runs on —
have no in-tree instantiation (the dragon/warp twins carry the identical
caveat).

## What was run on the guest (live benign path)

DragonFly 6.5-DEVELOPMENT X86_64_GENERIC, sc0 VGA + vga0 (ISA), QEMU/KVM.

1. `kldload fire_saver` → KLDLOAD_OK — `fire_initialise`'s `get_mode_info`
   gate passed and the module registered as the LKM saver.
2. `vidcontrol -t 1 < /dev/ttyv0` → ARMED; slept 5 s → the saver dispatched
   (~125 fire ticks: bottom-row seed, fade loop, 64000-byte blit each).
3. `vidcontrol -t 600` (CONS_BLANKTIME touches the saver →
   `sc_touch_scrn_saver` → next timer tick stops it, syscons.c:2192-2207) →
   REARMED_OK, STILL_ALIVE — text mode restored cleanly.
4. Second cycle: `-t 3`, 8 s of fire, disarm → SECOND_CYCLE_DONE.
5. `kldunload fire_saver` → UNLOADED (unload path: splash_unregister →
   splash_term → scsplash_callback(SPLASH_TERM) → remove_scrn_saver →
   stop_scrn_saver; exercised without the DF-5195 deadlock because the saver
   was not blanked at unload time).
6. Serial log (vm.sh log): no panic, no console wedge.

Result: **the code path is live and behaves when `set_video_mode` succeeds;
the bad branch cannot be made to execute on a stock kernel.** Honest
classification: not_reproduced / impact none / latent hardening — consistent
with the orchestrator's DB rows for DF-5114 and DF-5197 (`untested`, impact
"latent out-of-window video MMIO writes (not kernel RAM)" / none).

No uid=0 chain exists for this class: the primitive is MMIO-window-relative,
not kernel-memory-relative, and arming it at all requires root (`kldload`).
Fix validation (baseline/patched kernel rebuild) is not applicable to a
non-reproducing latent finding; `fix.diff` is authored against the read-only
`sys/` tree for upstream.

## Everything else checked in fire_saver.c (pass 2 negative result)

- Fade loop writes bounded: max `x + y*X_SIZE` = 319+199*320 = 63999 < 64320
  (fire_saver.c:94). Left-edge read `(x-1)+(y+1)*320` ≥ 319, in bounds.
  Right/bottom read `(x+1)+200*320` = 64320 = the known DF-2193 one-past-end
  read — not re-reported.
- Bottom-row seed: indices 64000..64319 in bounds; `krandom() % 160 + 96`
  ∈ [96,255] fits u_char (krandom is libkern random.c:50).
- Palette: indices ≤ 767 within fire_pal[768]; red/green wrap at entry 255
  (256→0) is cosmetic only; `load_palette` passes a 768-byte table.
- `memcpy` is exactly 64000 bytes = mode-13h visible surface (64 KB+ aperture)
  — no warp-DF-5196-style blit off-by-one.
- Unblank/mode-restore ownership is correct (syscons restores, fire only
  resets `blanked`); module unload path exercised live.
- Missing `crit_enter()` around the first-blank block (dragon has one,
  dragon_saver.c:191-196) is harmless: the saver is dispatched only from the
  single `scrn_timer` callout under `syscons_lock` (syscons.c:2182-2232).
- `blanked <= 0` / `blanked = 0` re-init logic is idempotent; no
  double-entry, no state carried across module reloads (module statics).

## Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest `make -j6 nativekernel KERNCONF=X86_64_GENERIC` completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
