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

CALLOUT_PREVENTED set on wrong structure (verifier/cc) vs read from backend _callout -> wrong callout_stop/cancel/drain return

Field Value
ID DF-0048
Status new
Severity Info
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N
CWE CWE-682 Incorrect Calculation
File sys/kern/kern_timeout.c
Lines 600-601 (write), 623 (2nd write), 926 (read)
Area kern
Confidence likely
Discovered 2026-06-29
Reported pending

Summary

When softclock_handler skips a callback because a concurrent callout_stop/cancel request set CALLOUT_STOP/CANCEL between the wheel-spinlock release and the dispatch, it records CALLOUT_PREVENTED on c->verifier->flags โ€” the frontend struct callout's flags. But the sole reader, _callout_cancel_or_stop at :926, reads CALLOUT_PREVENTED from c->flags โ€” the backend _callout's flags. These are two different uint32_t fields on two different structures. The bit written to cc->flags is never read, and _callout_update_spinlocked's "not SET" branch (:293-304, :334-343) explicitly does NOT set PREVENTED on c->flags. Net result: callout_stop/cancel/drain return 0 ("not prevented / already completed") when the callback was in fact prevented, in this narrow race window.

Root cause

sys/kern/kern_timeout.c:600-601:

atomic_set_int(&c->verifier->flags, CALLOUT_PREVENTED);   /* frontend cc */

and :623 (second site, same). The reader at sys/kern/kern_timeout.c:926:

res = ((c->flags & CALLOUT_PREVENTED) != 0);              /* backend _callout */

The two flags fields diverge after _callout_gettoc's initial copy (:732).

Threat model & preconditions

  • Impact: no memory-safety impact. Synchronous stop/cancel/drain block until INPROG clears (:910-921), so firing-after-free is prevented by the blocking semantics regardless of the return value. The wrong return value can cause callers that branch on "did I stop it vs did it already fire" to take the wrong cleanup path (a caller seeing 0 โ€” thinks the callback ran โ€” when it was actually prevented may skip cleanup or proceed with stale assumptions). Caller-dependent; no demonstrated kernel memory corruption, info leak, or privilege escalation. Foundational infra โ€” worth fixing for correctness.
  • Reachability: a callout_stop/cancel/drain racing the softclock dispatch of that callout.

Set CALLOUT_PREVENTED on c->flags (the backend _callout), which is what the reader checks:

--- a/sys/kern/kern_timeout.c
+++ b/sys/kern/kern_timeout.c
@@ -598
            if (c->flags &
                (CALLOUT_CANCEL | CALLOUT_STOP)) {
-               atomic_set_int(&c->verifier->flags,
+               atomic_set_int(&c->flags,
                           CALLOUT_PREVENTED);
@@ -623
            } else if (c->flags &
                   (CALLOUT_CANCEL | CALLOUT_STOP)) {
-               atomic_set_int(&c->verifier->flags,
+               atomic_set_int(&c->flags,
                           CALLOUT_PREVENTED);

References

Timeline

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