DragonFlyBSD Kernel Audit
DF-2917 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-2917",
  "status": "reproduced",
  "reproduced": 1,
  "impact": "dos",
  "confidence": "certain",
  "verdict": "REPRODUCED end-to-end. _sleepq_wait_begin() (sys/kern/subr_sleepqueue.c:317-340) computes 'domain = PDOMAIN_FBSD0 + td->td_sqqueue * PDOMAIN_FBSDINC' at :327 into a local that is then never used; tsleep() is called at :331/:336 with tflags alone. PINTERLOCKED (0x400, sys/sys/param.h:326) carries no domain bits, while sleepq_add() interlocked the thread with tsleep_interlock(wchan, PDOMAIN_FBSD0+...) recording td_wdomain = 0x01000000 (subr_sleepqueue.c:275-276, kern_synch.c:446). tsleep()'s interlock validation (kern_synch.c:660-663, td->td_wdomain != (flags & PDOMAIN_MASK)) therefore always mismatches and takes the 'goto resume' fast path: every sleepq_wait(), sleepq_wait_sig(), sleepq_timedwait() and sleepq_timedwait_sig() (subr_sleepqueue.c:387-458) returns immediately (ret 0) WITHOUT ever descheduling. Demonstrated on kernel #2 (DF-0139 mask + DF-2915 fix): a kthread executed sleepq_lock/sleepq_add/sleepq_wait with NO waker at all and sleepq_wait() returned ret=0 after 0 sbticks ('round1 (nowake): sleepq_wait() returned ret=0 after 0 sbticks with NO wakeup issued'). For any FreeBSD-compat/Linux-KPI consumer this turns every blocking wait into a busy-spin at kernel priority (per-CPU livelock) and every timeout into an instant success return - the blocking primitive the API promises does not exist. Fix validated on kernel #3: with tflags|domain (fix.diff, the established umtx pattern kern_umtx.c:227) the same no-wake sleeper genuinely blocked for ~200 ticks until the harness issued sleepq_broadcast() (woke in 1 tick on the follow-up round). Reachability: zero in-tree callers; kldload-gated (root).",
  "exploit_chain": "",
  "evidence": [
    "run.log - kernel #2 (mask+DF-2915 fix, DF-2917 live): 'SQDEMO e2e: round1 (nowake): sleepq_wait() returned ret=0 after 0 sbticks with NO wakeup issued' - instantaneous return, zero blocking",
    "run.2.log - kernel #3 (both fixes): 'round1: sleeper STILL BLOCKED after ~200 ticks with no wakeup - sleepq_wait() really sleeps (DF-2917 fixed); issuing broadcast' and 'round2 (broadcast): sleepq_wait() returned after 1 sbticks'",
    "fix.diff - tflags | domain in both tsleep() calls (variable already existed, dead, at :327)",
    "VERDICT.md/README.md - full source trace kern_synch.c:660-663 -> goto resume"
  ],
  "kernel_refs": [
    "sys/kern/subr_sleepqueue.c:275",
    "sys/kern/subr_sleepqueue.c:276",
    "sys/kern/subr_sleepqueue.c:317",
    "sys/kern/subr_sleepqueue.c:327",
    "sys/kern/subr_sleepqueue.c:331",
    "sys/kern/subr_sleepqueue.c:336",
    "sys/kern/subr_sleepqueue.c:387",
    "sys/kern/kern_synch.c:660",
    "sys/sys/param.h:326",
    "sys/kern/kern_umtx.c:227"
  ],
  "poc_changes": "Authored from scratch. The demonstrator is sqe2e.c (shared with the DF-2915 pack): round 1 runs lock/add/wait with no waker and times how long sleepq_wait() blocks; round 2 verifies broadcast wakeups. Required the DF-0139 test mask + DF-2915 fix kernels (see ../DF-2915) so the code path is reachable at all.",
  "attempts": 2,
  "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Thu Sep  3 09:16:39 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "runtime_sec": 600,
  "guest_dirty": 0,
  "build_cmd": "cd /tmp/sqdemo/sqe2e && make   (module; see ../DF-2915/build.sh)",
  "run_cmd": "kldload /root/sqe2e.ko ; sleep 1 ; dmesg | tail",
  "code_hash": "9fa6c75f5ee70aa5f6a44b564f087fe8b4bbab8fcb52b41c45aa0a0ef5f47f89",
  "notes": "Trigger source sqe2e.c shared with DF-2915 (same file, same hash). Baseline kernel #2 (Sep 3 09:16:39, labelled '#1'): instant return. Fixed kernel #3 (Sep 3 09:26:13, labelled '#2'): real 200-tick block + 1-tick broadcast wake. The dead 'domain' local at :327 is the smoking gun for the intended tflags|domain.",
  "recommended_fix": "Pass the already-computed domain into tsleep(): tsleep(td->td_wchan, tflags | domain, td->td_wmesg, ...) in both branches of _sleepq_wait_begin() (subr_sleepqueue.c:331/:336).",
  "fix_status": "fixed",
  "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #2: Thu Sep  3 09:26:13 UTC 2026     root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC  x86_64",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "fix.diff applied in the guest /usr/src on top of mask+DF-2915-fix, kernel rebuilt (kbuild3_fix2917.log, rc=0) and installed. The identical no-wake demonstrator that returned in 0 ticks on the baseline now blocks for ~200 ticks until a broadcast is issued (and a subsequent round wakes in 1 tick). Blocking semantics fully restored; fix_status: fixed.",
  "fix_evidence": "run.2.log on kernel #3 ('STILL BLOCKED after ~200 ticks ... really sleeps'; 'round2 (broadcast): returned after 1 sbticks'); kbuild3_fix2917.log (full build log)."
}