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)
PoC verification
Evidence pack
findings/poc/DF-4298 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 2.6 KB | β raw | |
| verdict.json | file | 1.9 KB | view 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) takeskbd_token, then callskbd_ioctl(kbd.c:808) βkbdsw.c:209-211KBD_LOCK(kbd)βlockmgr(kb_lock, LK_EXCLUSIVE)β driverioctl.kbd_register(kbd.c:191-246) holdskbd_tokenacrosskbd_ioctl(mux, KBADDKBD, ...)(kbd.c:221/238) β sameKBD_LOCKon the mux.
Order B β kb_lock β kbd_token
- syscons
sc_ioctl(sys/dev/misc/syscons/syscons.c:1630-1631) callskbd_ioctl(sc->kbd, cmd, data)withoutkbd_tokenβKBD_LOCKtaken first β driver ioctl (e.g.atkbd_ioctldefault case, atkbd.c:1113) βgenkbd_commonioctl(kbd.c:999) takeskbd_tokenwhilekb_lockis held. - Same shape:
vkbd_ioctlholdsVKBD_LOCK(vkbd.c state lock) then callsgenkbd_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).
Recommended fix
Make genkbd_commonioctl/genkbd_get_fkeystr token use consistent: either
- have
sc_ioctl/vkbd_ioctlpaths takekbd_tokenbeforekbd_ioctl(making kbd_token strictly outermost), or - drop
kbd_tokenfromgenkbdioctl(thekbdswdispatch is already serialized bykb_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_testableConfirmed 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).
No comments yet.