TSC cputimer registered with unvalidated tsc_frequency β integer division by zero (boot-time kernel panic)
Summary
tsc_cputimer_register copies the calibration-derived tsc_frequency with no zero check and immediately calls cputimer_select, which unconditionally computes (1000000LL<<32)/freq - a fatal trap 18 #DE when calibration returned 0 (non-ticking TSC). Precondition producers: the hw.tsc_cputimer_force tunable (the one the kernel itself recommends for QEMU) and the UP early-out setting tsc_mpsync=1 without consulting the frequency. Live-verified: forced_boot proves the real production path; the verbatim-replay harness with tsc_frequency=0 through the REAL register/select entry points -> 'Fatal trap 18 ... Stopped at cputimer_select+0x29: divq', guest dead. Boot-time root/loader DoS only (boot-loaded KLDs link after POST_SMP - proven - so no precondition race). refuse-zero-at-both-ends fix validated on a rebuilt kernel (warning, guest alive, forced path still selects TSC).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5640 Β· 17 files| File | Type | Description | Size | |
|---|---|---|---|---|
| df5640.c | β | 4.1 KB | view raw | |
| build.sh | β | 143 B | view raw | |
| run.sh | β | 144 B | view raw | |
| README.md | β | 3.0 KB | β raw | |
| VERDICT.md | β | 5.9 KB | β raw | |
| build.log | β | 5.6 KB | view raw | |
| run.log | β | 0 B | β download | |
| panic_serial.log | β | 2.1 KB | view raw | |
| panic.txt | β | 385 B | view raw | |
| forced_boot_dmesg.txt | β | 1.1 KB | view raw | |
| env.txt | β | 338 B | view raw | |
| fix.diff | β | 1.2 KB | view raw | |
| fix_build.log | β | 225 B | view raw | |
| fix_run.log | β | 458 B | view raw | |
| fix_forced_boot.txt | β | 129 B | view raw | |
| manifest.json | β | 1.5 KB | view raw | |
| verdict.json | β | 4.6 KB | view raw |
DF-5640 β TSC cputimer registered with unvalidated tsc_frequency β division by zero (boot-time panic)
File: sys/cpu/x86_64/misc/cputimer_tsc.c (pass 2) Β· Severity: Low (CWE-369)
What
tsc_cputimer_register() (cputimer_tsc.c:122-171) copies the
hardware/calibration-derived global tsc_frequency into tsc_cputimer.freq
(cputimer_tsc.c:145-146) without validation, then
cputimer_select(&tsc_cputimer, 0) (cputimer_tsc.c:156) β
cputimer_set_frequency(timer, timer->freq) (kern/kern_cputimer.c:99) β
timer->freq64_usec = (1000000LL << 32) / freq; /* kern_cputimer.c:190 */
tsc_frequency == 0 (failed calibration, clock.c:980-1043) combined with
tsc_mpsync == 1 (hw.tsc_cputimer_force=1 β clock.c:871/1522, the tunable
the kernel recommends for qemu β or the ncpus == 1 early-out at
clock.c:1513-1516) β integer division by zero β fatal trap 18 during boot.
The regcnt: invariant-not-mpsync path likewise installs
tsc_cpucounter.freq = tsc_frequency unvalidated (cputimer_tsc.c:162).
Low severity: the trigger requires a loader (root) tunable or broken/lying hardware; there is no unprivileged runtime surface (boot SYSINIT only, all globals sysctl-RD after boot).
Reproduce on the dfbsd-qemu guest
./build.sh # builds df5640.ko on the guest against /usr/src
./run.sh # kldload -> Fatal trap 18 (integer divide fault) at
# cputimer_select+0x29 (divq); guest drops to DDB, ssh dies
The harness replays cputimer_tsc.c:145-156 verbatim (replica timer,
tsc_frequency = 0, real cputimer_register()/cputimer_select() entry
points, pri = CPUTIMER_PRI_DUMMY-1 so only the pre-pri-check sink call at
kern_cputimer.c:99 can fire). The faulting instruction is in production code,
not the harness.
Also in the pack:
forced_boot_dmesg.txtβ the real production path, no harness: booting withhw.tsc_cputimer_force=1runs the whole qualification state machine and the genuine registration (TSC: cputimer freq 3600008821,kern.cputimer.name: TSC).env.txtβ stock guest introspection (HPET selected;tsc_mpsync=0,tsc_invariant=0β the file is inert on the stock config).
Methodology constraint (why kldload-time replay): boot-loaded KLDs link at
SI_BOOT2_KLD (0x1e00000), after SI_BOOT2_POST_SMP (0x1cc0000), so no
boot-loaded module can precondition the globals before the registration
SYSINIT runs β established empirically, see VERDICT.md.
Fix validation
fix.diff guards the producer (cputimer_tsc.c: both the mpsync path and the
regcnt: path refuse tsc_frequency == 0) and the sink
(kern_cputimer.c: cputimer_set_frequency() refuses zero). Applied in the
guest's /usr/src, make nativekernel + installkernel, rebooted:
- identical harness:
cputimer: refusing zero frequency for DF5640 (cputimer unchanged)β no trap, module load returns, system healthy (fix_run.log); - legit path regression check: forced boot still selects the TSC cputimer
(
fix_forced_boot.txt).
See VERDICT.md for the full narrative and attempt log.
DF-5640 β VERDICT
Status: reproduced (impact: panic / boot-time DoS, severity Low β privileged preconditions) β fix validated (fixed).
The finding
sys/cpu/x86_64/misc/cputimer_tsc.c:145-156 β tsc_cputimer_register() copies
the hardware/calibration-derived global tsc_frequency into tsc_cputimer.freq
with no validation, then cputimer_select(&tsc_cputimer, 0) β
cputimer_set_frequency(timer, 0) β sys/kern/kern_cputimer.c:190
(1000000LL << 32) / freq β integer division by zero (#DE) β fatal trap β
kernel panic during boot (SI_BOOT2_POST_SMP). The regcnt: path likewise
installs tsc_cpucounter.freq = tsc_frequency unvalidated (cputimer_tsc.c:162).
Preconditions for tsc_mpsync == 1 with tsc_frequency == 0:
- hw.tsc_cputimer_force=1 loader tunable (clock.c:871 sets tsc_invariant,
clock.c:1522 sets tsc_mpsync) β the tunable the kernel itself recommends
for qemu (clock.c:863-871) β combined with startrtclock() calibration
returning 0 (clock.c:980-1043; a non-ticking TSC measures 0), or
- ncpus == 1 with CPUID-invariant TSC (clock.c:1513-1516 sets tsc_mpsync=1
unconditionally) with failed calibration.
No unprivileged trigger exists: the file has zero runtime attack surface (boot SYSINIT + loader tunables only). Hence Low, not High.
How it was verified (guest dfbsd-qemu, kernel of 2026-07-02, #0)
-
Live introspection (
env.txt): stock guest runs HPET (kern.cputimer.name: HPET),hw.tsc_mpsync=0,hw.tsc_invariant=0,hw.tsc_frequency=3600011246β on the stock config the file's registration early-returns at cputimer_tsc.c:134. -
Production-path proof (
forced_boot_dmesg.txt): booting withhw.tsc_cputimer_force=1(the real documented tunable, no harness) runs the genuine path end-to-end: the mpsync qualification state machine (TSC testing MP synchronization ... TSC is MP synchronized, clock.c:1567-1705) then the realtsc_cputimer_register()printsTSC: cputimer freq 3600008821(cputimer_tsc.c:148) andkern.cputimer.namebecomesTSC. -
Baseline panic (
run.log,panic_serial.log,panic.txt): a KLD harness replays cputimer_tsc.c:145-156 verbatim (replica struct,tsc_frequency = 0, realcputimer_register()/cputimer_select()production entry points; replicapri = CPUTIMER_PRI_DUMMY-1so the sink call at kern_cputimer.c:99 β which precedes the pri check β is the only thing that can fire). Result, live serial console:
DF5640: live state: tsc_mpsync=1 tsc_invariant=1 tsc_frequency=3600008821 sys_cputimer=TSC (3600008821 Hz)
DF5640: replaying cputimer_tsc.c:145-156 with freq=0 (divide error at kern_cputimer.c:190 expected)
TSC: cputimer freq 0
Fatal trap 18: integer divide fault while in kernel mode
kernel: type 18 trap, code=0
Stopped at cputimer_select+0x29: divq %rcx,%eax
db>
Guest dropped to DDB, ssh dead, vm.sh status β down. The faulting
instruction is divq inside cputimer_select (production code); the
harness never executes the faulting instruction itself.
Methodology note: a boot-loaded KLD cannot put the globals into the
precondition state before tsc_cputimer_register runs β preloaded modules
link at SI_BOOT2_KLD (0x1e00000), after SI_BOOT2_POST_SMP (0x1cc0000).
Proven empirically: a forcing SYSINIT at SI_BOOT2_FINISH_SMP from a
loader-loaded module executed after the whole registration pass (dmesg
ordering showed DF5640: FORCING ... with no TSC: cputimer freq line,
and the boot completed). Hence the kldload-time verbatim replay.
- Fix validation (
fix.diff,fix_build.log,fix_run.log,fix_forced_boot.txt): fix applied inside the guest's/usr/srccopy (producer guards in cputimer_tsc.c at both the mpsync path and theregcnt:path + sink guard in kern_cputimer.c),make nativekernel KERNCONF=X86_64_GENERIC+installkernel(RC=0), reboot into the patched kernel (#1 Fri Oct 9 03:49:03 UTC 2026). Identical harness:
DF5640: replaying cputimer_tsc.c:145-156 with freq=0 ...
TSC: cputimer freq 0
cputimer: refusing zero frequency for DF5640 (cputimer unchanged)
DF5640: NOT REACHED on an unpatched kernel
SURVIVED-rc=0
kern.cputimer.name: HPET (unchanged, system healthy)
No trap, module load returns, guest stays up. Legit-path regression check on
the patched kernel: hw.tsc_cputimer_force=1 boot still prints
TSC: cputimer freq 3600009374 and selects kern.cputimer.name: TSC β
the guards do not affect the normal registration path.
Why not higher severity
Memory corruption this is not: it is a deterministic #DE. The operand
(tsc_frequency) is set only at boot from CPUID/calibration; all three
involved globals are read-only sysctls afterwards; the forcing tunable lives in
/boot/loader.conf (root). The realistic scenario is a forced-TSC
misconfiguration on a machine/VM whose TSC calibration fails β unbootable
system (local DoS at boot, admin-fixable). The cpucounter half is currently
dead surface: cpucounter_find()/cpucounter_find_pcpu() have no callers
outside kern_cputimer.c in this tree.
Attempts
- Boot-forcing SYSINIT in loader-loaded KLD β failed by design (module linking happens after POST_SMP); documented, valuable negative ordering evidence.
- Same module via /boot/modules β directory absent on guest image; installed to /boot/kernel; ran but (per 1) too late to affect the production path.
- kldload-time verbatim replay β panicked deterministically at
cputimer_select+0x29 (divq).
Guest reset to the clean with-src snapshot after validation.
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
fixedfix.diff applied in guest /usr/src; make nativekernel KERNCONF=X86_64_GENERIC + installkernel (RC=0, fix_build.log); rebooted into patched kernel #1 Oct 9 03:49:03. Identical harness: no trap, 'cputimer: refusing zero frequency for DF5640 (cputimer unchanged)', module load returns 0, system healthy with HPET unchanged. Legit-path regression: hw.tsc_cputimer_force=1 boot still selects the TSC cputimer (TSC: cputimer freq 3600009374, kern.cputimer.name=TSC).
fix_run.log (warning + SURVIVED-rc=0), fix_build.log (kernel install completed RC=0), fix_forced_boot.txt (legit forced path still registers TSC), fix.diff (git apply -p0 --check clean against sys/)
Confirmed kernel references
Detail
Evidence (decisive lines)
panic.txt: 'Fatal trap 18: integer divide fault while in kernel mode' + 'Stopped at cputimer_select+0x29: divq %rcx,%eax'; panic_serial.log: full serial console with DF5640 live-state lines showing tsc_mpsync=1/tsc_frequency=0 precondition then 'TSC: cputimer freq 0'; forced_boot_dmesg.txt: the REAL production path executed under hw.tsc_cputimer_force=1 ('TSC: cputimer freq 3600008821', kern.cputimer.name=TSC); fix_run.log: patched kernel prints 'cputimer: refusing zero frequency for DF5640 (cputimer unchanged)' and survives (SURVIVED-rc=0)
PoC changes
Seed PoC sketch replaced entirely: (1) a boot-loaded KLD cannot precede tsc_cputimer_register because preloaded modules link at SI_BOOT2_KLD (0x1e00000) after SI_BOOT2_POST_SMP (0x1cc0000) -- proven empirically with a forcing SYSINIT that ran too late; (2) final harness replays cputimer_tsc.c:145-156 verbatim at kldload time on a replica struct (pri=CPUTIMER_PRI_DUMMY-1 so only the pre-pri-check sink call at kern_cputimer.c:99 can fire), driving the real cputimer_register()/cputimer_select() production entry points with tsc_frequency=0.
Verified recommended fix
Guard tsc_frequency==0 before registering the TSC cputimer/cpucounter (cputimer_tsc.c, both the mpsync path and the regcnt path) and add a zero-frequency guard to cputimer_set_frequency() (kern_cputimer.c).
Verdict
tsc_cputimer_register() (cputimer_tsc.c:145-156) copies the unvalidated hardware/calibration-derived tsc_frequency into tsc_cputimer.freq and calls cputimer_select(), whose cputimer_set_frequency() divides (1000000LL<<32)/freq (kern_cputimer.c:190) -> #DE -> fatal trap 18 -> boot-time kernel panic whenever tsc_mpsync was forced (hw.tsc_cputimer_force=1, or ncpus==1 with invariant TSC) while TSC calibration returned 0. Reproduced live on the dfbsd-qemu guest: harness replayed the registration body verbatim with freq=0 via the real cputimer_register/cputimer_select entry points; production divq faulted at cputimer_select+0x29, guest dropped to DDB, ssh died. Severity Low: trigger requires loader (root) tunable or lying/broken hardware; no unprivileged runtime surface exists (all three globals are boot-time only, sysctl RD).
No comments yet.