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

Lock-order inversion: kbd_token acquired before kb_lock in kbd.c cdev/register paths versus kb_lock before kbd_token in the syscons/vkbd ioctl paths (AB-BA deadlock)

Summary

genkbdioctl takes kbd_token then calls kbd_ioctl which takes kb_lock; kbd_register likewise holds the token across the dispatch. Meanwhile syscons sc_ioctl calls kbd_ioctl WITHOUT the token, so kb_lock is held first and genkbd_commonioctl takes kbd_token inside the fall-through; vkbd same. A privileged /dev/kbdN ioctl or module-load thread (token->lock) deadlocks against an unprivileged ttyv console ioctl (lock->token) - lockmgr sleeps while the lwkt token stays owned, wedging both threads and stalling other CPUs spinning on the token. Race-window gated. Establish-one-order fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4298 Β· 2 files
FileTypeDescriptionSize
README.md file 2.6 KB ↓ raw
verdict.json file 1.9 KB view raw
README.md file
↓ download raw

DF-4298 β€” kbd_token ↔ kb_lock lock-order inversion (AB-BA)

Status: skipped (Low, race-window deadlock; not practically demonstrable reliably on the single guest β€” static protocol violation documented with line-exact citations).

Static analysis

Two conflicting acquisition orders exist for lwkt_token kbd_token and the per-keyboard kb_lock (lockmgr):

Order A β€” kbd_token β†’ kb_lock

  • genkbdioctl (sys/dev/misc/kbd/kbd.c:802) takes kbd_token, then calls kbd_ioctl (kbd.c:808) β†’ kbdsw.c:209-211 KBD_LOCK(kbd) β†’ lockmgr(kb_lock, LK_EXCLUSIVE) β†’ driver ioctl.
  • kbd_register (kbd.c:191-246) holds kbd_token across kbd_ioctl(mux, KBADDKBD, ...) (kbd.c:221/238) β†’ same KBD_LOCK on the mux.

Order B β€” kb_lock β†’ kbd_token

  • syscons sc_ioctl (sys/dev/misc/syscons/syscons.c:1630-1631) calls kbd_ioctl(sc->kbd, cmd, data) without kbd_token β†’ KBD_LOCK taken first β†’ driver ioctl (e.g. atkbd_ioctl default case, atkbd.c:1113) β†’ genkbd_commonioctl (kbd.c:999) takes kbd_token while kb_lock is held.
  • Same shape: vkbd_ioctl holds VKBD_LOCK (vkbd.c state lock) then calls genkbd_commonioctl β†’ kbd_token.

Deadlock shape

Thread A (root, ioctl on /dev/kbdN): holds kbd_token, blocks acquiring kb_lock[X]. Thread B (unprivileged, ioctl on /dev/ttyv*): holds kb_lock[X], blocks acquiring kbd_token in genkbd_commonioctl.

lockmgr sleeps while lwkt_token stays owned by the sleeping thread β‡’ both threads wedge (and any other CPU spinning on kbd_token stalls). Triggering requires precise contention between a cdev ioctl (or keyboard registration at module load) and a console ioctl β€” hence Low/likely (certain as a protocol violation, racy as an exploit).

Make genkbd_commonioctl/genkbd_get_fkeystr token use consistent: either

  • have sc_ioctl/vkbd_ioctl paths take kbd_token before kbd_ioctl (making kbd_token strictly outermost), or
  • drop kbd_token from genkbdioctl (the kbdsw dispatch is already serialized by kb_lock + registration is token-protected), i.e.:
--- a/sys/dev/misc/kbd/kbd.c
+++ b/sys/dev/misc/kbd/kbd.c
@@ -799,7 +799,6 @@ genkbdioctl(struct dev_ioctl_args *ap)
    keyboard_t *kbd;
    int error;

-   lwkt_gettoken(&kbd_token);
    kbd = kbd_get_keyboard(KBD_INDEX(dev));
    if ((kbd == NULL) || !KBD_IS_VALID(kbd)) {
-       lwkt_reltoken(&kbd_token);
        return ENXIO;
    }

(one of the two; full analysis needed upstream β€” the token protects the keyboard[]/kbdsw[] arrays, kbd_get_keyboard already takes it internally and returns a stable pointer while kb_lock is held by kbd_ioctl).

Fix verification

not_testable
per-fix-DF-4298

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4298/README.md (line-exact citations of both acquisition orders)

PoC changes

no runnable PoC authored; static evidence only

Verified recommended fix

Establish a single lock order: make genkbdioctl drop kbd_token before kbdsw dispatch (kb_lock already serializes) or require kbd_token as the outermost lock in the syscons/vkbd ioctl paths.

Verdict

Skipped per contract (Low-severity lock-protocol finding). The lock-order inversion kbd_token -> kb_lock (kbd.c:802-808 via kbdsw.c:209-211; also kbd.c:191-221 kbd_register calling kbd_ioctl(KBADDKBD) under the token) versus kb_lock -> kbd_token (syscons.c:1630-1631 kbd_ioctl without the token -> atkbd.c:1113 default -> kbd.c:999 genkbd_commonioctl taking the token; vkbd.c VKBD_LOCK -> genkbd_commonioctl same shape) is statically certain as an AB-BA protocol violation, but manifesting it requires winning a narrow race between a privileged /dev/kbdN ioctl (or keyboard registration) and a ttyv console ioctl on the same kernel; no reliable deterministic trigger exists on the single-tenant guest, so no QEMU run was attempted and impact remains a potential two-thread wedge (DoS).