โฌข DragonFlyBSD Kernel Audit
โ† dashboard
DF-0019

usched_bsd4.queue_checks accepts <=0 causing NULL-deref/panic in cache-coherent chooseproc

Field Value
ID DF-0019
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H
CWE CWE-20 Improper Input Validation
File sys/kern/usched_bsd4.c
Lines 1483, 1544-1548, 1560, 1572, 2043
Area kern
Confidence certain
Discovered 2026-06-29
Reported pending

Summary

kern.usched_bsd4.queue_checks is registered SYSCTL_ADD_INT(..., CTLFLAG_RW, ...) with the default sysctl_handle_int handler (no lower bound). Setting it to 0 (or negative) โ€” a root operation โ€” makes bsd4_chooseproc_locked_cache_coherent()'s while (checks < usched_bsd4_queue_checks) loop never execute, leaving min_level_lwp == NULL. The function then assigns lp = min_level_lwp (NULL) and trips KASSERT(lp) (INVARIANTS) or NULL-dereferences lp (production), panicking the kernel on the next user reschedule in cache_coherent mode with a non-empty runqueue.

Root cause

sys/kern/usched_bsd4.c:1483:

while (checks < usched_bsd4_queue_checks) {     /* body never runs if <= 0 */
    ...
}
...
lp = min_level_lwp;                             /* :1544  NULL */
q = min_q; which = min_which; pri = min_pri;
KASSERT(lp, ("chooseproc: at least the first lp was good"));   /* :1548 */
...
if (chklp) {
    if (chklp->lwp_priority < lp->lwp_priority + PPQ) { ... }  /* :1560 NULL deref */
}
...
TAILQ_REMOVE(q, lp, lwp_procq);                 /* :1572 NULL deref */

min_level_lwp is initialized NULL (~:1428) and is only assigned inside the loop body, which the loop guard (checks < queue_checks, with checks starting at 0) never enters when usched_bsd4_queue_checks <= 0. The sysctl registration (:~2043) uses SYSCTL_ADD_INT(..., CTLFLAG_RW, &usched_bsd4_queue_checks, 5, ...) with no handler-level bound โ€” contrast sysctl_usched_bsd4_stick_to_level (:1868-1882) which validates its range, and bsd4_recalculate_estcpu which clamps usched_bsd4_decay to [1..1024] (:1071-1075). queue_checks has neither protection.

Threat model & preconditions

  • Attacker position: privileged โ€” writing kern.usched_bsd4.* requires SYSCAP_NOSYSCTL_WR (root). So this is a privileged-user-triggered kernel panic (self-DoS / hardening defect), not a privilege escalation.
  • Privileges gained or impact: kernel panic (full-system DoS). No integrity/confidentiality impact.
  • Required config or capabilities: root; the bsd4 scheduler in use; kern.usched_bsd4.cache_coherent != 0 (boot default on multi-node/HT hardware) and a non-empty runqueue.
  • Reachability: sysctl kern.usched_bsd4.queue_checks=0, then any context switch that reaches bsd4_chooseproc_locked_cache_coherent.

Proof of concept

PoC source: findings/poc/DF-0019/queue_checks_panic.sh

Run (root, disposable VM)

sh findings/poc/DF-0019/queue_checks_panic.sh

Expected output

panic: chooseproc: at least the first lp was good     # INVARIANTS kernel
   (or) Fatal trap 12 ... in bsd4_chooseproc_locked_cache_coherent   # production

Impact

Root-only self-DoS. The kernel should not panic from a sysctl write of an in-range integer โ€” it is a robustness/hardening defect and a violation of the scheduler's own invariants. Rated Low (privileged trigger).

Validate the sysctl with a custom handler (reject < 1), mirroring stick_to_level, and/or clamp in the consumer:

--- a/sys/kern/usched_bsd4.c
+++ b/sys/kern/usched_bsd4.c
@@ -1480,7 +1480,7 @@
     * minimize the contention (we are in a locked region
     */
-   while (checks < usched_bsd4_queue_checks) {
+   while (checks < (usched_bsd4_queue_checks > 0 ? usched_bsd4_queue_checks : 1)) {

plus a sysctl_usched_bsd4_queue_checks handler that returns EINVAL for new_val < 1 (registered via SYSCTL_ADD_PROC), matching the sysctl_usched_bsd4_stick_to_level pattern at :1868-1882.

References

Timeline

  • 2026-06-29 Discovered during automated file-by-file audit of sys/kern/usched_bsd4.c.
  • pending Reported to DragonFlyBSD security contact.