# DF-2556 — clist_alloc_cblocks() leaks the previous c_data buffer

## Verdict: REPRODUCED (memory leak / DoS); fix_status = fixed

## Bug
`clist_alloc_cblocks()` (sys/kern/tty_subr.c:48-81) `kmalloc()`s a new `data`
buffer (line 61), copies the old contents, then overwrites `cl->c_data = data`
(line 80) **without ever `kfree()`ing the previous `cl->c_data`**. Every
reallocation to a *different non-zero* size therefore leaks one M_TTYS
allocation. (The `ccmax==0` path correctly calls `clist_free_cblocks()` at :58;
the `ccmax==c_ccmax` path returns early at :56; only the resize path leaks.)

## Live trigger (code-level, unambiguous)
`tty.c:1085 ttsetwater(tp)` is called from the TIOCSETA handler; it calls
`clist_alloc_cblocks(&t_rawq, x)` (:2457) and `clist_alloc_cblocks(&t_outq, x)`
(:2489) where `x` depends on the baud rate. The clist sizes only vary with baud
rate on a driver that sets `t_ispeedwat/t_ospeedwat = (speed_t)-1`, because then
`cps = speed/10` (ttsetwater :2439/2467) and the computed `x` changes. The only
in-tree driver that does this is **sio** (sys/dev/serial/sio/sio.c:2480-2481,
inside the input-buffer-resize path `siosetitm`). A user logged in on a sio
serial port who repeatedly changes the baud rate via `TIOCSETA`/`stty` leaks
kernel memory on every change — an unprivileged memory-exhaustion DoS.

## Guest limitation (why a userspace PoC can't fire here)
This QEMU guest has exactly one sio tty, `/dev/ttyd0`, which is the **kernel
console** (`comconsole`). The console subsystem **locks its baud rate**:
verified `stty -f /dev/ttyd0 9600` and `stty -f /dev/ttyd0 50` return silently
but `stty -f /dev/ttyd0 speed` still reports `115200`; a C `tcsetattr(B300)`
returns rc=0 but `tcgetattr` readback is still `115200`. So the clist size never
changes via TIOCSETA on the console, and no leak can be induced from userspace
on this guest. There is no second / non-console sio port. (For a pty the leak
also cannot fire: pty leaves `speedwat=0`, which makes both `t_rawq` and `t_outq`
sizes baud-rate-independent — `imax(ohiwat, TTMAXHIWAT)` clamps the output size
to a constant, and the input side uses a constant `TTYHOG-512`.)

## Reproduction (primitive confirmed at function level)
Because the live userspace trigger needs hardware this guest lacks, the leak is
demonstrated with a tiny diagnostic module (`leak_mod.c`) that calls
`clist_alloc_cblocks()` directly with alternating sizes (1024 ↔ 16384 shorts)
for 20000 cycles and measures M_TTYS growth. This is function-level primitive
confirmation (same approach used for harness-only findings).

**Unpatched #0 kernel:**
```
M_TTYS BEFORE: 37 allocs / 143K
kldload df2556_leak.ko (20000 resize cycles)
M_TTYS AFTER:  19.6K allocs / 332M
```
~20000 leaked M_TTYS buffers = **332 MB** of leaked kernel memory from 20000
resizes. The leak is unbounded (scales with resize count).

## Fix
`fix.diff` adds a `kfree()` of the previous `cl->c_data` before reassigning, in
`clist_alloc_cblocks()` (sys/kern/tty_subr.c, guarded for the first allocation
where `c_data == NULL`):
```c
	if (cl->c_data != NULL)
		kfree(cl->c_data, M_TTYS);
	cl->c_data = data;
```

## Fix validation (full nativekernel rebuild — tty_subr.c is built-in)
Built `make -j6 nativekernel KERNCONF=X86_64_GENERIC` (rc=0), installed
`kernel.stripped` → `/boot/kernel/kernel` (verified valid ELF, new BuildID
b32b089c, size identical 15705800), rebooted to `#1` (Sun Aug 9 05:09:04 UTC
2026). Re-ran the SAME diagnostic module:
```
PATCHED #1: M_TTYS BEFORE: 37 allocs / 143K
            kldload df2556_leak.ko (20000 resize cycles)
            M_TTYS AFTER:  37 allocs / 143K   (FLAT)
```
**Leak eliminated** — identical workload, M_TTYS growth gone. fix_status=fixed.

## Impact
Unbounded kernel memory leak (M_TTYS) → local memory-exhaustion DoS. Triggerable
unprivileged by any user with access to a sio serial tty (real serial terminal /
modem / console-server port) by repeatedly changing the baud rate; on this guest
the only sio tty is the locked console. Not a memory-corruption primitive (no
attacker-shaped write) — it is a pure resource leak. Medium severity appropriate.
