# DF-4542 — syscons CONS_SET mode change vs tty output: no common lock → reproducible kernel panic (+ UAF-write window)

## Summary

`sc_set_text_mode()` (sys/dev/misc/syscons/scvidctl.c:54-184) tears down
and reallocates the per-vty shadow screen buffer (`scp->vtb`, via
`sc_alloc_scr_buffer()`, free+kmalloc with `wait=TRUE`, i.e. it can
SLEEP mid-mutation) and the scrollback ring (`scp->history`, via
lock-free `sc_alloc_history_buffer()`), and rewrites `scp->xsize/ysize/
xpos/...` while holding only `lwkt`'s `vga_token`.

The tty output path (`scstart()` → `sc_puts()` → terminal emulator
writing `scp->vtb` and pushing scroll lines into `scp->history`) holds
only the `syscons_mtx` **spinlock**.

**The two paths share no lock.** `vga_token` is not held by the output
path and is dropped by LWKT whenever the mode-change thread blocks
inside the M_WAITOK allocation, so the output/render path can execute
against a half-updated `scp`: freed `vtb`/history rings being written by
the emulator (UAF write), torn `xsize` vs. buffer geometry (OOB write).

Under contention this corrupts kernel synchronization state and panics:

```
panic with 1 spinlocks held
panic: assertion "count & TOK_COUNTMASK" failed in _lwkt_reltokref
       at /usr/src/sys/kern/lwkt_token.c:458
cpuid = N
Trace: lwkt_relalltokens() ... lwkt_switch()
```

## Reproduction (audit guest, DragonFly 6.5-DEVELOPMENT, X86_64_GENERIC)

`scstress.c` — N concurrent ioctl threads doing `CONS_SET` text-mode
cycles (80x25 ↔ 80x50 ↔ 80x30 ↔ 80x60) on /dev/ttyv1..4, plus writer
threads spamming `write()` (forcing `sc_puts` + history scroll).

Isolation matrix (60 s each):

| mode | stressors | result |
|------|-----------|--------|
| 1  | CONS_SET + KDSETMODE/KDRASTER churn only (~2.9M ioctls) | clean |
| 2  | writers only (~1.3M writes) | clean |
| 10 | **CONS_SET only + writers** | **panic** (same signature) |
| 3/7| full mix (adds KD_TEXT1/KDRASTER churn, VT_ACTIVATE cycler) | **panic** |

3/3 panics in the CONS_SET+writer configurations, zero panics in any
single-stressor round.  Minimal pair = `CONS_SET` (`sc_set_text_mode`)
concurrent with tty writes (`scstart`/`sc_puts`).

## Threat model

Any process holding a writable fd on a /dev/ttyvN can issue CONS_SET —
`sc_vid_ioctl` performs **no privilege check**; only device permissions
gate it (root-only 0600 on the default ssh-accessible guest; a user
logged in on a real console holds such an fd for their session).  Two
processes (or threads) suffice: one mode-cycling (e.g. what
`vidcontrol`/X startup does), one printing.  Result: reliable local
kernel panic (DoS) with a genuine kernel-heap UAF-write window behind it
(emulator glyphs written into freed M_SYSCONS slabs).

## Fix

`fix.diff` — serialize the mode-change commit against the output path:
export `syscons_lock()/syscons_unlock()`, do the sleeping buffer
allocation first, swap pointers + update scp meta fields + reinit the
emulator under the (reentrant) `syscons_mtx` spinlock, and free old
buffers outside it.  Validated: patched kernel survives the identical
stress that panicked 3/3 (see VERDICT.md).

## Files

* `scstress.c` / `scprobe.c` — sources
* `build.sh` / `run.sh`
* `run.panic{1,2,3}_bootlog.txt` — the three panics
* `env.txt` — guest environment
* `fix.diff` — validated fix (never applied to the read-only sys/ tree;
  applied only inside the guest's /usr/src copy for validation)
