vktimer_intr_reload(): wakeup CAS loop is dead code β unsigned (sysclock_t) delta cast makes the while-condition always false, disabling the atomic vktimer_target-lowering protocol
Summary
The wrap-around delta in the while at :302 is cast to sysclock_t (uint64_t) and compared < 0 - always false - so atomic_swap_long(&vktimer_target, reload) NEVER executes (the if five lines up uses the correct ssysclock_t cast; the while's cast is a typo). The lock-free protocol pulling the cothread's wake target to the earliest deadline is inert: vktimer_target is only ever updated by the cothread itself, so every earlier-than-stale-target reload re-fires cothread_wakeup -> pthread_kill(SIGINT) instead of only target-lowering ones - redundant signal churn under dense one-shot timer load plus loss of the intended race-window narrowing. A/B on instrumented vkernel64 builds: stock vkt_swap 0 executions across 42,294 opportunities (dead code proven), fixed ~1:1 executions (12,515->32,722); drift 1.000 both, no regression. Pure logic defect, no memory-safety route.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5568 Β· 18 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 3.9 KB | β raw | |
| VERDICT.md | β | 6.8 KB | β raw | |
| drift.c | β | 1.8 KB | view raw | |
| hog.c | β | 844 B | view raw | |
| instr.diff | β | 1.2 KB | view raw | |
| instrfix.diff | β | 1.2 KB | view raw | |
| fix.diff | β | 685 B | view raw | |
| build.sh | β | 218 B | view raw | |
| run.sh | β | 164 B | view raw | |
| build.instr.log | β | 934.4 KB | β download | |
| build.fixed.log | β | 934.6 KB | β download | |
| run.instr.log | β | 2.8 KB | view raw | |
| run.fixed.log | β | 2.8 KB | view raw | |
| vkcon.instr.log | β | 2.6 KB | view raw | |
| vkcon.fixed.log | β | 2.7 KB | view raw | |
| env.txt | β | 1.2 KB | view raw | |
| manifest.json | β | 1.3 KB | view raw | |
| verdict.json | β | 4.6 KB | view raw |
DF-5568 β vkernel64 vktimer_intr_reload(): wakeup CAS loop is dead code ((sysclock_t) β¦ < 0 unsigned compare)
What
sys/platform/vkernel64/platform/systimer.c:301-303:
if (vktimer_cotd && (ssysclock_t)(reload - vktimer_target) < 0) {
while ((sysclock_t)(reload - vktimer_target) < 0) /* <-- BUG */
reload = atomic_swap_long(&vktimer_target, reload);
cothread_wakeup(vktimer_cotd, &vktimer_ts);
}
sysclock_t is __uint64_t (sys/sys/systimer.h:51). The while
condition casts the wrap-around delta to an unsigned type and then
compares < 0 β always false. The loop body (atomic_swap_long target
lowering, the lock-free protocol intended to pull vktimer_target down to
the earliest deadline seen by any vcpu) never executes. The if five
lines up uses the correct (ssysclock_t) (int64_t) cast for the identical
expression β the while cast is a typo.
Consequences (all measured, see run logs):
- the atomic target-lowering protocol is inert; vktimer_target is only
ever updated by the cothread itself (systimer.c:263);
- every earlier-than-stale-target reload re-fires cothread_wakeup() β
pthread_kill(SIGINT) (cothread.c:192-197) instead of only those that
lower the target β redundant signal churn under dense one-shot timer
load (checklist (a) amplification);
- the intended narrowing of the reload-vs-cothread wakeup race is absent.
NOT affected: timer delivery itself β 10 ms sleeps are exact
(ratio 1.000) and the vkernel runs to completion on both builds; the
1 ms-sleep 10x quantization seen in both runs is the documented
coarse-timer clamp at systimer.c:275-282 (use_precise_timer == 0,
default per init.c:126; -t boot flag enables precise mode) β it is
identical before and after the fix.
How it was proven (A/B instrumented kernels)
Two vkernel64 kernels were built in the guest (nativekernel
KERNCONF=VKERNEL64, DF-5474 --defsym link workaround):
kernel.instrβ stock logic + two counters exposed asdebug.vkt_if(entries into theifbody) anddebug.vkt_swap(executions of thewhilebody). Seeinstr.diff.kernel.fixedβ same + the one-cast fix ((sysclock_t)β(ssysclock_t)in thewhile). Seeinstrfix.diff(verification build) andfix.diff(the clean, shippable fix).
Each kernel booted with -n 2 on /vkroot2.img; inside the vkernel:
counter read T0 β drift (timer sanity) β two 20 s hog processes
(dense 2 ms one-shot sleeps driving vktimer_intr_reload) β counter
read T1.
| build | vkt_if T0βT1 | vkt_swap T0βT1 | drift 10ms | vkernel |
|---|---|---|---|---|
stock (sysclock_t) |
16042 β 42294 | 0 β 0 | 1.000 | alive |
fixed (ssysclock_t) |
12411 β 32540 | 12515 β 32722 | 1.000 | alive |
42,294 opportunities, zero executions on stock; ~1:1 execution with the fix. The only delta between the two kernels is the cast. QED.
Reproduce
On the DragonFly guest as root (files from this pack in /root):
sh build.sh # binaries + /vkroot2.img + kernel.instr + kernel.fixed
sh run.sh /root/kernel.instr instr # stock -> expect vkt_swap: 0
sh run.sh /root/kernel.fixed fixed # patched -> expect vkt_swap > 0
Expected decisive output (inside the vkernel console, after the hogs):
# sysctl debug.vkt_if; sysctl debug.vkt_swap debug.vkt_if: 42294 (stock: large) debug.vkt_swap: 0 (stock: ZERO -> CAS body dead)
mkdir -p /var/vkernel and sysctl vm.vkernel_enable=1 are required
before the first boot. Root fs is specified as ufs:vkd0 at the
mountroot> prompt (fed automatically by run.sh).
Fix
fix.diff β one-character-class change:
- while ((sysclock_t)(reload - vktimer_target) < 0)
+ while ((ssysclock_t)(reload - vktimer_target) < 0)
Validated: with the fix the loop executes under the same workload, timer behavior is unchanged (drift 1.000 both builds, hogs complete, no panic), and the vkernel stays alive.
DF-5568 β VERDICT
Status: reproduced (dead code proven at scale, A/B against the one-cast fix). Impact: none (correctness / redundant-signal-churn defect; no memory safety, no privilege, no leak, no hang). Severity: Low. Confidence: certain.
Root cause (static)
sys/platform/vkernel64/platform/systimer.c:301-303:
if (vktimer_cotd && (ssysclock_t)(reload - vktimer_target) < 0) {
while ((sysclock_t)(reload - vktimer_target) < 0)
reload = atomic_swap_long(&vktimer_target, reload);
cothread_wakeup(vktimer_cotd, &vktimer_ts);
}
sysclock_t = __uint64_t (sys/sys/systimer.h:51); ssysclock_t =
int64_t (sys/sys/systimer.h:52). (sysclock_t)(x) < 0 is always
false for an unsigned type, so the while body β the
atomic_swap_long() that lowers vktimer_target to the earliest
deadline any vcpu has programmed β is dead code. The if on the line
above evaluates the identical expression with the correct
(ssysclock_t) cast; the while's cast is a typo.
What the dead protocol was for
vktimer_target is the cothread's next-wake deadline
(vktimer_thread(), systimer.c:262-263). When a vcpu programs a
one-shot systimer earlier than that deadline, vktimer_intr_reload()
wants to (a) atomically pull the target down and (b) kick the cothread
(cothread_wakeup() β zeroes vktimer_ts + pthread_kill(SIGINT),
cothread.c:192-197). With the loop dead, only (b) happens, and (b) is
re-fired for every earlier-than-stale-target reload rather than only
those that lower the target β redundant pthread_kill(SIGINT) churn
under dense one-shot timer load (the vkernel "signal storm" surface),
plus the loss of the intended race-window narrowing between
vktimer_reload[] stores (systimer.c:300) and the cothread's rescan
(systimer.c:254-274).
Verification (DragonFly guest, vkernel64 rig after DF-5337/DF-5538/DF-5556)
Guest: DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 (X86_64_GENERIC, hw.ncpu=6), cc 8.3 [DragonFly].
Built two vkernel64 kernels from the guest's /usr/src
(make -j6 nativekernel KERNCONF=VKERNEL64, DF-5474 link workaround:
kernel.instr: stock logic + countersdebug.vkt_if/debug.vkt_swap(instr.diff);kernel.fixed: + the one-cast fix (instrfix.diff; clean fix infix.diff).
Booted each with -m 512m -r /vkroot2.img -n 2 -e init_path=/bin/vkinit;
inside: T0 counters β drift β two 20 s hogs (2 ms clock_nanosleep
loops) β T1 counters.
Results (run.instr.log, run.fixed.log)
| build | vkt_if T0βT1 | vkt_swap T0βT1 |
|---|---|---|
stock (sysclock_t) |
16042 β 42294 | 0 β 0 |
fixed (ssysclock_t) |
12411 β 32540 | 12515 β 32722 |
- Stock: the
ifbody ran 42,294 times; thewhilebody ran 0 times. Dead code proven at scale (idle boot alone produced 16,042ifentries). - Fixed: the
whilebody runs ~1:1 with theif(slightly more β multiple iterations under contention), i.e. the intended atomic target-lowering protocol is restored by the single cast change. - No functional regression:
DRIFT10MS ratio 1.000on both builds, hogs complete (HOGS_RC=0), vkernelALIVE_AT_ENDon both builds, no panic, no watchdog. DRIFT1MS ratio 10.000(identical both builds) is the documented coarse-timer clampsystimer.c:275-282withuse_precise_timer = 0(default,init.c:126;-tenables precise mode) β pre-existing designed behavior, not this finding, and unaffected by the fix.
Fix validation
- Baseline reproduces the defect (vkt_swap = 0 despite 42k opportunities):
fix_baseline_reproduced = true. - Patched kernel: defect gone (vkt_swap > 0), timer function unchanged
(drift identical, workload completes, vkernel alive):
fix_patched_reproduced = trueβfix_status = fixed.
Why severity is Low (negative results that bound it)
- No memory-safety surface in the file: no copyin/copyout, no dynamic
allocation after boot, arrays indexed only by
gd_cpuid < MAXCPU(systimer.c:300;optcpusclamped 1..MAXCPU atinit.c:395-397); cothread reads ofvktimer_reload[]/vktimer_targetare aligned 64-bit scalars (tear-free on x86_64). - No missed-wakeup hang: the cothread recomputes
vktimer_targetfromvktimer_reload[]every pass and sleeps for the min delta; even a stale target can only cause extra wakeups, not fewer (systimer.c:246-283). - No teardown UAF:
vktimer_cotdis never destroyed (nocothread_delete()caller for it;rgover vkernel64). - Boot-order race killed:
ap_tids[]is populated synchronously bymp_start()atSI_BOOT2_START_CPU(autoconf.c:101,199,mp.c:403,464-470) which precedesSI_BOOT2_CLOCKREG(kernel.h:158,162;systimer.c:136), so the cothread never signals a nonexistent thread;pthread_kill(ap_tids[n], SIGURG)(systimer.c:258) is safe post-boot. - Runtime re-selection of the interrupt cputimer can't re-run
vktimer_intr_initclock()(no second cothread):initclockruns only from theSI_ORDER_SECONDsysinit (kern_cputimer.c:432-465), and with a single registered "vkernel" intr timer thekern.cputimer_intr.selectsysctl re-selects the same object (kern_cputimer.c:518-521). sys_cputimerswap is boot-time-only over static structures (dummy_cputimerβvkernel_cputimer,kern_cputimer.c:84-117),count()is stateless β no cothread-vs-swap UAF.- Reload arithmetic is clamped (
systimer.c:295-299: negativeβ1, β₯freqβfreq);muldivu64(reload β€ freq-1, 1e9, freq)cannot overflow and yieldstv_nsec < 1e9(systimer.c:283).
Rig notes (what had to be fixed vs the seed)
mkdir -p /var/vkernel+sysctl vm.vkernel_enable=1required before first boot (fresh guest snapshot).- The run-monitor must not grep for a marker that appears in the echoed
fifo input (first run self-terminated early) β output-only marker
TERMINUS: 42used instead. fix.diffis generated against the pristine file; the instrumented verification build needs the combinedinstrfix.diff(instrumentation- cast) β applying
fix.diffon top ofinstr.difffails on context (braces added around thewhilebody) by construction. unamedidn't land in the image (cp silently skipped);kern.clockrateused instead β cosmetic only.
Artifacts
instr.diff, instrfix.diff, fix.diff, drift.c, hog.c,
build.sh, run.sh, build.instr.log, build.fixed.log,
run.instr.log, run.fixed.log, vkcon.instr.log, vkcon.fixed.log,
env.txt, manifest.json, verdict.json.
Fix status (batch validation)
Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.
Fix verification
fixedApplied fix.diff (one-cast change) to the guest /usr/src copy, rebuilt the VKERNEL64 kernel, and re-ran the identical workload: baseline kernel shows vkt_swap 0->0 (dead CAS, 42,294 if-entries); patched kernel shows vkt_swap 12515->32722 (protocol live). Timer function is unchanged (DRIFT10MS 1.000 on both, HOGS_RC=0, ALIVE_AT_END, no panic, no watchdog), so the fix restores the intended protocol with no regression.
fix.diff; instrfix.diff (verification build); run.instr.log vs run.fixed.log counter tables; build.fixed.log; vkcon.fixed.log
Confirmed kernel references
Detail
Evidence (decisive lines)
run.instr.log / run.fixed.log: counter tables 'debug.vkt_if: 16042->42294, debug.vkt_swap: 0->0' (stock) vs 'debug.vkt_if: 12411->32540, debug.vkt_swap: 12515->32722' (fixed); DRIFT10MS ratio 1.000 on both; HOGS_RC=0 and ALIVE_AT_END on both. VERDICT.md has the full A/B narrative and the negative-results appendix (boot-order, teardown, arithmetic bounds).
PoC changes
Rig repaired vs DF-5556 seed: created /var/vkernel and set vm.vkernel_enable=1 on the fresh snapshot; output-only completion marker (TERMINUS: 42) because the echoed fifo input previously matched the VKDONE grep and killed the run early; split verification diffs into instr.diff (counters) and instrfix.diff (counters + cast fix) because fix.diff (vs pristine) cannot apply on top of instr.diff by construction; added drift.c PASS/FAIL calibrated to coarse mode (10ms exact / 1ms clamp documented at systimer.c:275-282 with use_precise_timer=0).
Verified recommended fix
In vktimer_intr_reload(), change the while loop's cast from (sysclock_t) to (ssysclock_t) so the atomic_swap_long target-lowering loop actually executes.
Verdict
vktimer_intr_reload()'s wakeup CAS loop (systimer.c:301-303) casts the wrap-around delta to unsigned sysclock_t (__uint64_t, systimer.h:51) before comparing < 0, so the while condition is always false and the atomic_swap_long target-lowering protocol never executes. Proven behaviorally on the guest with two instrumented vkernel64 kernels (counters debug.vkt_if / debug.vkt_swap): stock build entered the if-body 42,294 times over boot+drift+2x20s one-shot-timer hogs while the while-body executed exactly 0 times; the otherwise-identical kernel with the single (sysclock_t)->(ssysclock_t) cast fix executed the while-body 32,722 times (~1:1 with if-entries), with identical timer behavior (DRIFT10MS ratio 1.000 both builds, hogs complete, vkernel alive, no panic). The defect is real dead code that disables the intended cothread-wakeup target-lowering and causes redundant cothread_wakeup/pthread_kill(SIGINT) churn under dense one-shot timer load, but timer delivery itself never broke (no hang, no missed-wakeup in evidence), and there is no memory-safety, privilege, or info-leak consequence; severity Low.
No comments yet.