⬢ DragonFlyBSD Kernel Audit
DF-5324 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5324",
  "status": "reproduced",
  "reproduced": true,
  "impact": "panic",
  "confidence": "certain",
  "verdict": "Reproduced live on the QEMU guest: cycling QEMU monitor device_del/device_add of a usb-kbd while syscons is bound to ukbd0 and a 6-thread root process spams console keyboard ioctls (KDSETREPEAT/KDSKBMODE/KDSETLED on /dev/consolectl and /dev/ttyv0) panics the kernel within ~100 unplug events: Fatal trap 12 ... fault virtual address = 0x58 ... Stopped at kbd_ioctl+0x73: movq 0x58(%rax),%rax - exactly kbdsw.c:210 (*kbdsw[kbd->kb_index]->ioctl)() with kbdsw[index]==NULL after ukbd_detach->kbd_unregister (kbd.c:282) cleared the slot and freed the softc while the syscons consumer (syscons.c:1431 fetch of sc->kbd, no lock across fetch->dispatch) was in flight. Guest halted in ddb (CPUs stopped). Baseline kernel 6.5-DEVELOPMENT #0; patched kernel (fix.diff: kbdsw_check() dispatch validation extended from the existing kbd_intr() pattern) survived >150 identical unplug cycles under load with zero panics.",
  "exploit_chain": "unplug (physical / usbconfig / unpriv ugen USB_SET_CONFIG per DF-5316) -> ukbd_detach (ukbd.c:1370) -> kbd_unregister: kbdsw[i]=NULL + lockuninit(kb_lock) + softc freed; concurrent console ioctl thread that fetched sc->kbd (syscons.c:1431) resumes -> lockmgr on freed kb_lock (UAF write) -> kbdsw[kbd->kb_index] read from freed memory -> dispatch through NULL (observed panic) or through attacker-groomed slab content (kb_index garbage -> kbdsw[OOB] -> wild indirect call = RIP control; not demonstrated, window is tens of ns). Observed impact: reliable local panic/DoS of the console subsystem and whole machine.",
  "evidence": "panic.txt lines 40-59: Fatal user address access from kernel mode from kbdspam, fault virtual address = 0x58, Stopped at kbd_ioctl+0x73: movq 0x58(%rax),%rax, CPUs stopped at db> prompt. fix_run.log: >150 cycles on patched kernel, no panic. VERDICT.md: full path:line trace and negative results.",
  "kernel_refs": [
    "sys/bus/u4b/input/ukbd.c:1370",
    "sys/bus/u4b/input/ukbd.c:1426",
    "sys/dev/misc/kbd/kbd.c:281",
    "sys/dev/misc/kbd/kbd.c:285",
    "sys/dev/misc/kbd/kbdsw.c:203",
    "sys/dev/misc/kbd/kbdsw.c:209",
    "sys/dev/misc/syscons/syscons.c:1431",
    "sys/dev/misc/syscons/syscons.c:891"
  ],
  "poc_changes": "Original audit had no runnable PoC (finding derived in this run). Built from scratch: kbdspam.c (multi-thread console ioctl spam), kbdsetup.c (KBRELKBD release of ukbd0 from kbdmux0 + CONS_SETKBD bind), rebind.sh (per-cycle re-arm with retries after discovering ukbd is auto-claimed by kbdmux at attach), race.sh/race2.sh (QEMU monitor device_del/device_add cycling with armed-cycle counting). Learned: /dev/kbdN genkbd path is safe (holds kbd_token across kbd_ioctl) - the vulnerable consumer is the syscons path; ukbd must be un-claimed from the mux before syscons can bind it.",
  "attempts": 2,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul  2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64",
  "runtime_sec": 3000,
  "guest_dirty": 0,
  "build_cmd": "cc -O2 -pthread -o /tmp/kbdspam /tmp/kbdspam.c && cc -O2 -o /tmp/kbdsetup /tmp/kbdsetup.c",
  "run_cmd": "QEMU_EXTRA=-device piix3-usb-uhci,id=uhci0 -device usb-kbd,id=k1 dfbsd-qemu/vm.sh reset with-src; (guest) sh /tmp/rebind.sh; /tmp/kbdspam 6 &; (host) sh race2.sh 55",
  "code_hash": "08d0ff688aedd8682e34118cdb439676c0a21270f41b3423ee8b41bbd8d49434",
  "notes": "Guest reset to with-src clean snapshot after validation (no USB devices attached). Unplug-side can be forced without physical access via the unprivileged ugen USB_SET_CONFIG re-enumeration already proven in DF-5316. The lockmgr-on-freed-lock write and kbdsw[freed->kb_index] call are the escalation ceiling (needs slab grooming inside a tens-of-ns window); not pursued to uid=0. Info-level companion finding DF-5325: ukbd_detach mutates sc_ndata/sc_odata and runs ukbd_interrupt without kb_lock (ukbd.c:1391-1394) racing the kb_lock-holding ukbd_timeout callout - in-bounds input-ring corruption only, not separately verified.",
  "recommended_fix": "Add kbdsw_check() dispatch validation (bounds + NULL) to all kbdsw.c accessors like kbd_intr() already does; full fix requires keyboard reference counting so the softc cannot be freed while consumers hold keyboard_t pointers.",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff (kbdsw.c kbdsw_check() at every accessor dispatch) applied to guest /usr/src, kernel rebuilt via make nativekernel, installed, rebooted (6.5-DEVELOPMENT #1 Fri Oct 2 18:46:22). Identical race workload re-run for >150 unplug/replug cycles across 3 batches: zero panics, guest up, console functional, stale-pointer ioctls return ENODEV. The reproduced NULL-deref crash is gone. Residual TOCTOU (freed-slab index read between check and call) documented in the diff header; complete fix needs kbd refcounting.",
  "fix_evidence": "fix_run.log (patched-kernel batches, no panic), fix_build.log (build/install/reboot transcript), fix.diff (git-apply clean against sys/)",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Fri Oct  2 18:46:22 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64"
}