⬢ DragonFlyBSD Kernel Audit
DF-5235 / verdict.json
← back to finding ↓ download raw
{
  "finding_id": "DF-5235",
  "status": "reproduced",
  "verdict": "wait_for_completion()/wait_for_completion_interruptible() pass MAX_SCHEDULE_TIMEOUT (LONG_MAX) into lksleep()s int timo, truncating to -1 on x86_64; _callout_setup_quick() clamps the negative delta to the next tick, so every infinite completion wait returns spurious success after 1 tick (10 ms at hz=100) before the completion fires. Proven on-guest: drm_atomic_helper.c:2627/i915 intel_psr.c:869 entry point returned ret=0 after 1 tick with the completer scheduled 500 ticks out (two independent loads); control with an in-range 10-tick timeout behaves per contract; fix.diff (INT_MAX->timo 0 conversion, same as schedule_timeout) makes both waits block the full 499-500 ticks until the real completer. Demonstrated impact is the broken synchronization primitive itself; in-tree consumers are DRM drivers (hw-gated, absent on this QEMU guest), so no dos/corruption is observable here.",
  "reproduced": true,
  "impact": "none",
  "confidence": "certain",
  "exploit_chain": "",
  "evidence": "run.log + run.2.log: compbug[1] interruptible: ret=0 elapsed=1 ticks ... BUG (baseline, 2 loads); run-fixed.log: elapsed=499 ticks ... OK (after fix.diff); console.txt: full A/B/A load history; env.txt: hz=100, no drm modules loaded; SHA256SUMS: tested linux_completion.c byte-identical (477bdd88...) to the audit tree; compbug.c:34-47 stubs are linkage-only, never executed on the completion path",
  "kernel_refs": [
    "sys/dev/drm/include/linux/completion.h:90",
    "sys/dev/drm/linux_completion.c:33",
    "sys/dev/drm/linux_completion.c:40",
    "sys/dev/drm/include/linux/sched.h:78",
    "sys/dev/drm/include/linux/sched.h:130",
    "sys/sys/systm.h:414",
    "sys/kern/kern_synch.c:692",
    "sys/kern/kern_timeout.c:971",
    "sys/dev/drm/drm_atomic_helper.c:2627",
    "sys/dev/drm/i915/intel_psr.c:869"
  ],
  "poc_changes": "PoC authored from scratch (no seed): kmod compiling the VERBATIM sys/dev/drm/linux_completion.c (+ linux_wait.c for __init_waitqueue_head, with linkage-only stubs for the never-executed wait_event/current path); needed KCFLAGS + -include kconfig.h per sys/dev/drm/drm/Makefile conventions after plain CFLAGS+= was overridden by bsd.kmod.mk",
  "attempts": 4,
  "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": 900,
  "guest_dirty": 0,
  "build_cmd": "cd /root/df5235 && make",
  "run_cmd": "kldload ./compbug.ko && sleep 13 && dmesg | grep compbug | tail",
  "code_hash": "ad67098e59a832ee99e3449d0e5ea6b92b62d0393a79f3d91b966baa9c4cc15f",
  "notes": "Distinct from DF-2222 (same file): DF-2222 = missing loop break on ERESTART (signal -> unkillable busy-spin); DF-5235 = timeout truncation (no signal -> premature EWOULDBLOCK -> spurious success). Opposite failure modes, independent lines (completion.h:91-102 vs :90), independent fixes. With a pending signal the CURSIG short-circuit (kern_synch.c:622) arms no callout so DF-2222 dominates; without one, the truncation dominates. i386 would truncate LONG_MAX to INT_MAX (248d) and mask the bug; x86_64 is the vulnerable width. Guest left clean: compbug unloaded, /usr/src header reverted to pristine (verified), then vm.sh reset with-src.",
  "recommended_fix": "In __wait_for_completion_generic(), convert the timeout to lksleeps int timo exactly like schedule_timeout(): timo = (timeout >= INT_MAX) ? 0 : (int)timeout; and pass timo to lksleep (see fix.diff).",
  "fix_status": "fixed",
  "fix_baseline_reproduced": 1,
  "fix_patched_reproduced": 0,
  "fix_verdict": "Applied fix.diff to the guests /usr/src copy of completion.h, rebuilt the same module (build-fixed.log), re-ran the identical PoC: baseline elapsed=1 tick BUG (both loads) -> patched elapsed=499 ticks, waits block until the real completer fires, control case unchanged (9-10 ticks). Premature-return behavior gone; Linux return semantics restored. fix_patched_reproduced=0 means the BUG behavior did NOT reproduce on the patched build (desired outcome).",
  "fix_evidence": "fix.diff; run.log (baseline BUG); run.2.log (baseline BUG, 2nd load); run-fixed.log (patched OK elapsed=499); build.log vs build-fixed.log; console.txt full history"
}