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/drainblock untilINPROGclears (: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/drainracing the softclock dispatch of that callout.
Recommended fix
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
sys/kern/kern_timeout.c:600-601,623โPREVENTEDwritten toc->verifier->flags.sys/kern/kern_timeout.c:926โPREVENTEDread fromc->flags.- CWE-682 Incorrect Calculation.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/kern_timeout.c. - pending Reported to DragonFlyBSD security contact.