β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4301

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4301 Β· 5 files
FileTypeDescriptionSize
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.

VERDICT.md
↓ download raw

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)

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_testable
baseline no→ patch + rebuild →patched clean

fix.diff authored; not buildable-triggerable (latent path)

VERDICT.md diff
↓ fix.diffper-fix-DF-4301

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.