# DF-4301 — vkbd_term() ignores kbd_unregister() failure and frees a possibly still-registered/busy keyboard

## Finding
`vkbd_term()` (sys/dev/misc/vkbd/vkbd.c:848-857) calls
`kbd_unregister(kbd)` and discards its return value, then unconditionally
frees `state`, the keymaps, and `kbd` itself:

```c
	kbd_unregister(kbd);		/* return value ignored */
	VKBD_LOCK_DESTROY(state);
	bzero(state, sizeof(*state));
	kfree(state, M_VKBD);
	kfree(kbd->kb_keymap, M_VKBD);
	...
	kfree(kbd, M_VKBD);
```

`kbd_unregister()` (sys/dev/misc/kbd/kbd.c:251-286) can return EBUSY
(a registered KBDIO_UNLOADING callback refused to release the keyboard,
kbd.c:268-277) or ENOENT (index mismatch). On EBUSY the keyboard is
still registered in `keyboard[]`/`kbdsw[]` and still BUSY — freeing it
leaves dangling `keyboard[kbd->kb_index]`/`kbdsw[]` entries and a live
consumer (kbdmux/syscons callback) pointing at freed memory → UAF on the
next console key poll.

Today no in-tree KBDIO_UNLOADING handler returns an error (kbdmux.c:206
and syscons.c:891 both release and return 0), so the free-while-busy is
latent; any future/3rd-party keyboard consumer that refuses UNLOADING,
or a kbdsw[] array mismatch, turns it into a live UAF. Also note
`vkbd_term` does not use KBD_ALWAYS_LOCK etc. — the ignored error is the
defect.

## Status
Analysis-only (speculative): no in-tree refusal path exists to trigger
it; filed for the missing error handling on a lifetime-critical call.

## Fix
Propagate the error and refuse to free when still registered/busy.
