vkbd_term() ignores kbd_unregister() failure and frees a possibly still-registered/busy keyboard (latent UAF)
Summary
vkbd_term discards kbd_unregister's return and unconditionally kfrees state/keymaps/kbd. kbd_unregister returns EBUSY when a KBDIO_UNLOADING callback refuses to release - in that case the keyboard is still in keyboard[]/kbdsw[] and still BUSY when freed: dangling array entries plus a live consumer callback -> UAF on the next dispatch. Latent today (both in-tree handlers always release). Honor-the-error fix.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4301 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.5 KB | β raw | |
| VERDICT.md | β | 1.5 KB | β raw | |
| fix.diff | β | 360 B | view raw | |
| manifest.json | β | 395 B | view raw | |
| verdict.json | β | 1.4 KB | view raw |
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:
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.
DF-4301 verdict
Reproduced?
No β latent by construction: triggering requires a KBDIO_UNLOADING
callback that returns an error, and both in-tree registrars (kbdmux.c:206-227,
syscons.c:891-900) unconditionally release and return 0. Status untested.
Root cause (path:line)
- sys/dev/misc/vkbd/vkbd.c:848 β
kbd_unregister(kbd);return ignored. - sys/dev/misc/vkbd/vkbd.c:850-857 β unconditional kfree of state/keymaps/kbd.
- sys/dev/misc/kbd/kbd.c:268-277 β the EBUSY path the caller must honor.
Impact
If any keyboard consumer refuses KBDIO_UNLOADING (or the kbd index is inconsistent), the vkbd control fd's close path frees a keyboard that is still registered in keyboard[]/kbdsw[] and still held BUSY by the consumer: use-after-free on the next kbdsw dispatch (console input). Root-triggered (0600 device) + needs a refusing consumer β Low, speculative.
Fix
--- a/sys/dev/misc/vkbd/vkbd.c
+++ b/sys/dev/misc/vkbd/vkbd.c
@@ -845,7 +845,10 @@ vkbd_term(keyboard_t *kbd)
vkbd_state_t *state = (vkbd_state_t *) kbd->kb_data;
- kbd_unregister(kbd);
+ if (kbd_unregister(kbd) != 0)
+ return (EBUSY); /* still registered/busy: do not free */
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.
Fix verification
not_testablefix.diff authored; not buildable-triggerable (latent path)
VERDICT.md diff
Confirmed kernel references
Detail
Evidence (decisive lines)
VERDICT.md path trace (vkbd.c:848-857 vs kbd.c:268-277; kbdmux.c:206 / syscons.c:891 always return 0)
PoC changes
n/a
Verified recommended fix
Honor kbd_unregister()'s return in vkbd_term(): on error do not free.
Verdict
vkbd_term() discards kbd_unregister()'s error and unconditionally frees the keyboard (vkbd.c:848-857); on the kbd.c EBUSY path (KBDIO_UNLOADING refused) this frees a still-registered, still-busy keyboard -> dangling keyboard[]/kbdsw[] + consumer UAF. Latent: no in-tree UNLOADING handler returns an error today.
No comments yet.