DF-5235 / verdict.json
{ "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" } |