kern.stathz boot tunable ingested with no validation β kernel integer-divide fault at statclock systimer registration (unbootable system)
| Field | Value |
|---|---|
| ID | DF-2946 |
| 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-369 / CWE-20 |
| File | sys/kern/subr_param.c |
| Lines | 201-203 (sinks: kern_systimer.c:270-273, kern_cputimer.c:197-200) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-09-02 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:kern |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
init_param1() overwrites the derived stathz (hz+1) with the raw kern.stathz loader tunable and performs no validation. The value flows untouched into initclocks_other(), which registers the per-CPU statclock systimer with SYSTF_MSSYNC; _systimer_init_periodic() then computes the period with an unguarded '1000 / freq' (MSSYNC path) and cputimer_default_fromhz() computes 'freq / freq' on the other branch. kern.stathz=0 boots past init_param1 (hz itself stays valid, so the DF-0173 in-file divides succeed) and kills the kernel later at SI_BOOT2_POST_SMP. VERIFIED on the guest: single line in /boot/loader.conf β 'Fatal trap 18: integer divide fault while in kernel mode', 'Stopped at ... idivq %r12,%eax', dead in DDB, ssh never up. Negative stathz yields a negative period (garbage expiry math, broken statclock); huge stathz yields a sub-nanosecond statclock period (interrupt storm). The tree even guards stathz==0 for the clockrate sysctl ('stathz ? stathz : hz') β the systimer path is the omission. Distinct from DF-0173 (different tunable, later sink) and from the DF-2915/16/17 sleepq/systimer API defects. The hz>1000000 variant (ustick==0 β post-boot UNPRIVILEGED divide-by-zero via tv.tv_usec/ ustick in kern_time/bpf) and hz<0 variant fold into the same fix locus (init_param1 validation). Anyone with /boot/loader.conf control makes the machine permanently unbootable with one line β same trust boundary as DF-0173/DF-2894; persistent boot DoS ceiling.
Timeline
- 2026-09-02 Discovered during pass-2 audit of subr_param.c (GLM 5.3); unbootable state reproduced first attempt on stock guest.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2946 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 2.3 KB | β raw | |
| VERDICT.md | file | 4.1 KB | β raw | |
| env.txt | file | 218 B | view raw | |
| fix.diff | file | 1.2 KB | view raw | |
| manifest.json | file | 1.3 KB | view raw | |
| panic.txt | file | 5.8 KB | view raw | |
| plant.sh | file | 1.7 KB | view raw | |
| run.sh | file | 650 B | view raw | |
| sha256.txt | file | 148 B | view raw | |
| verdict.json | file | 4.8 KB | view raw |
DF-2946 β kern.stathz boot tunable fetched with no validation β boot-time integer divide fault
sys/kern/subr_param.c:202 (init_param1) overwrites the computed stathz
(default hz + 1) with the raw loader tunable kern.stathz and never
validates it. The value flows into initclocks_other()
(sys/kern/kern_clock.c:438-441), which registers the per-CPU statclock
systimer with SYSTF_MSSYNC. _systimer_init_periodic()
(sys/kern/kern_systimer.c:270-273) then computes the timer period with an
unguarded division:
if ((flags & SYSTF_MSSYNC) && freq <= 1000)
info->periodic = sys_cputimer->fromhz(1000) * (1000 / freq); /* 1000/0 */
else
info->periodic = sys_cputimer->fromhz(freq); /* freq/freq */
and cputimer_default_fromhz() (sys/kern/kern_cputimer.c:197-200) divides
sys_cputimer->freq / freq on the other branch.
kern.stathz="0"β integer divide fault panic during boot (demonstrated).kern.stathz="-N"β signed period, garbage modulo/expiry math, broken statclock.kern.stathzhuge β multi-GHz statclock (interrupt storm / unusable system).
Sibling of DF-0173 (kern.hz=0 divides at subr_param.c:204-207 inside
init_param1); this member survives init_param1 and detonates later, at
SI_BOOT2_POST_SMP. The clkinfo sysctl handler even guards for this
(stathz ? stathz : hz, kern_clock.c:1337) β the systimer path does not.
Reproduce (host side, guest up & clean)
dfbsd-qemu/vm.sh run_root 'cp /boot/loader.conf /boot/loader.conf.df2946bak && printf "kern.stathz=\"0\"\n" >> /boot/loader.conf && sync'
dfbsd-qemu/vm.sh run_root 'shutdown -r now'
sleep 75; dfbsd-qemu/vm.sh status # -> down
grep -n "Fatal trap" dfbsd-qemu/boot.log
dfbsd-qemu/vm.sh reset with-src
Expected output
Guest never reaches ssh (vm.sh status β down ~75 s after reboot command);
serial console (dfbsd-qemu/boot.log, see panic.txt) shows, immediately after
"Initialize MI interrupts for 6 cpus":
Fatal trap 18: integer divide fault while in kernel mode cpuid = 0; lapic id = 0 ... kernel: type 18 trap, code=0 CPU0 stopping CPUs: 0x0000003e Stopped at -0x7f9a53d9: idivq %r12,%eax db>
Impact: unbootable system (boot-time kernel panic). Trust boundary: /boot/loader.conf (root / loader prompt / console), same as DF-0173.
DF-2946 β VERDICT
Status: reproduced (impact: panic β boot-time kernel divide fault; single-variable, pristine guest)
What was run
Stock guest, kernel DragonFly 6.5-DEVELOPMENT #0 (INVARIANTS build,
X86_64_GENERIC, 6 vCPU, 4 GB). Single change: one line appended to
/boot/loader.conf:
kern.stathz="0"
then shutdown -r now.
What happened (vs. what was predicted)
Predicted: 1000 / freq (kern_systimer.c:271, SYSTF_MSSYNC path, or
fromhz() at kern_cputimer.c:199) traps during initclocks_other()
(SI_BOOT2_POST_SMP).
Observed (dfbsd-qemu/boot.log, full excerpt in panic.txt): the kernel boots through init_param1 without complaint (kern.stathz=0 is merely assigned β subr_param.c:202 has no validation), gets through SMP/MI-interrupt initialization ("Initialize MI interrupts for 6 cpus"), and dies with:
Fatal trap 18: integer divide fault while in kernel mode cpuid = 0; lapic id = 0 kernel: type 18 trap, code=0 CPU0 stopping CPUs: 0x0000003e Stopped at -0x7f9a53d9: idivq %r12,%eax db>
idivq = the 64-bit signed division of the period computation; the boot
position matches initclocks_other() exactly (it runs right after MI
interrupt init for all CPUs, before userland). Guest sits in DDB; ssh never
comes up; vm.sh status β down. Reproduced on the first attempt; single
variable planted (loader.conf otherwise untouched).
Why it is a finding and not a duplicate
- DF-0173 covers
kern.hz=0β division inside init_param1 (subr_param.c:204-207:ustick/nstick/ntp_default_tick_delta). Withkern.stathz=0alone, hz stays 100, those divisions succeed, the system passes init_param1 β and dies later in the systimer subsystem. - DF-2915/2916/2917 are sleepq/systimer API defects, not clock-tunable validation.
- The codebase itself knows stathz can be zero β
sysctl_kern_clockrateexportsstathz ? stathz : hz(kern_clock.c:1337) β but the systimer registration path (kern_clock.c:439) passes it unguarded.
Trust boundary / severity
Boot-tunable (loader.conf / loader prompt / console) β requires root or
boot-loader control, identical to DF-0173 (rated Low). No post-boot or
unprivileged write path exists (kern.stathz has no runtime sysctl;
maxprocperuid is RW but stathz is not). Ceiling: unbootable system.
Rated Low (local boot DoS, privileged trigger), consistent with DF-0173.
Negative results worth recording
kern.nbuf="-1048576"was planted first-hand and did not break boot: every nbuf cap in cpu_startup (machdep.c:424-458) compareslong nbufagainst unsignedvm_offset_texpressions, so the negative value is promoted to a huge unsigned, "capped due to kvm" then "due to physmem", and the system boots with a sane positive nbuf (129940 on this guest, warnings in boot.log). The apparent negative-nbuf valloc-corruption hypothesis is refuted on pc64 (same unsigned arithmetic in vkernel64 autoconf.c:156-158 rescues it there too).MSGBUFSIZE/msgbuf sizing: compile-time only (opt_msgbuf.h), no kenv tunable anywhere β not attacker-influenced in this threat model.maxthrssizis fetched (subr_param.c:230) but has zero consumers in the tree β dead knob.vmm_vendor[16]is written only from the 12-char CPUID hypervisor signature (initcpu.c:195,204, memcpy of 13 bytes incl. NUL) β bounded, hardware-trusted.- 32-bit
limsizeoverflow in init_param2 is unreachable: only pc64 and vkernel64 call it (both 64-bit; (size_t)physpages * PAGE_SIZE β€ 2^43). kern.hz > 1000000βustick == 0β post-boot unprivileged divide-by-zero viatv.tv_usec / ustick(kern_time.c:511 and ~8 more sites) β same root cause and fix locus as DF-0173 (hz validation in init_param1); noted here to make the fix clamp the upper bound too, not re-filed as a separate finding.
Fix validation
Not performed in-guest (Low-severity boot-DoS class, no demonstrated memory
corruption; kernel rebuild cycle not warranted for a 3-line clamp).
fix.diff is authored against the read-only sys/ tree: validate hz/stathz
in init_param1, plus a defense-in-depth freq <= 0 guard in
_systimer_init_periodic.
Fix verification
not_testableFix not built in-guest: Low-severity boot-DoS class with no demonstrated memory corruption (per contract, guest fix-validation cycles are reserved for memory-corruption findings); fix.diff is a minimal, line-accurate clamp authored against the read-only sys/ tree.
['fix.diff']
Confirmed kernel references
Detail
Exploit chain
1) privileged/console plant: kern.stathz="0" in /boot/loader.conf; 2) init_param1 ingests it unvalidated into stathz; 3) boot proceeds (hz valid, in-file divides fine); 4) initclocks_other at SI_BOOT2_POST_SMP calls systimer_init_periodic_flags(..., stathz=0, SYSTF_MSSYNC); 5) 1000/freq (or fromhz) executes idivq by zero -> Fatal trap 18 -> DDB; machine will not boot until loader.conf is repaired. Ceiling: persistent boot DoS (no post-boot or unprivileged trigger exists β kern.stathz has no runtime sysctl).
Evidence (decisive lines)
["panic.txt β 'Fatal trap 18: integer divide fault while in kernel mode' / 'kernel: type 18 trap' / 'Stopped at -0x7f9a53d9: idivq %r12,%eax' / 'db>' immediately after 'Initialize MI interrupts for 6 cpus'", 'env.txt β stock kernel identity (6.5-DEVELOPMENT #0, X86_64_GENERIC, hw.ncpu=6) and clean baseline vfs.nbuf/maxfiles/maxproc before the plant', 'run.sh β exact host-side reproduction sequence (plant -> reboot -> down at 75s -> reset)', 'VERDICT.md β analysis, duplicate-separation vs DF-0173/2915-17, and the refuted negative-kern.nbuf hypothesis (unsigned-promotion rescue in cpu_startup caps)', 'fix.diff β hz/stathz validation in init_param1 + freq<=0 guard in _systimer_init_periodic']
PoC changes
no compilation needed: the trigger is a single loader.conf line (plant.sh); predicted sink (1000/freq under SYSTF_MSSYNC vs fromhz) confirmed only by trap signature + boot position since the INVARIANTS kernel exports no symbols to the serial console
Verified recommended fix
Validate clock tunables in init_param1: clamp hz to [1,1000000] (default on violation) and floor stathz at 1 (default hz+1); defense-in-depth freq<=0 guard in _systimer_init_periodic.
Verdict
init_param1() (sys/kern/subr_param.c:202) overwrites stathz with the raw kern.stathz boot tunable with no validation; the value reaches initclocks_other() (kern_clock.c:438-441) which registers the statclock systimer with SYSTF_MSSYNC, and _systimer_init_periodic() (kern_systimer.c:270-273) / cputimer_default_fromhz() (kern_cputimer.c:199) divide by it unguarded. A single planted line kern.stathz="0" in /boot/loader.conf boots past init_param1 and kills the kernel at SI_BOOT2_POST_SMP with 'Fatal trap 18: integer divide fault ... idivq' (panic.txt), leaving the machine dead in DDB β unbootable until the tunable is removed. Negative or huge stathz similarly produce a negative/garbage statclock period or an interrupt storm. Same loader.conf trust boundary as DF-0173 (kern.hz=0), but a different tunable and a different, later sink: DF-0173 divides inside init_param1 (subr_param.c:204-207); kern.stathz=0 passes those divides (hz itself stays valid) and detonates in the systimer subsystem. The tree even guards stathz==0 elsewhere (kern_clock.c:1337 'stathz ? stathz : hz'), confirming the systimer path is the omission.
No comments yet.