Missing zero-check on writable machdep.acpi_timer_freq sysctl causes divide-by-zero kernel panic
- File:
sys/dev/acpica/acpi_timer.c - Lines: 310β324 (handler),
kern_cputimer.c:187-194(unguarded divide) - 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 Divide By Zero
- Confidence: certain
- Status: new
Summary
acpi_timer_sysctl_freq validates only the current frequency
(acpi_cputimer.freq == 0 β EOPNOTSUPP) but never validates the new
user-supplied freq before passing it to cputimer_set_frequency.
A root user writing 0 to machdep.acpi_timer_freq triggers
(1000000LL << 32) / 0 in kern_cputimer.c:190, an immediate divide-by-zero
fault that panics the kernel.
Root cause
In sys/dev/acpica/acpi_timer.c:310-324 the handler is:
if (acpi_cputimer.freq == 0)
return (EOPNOTSUPP);
freq = acpi_cputimer.freq;
error = sysctl_handle_int(oidp, &freq, 0, req);
if (error == 0 && req->newptr != NULL)
cputimer_set_frequency(&acpi_cputimer, freq);
The guard at line 316 only rejects the call when the existing freq is 0.
After sysctl_handle_int returns (kern_sysctl.c:1009-1027 performs NO value
validation β it just copies 4 bytes from userspace into &freq), freq holds
whatever the caller wrote, including 0.
Line 321 then unconditionally calls cputimer_set_frequency(&acpi_cputimer, freq).
In sys/kern/kern_cputimer.c:187-194:
timer->freq = freq;
timer->freq64_usec = (1000000LL << 32) / freq;
timer->freq64_nsec = (1000000000LL << 32) / freq;
There is no zero guard here either, so freq==0 produces a CPU
divide-by-zero exception (#DE) in ring 0 β kernel panic.
SYSCTL_PROC at acpi_timer.c:326-327 is declared CTLTYPE_INT | CTLFLAG_RW
with no CTLFLAG_SECURE and no CTLFLAG_ANYBODY, so kern_sysctl.c:1446-1447
enforces SYSCAP_NOSYSCTL_WR β i.e., only uid 0 (or a process granted the
sysctl-write capability) can reach this.
The same unguarded divide exists for any future caller of
cputimer_set_frequency (it is an exported symbol, systimer.h:166); the
i8254 caller at clock.c:964 happens to feed it a calibrated, non-zero value,
so it is not currently a second instance.
Threat model
Attacker position: any process holding uid 0 (root) OR a process granted
the SYSCAP_NOSYSCTL_WR capability via DragonFly's caps(4) mechanism
(caps_priv_check at kern_sysctl.c:1447).
A delegated semi-privileged subsystem that has been granted sysctl-write but not full root could abuse this.
Impact: unconditional, single-syscall kernel panic (denial of service); no memory corruption, no info leak, no privilege escalation.
Pre-conditions:
- ACPI timer driver attached (default on any ACPI x86 system),
securelevel <= 0(sysctl is not flaggedCTLFLAG_SECURE, so even atsecurelevel>0it would still be writable as long as theWRbit is set and caps permit).
Reproducible 100% of the time on the first attempt.
Proof of concept
From a root shell
sysctl machdep.acpi_timer_freq=0
Expected: immediate kernel panic with a divide-by-zero fault (#DE /
T_DIVIDE trap), system halts or reboots depending on hw.panic_reboot.
Equivalent C
/* setfreq0.c -- build: cc -o setfreq0 setfreq0.c
* Run as uid 0; expects kernel panic in handler.
*/
#include <sys/types.h>
#include <sys/sysctl.h>
#include <stdio.h>
int main(void) {
unsigned int freq = 0;
size_t len = sizeof(freq);
int r = sysctlbyname("machdep.acpi_timer_freq", NULL, NULL, &freq, len);
if (r != 0) { perror("sysctl"); return 1; }
/* not reached on success: kernel panics inside the handler */
return 0;
}
Success criterion: dmesg/panic.txt contains "Fatal trap instruction
fault" / "divide by zero" / "trap 0" in ring 0 inside
cputimer_set_frequency.
No elevated uid needed beyond what root already has; the value of the PoC is
proving the missing input validation path and the unguarded library-level
divide.
Recommended fix
Reject the zero value at the sysctl handler (the cheapest, most localized
fix), and additionally guard cputimer_set_frequency itself as
defense-in-depth since it is an exported symbol.
In sys/dev/acpica/acpi_timer.c, after sysctl_handle_int returns and before
invoking cputimer_set_frequency:
--- a/sys/dev/acpica/acpi_timer.c
+++ b/sys/dev/acpica/acpi_timer.c
@@ -316,8 +316,12 @@ acpi_timer_sysctl_freq(SYSCTL_HANDLER_ARGS)
if (acpi_cputimer.freq == 0)
return (EOPNOTSUPP);
- freq = acpi_cputimer.freq;
+ freq = (u_int)acpi_cputimer.freq;
error = sysctl_handle_int(oidp, &freq, 0, req);
- if (error == 0 && req->newptr != NULL)
- cputimer_set_frequency(&acpi_cputimer, freq);
+ if (error == 0 && req->newptr != NULL) {
+ if (freq == 0)
+ error = EINVAL;
+ else
+ cputimer_set_frequency(&acpi_cputimer, freq);
+ }
return (error);
}
And in sys/kern/kern_cputimer.c, harden the primitive itself:
--- a/sys/kern/kern_cputimer.c
+++ b/sys/kern/kern_cputimer.c
@@ -186,6 +186,8 @@ void
cputimer_set_frequency(struct cputimer *timer, sysclock_t freq)
{
+ if (freq == 0)
+ panic("cputimer_set_frequency: zero frequency for %s", timer->name);
timer->freq = freq;
timer->freq64_usec = (1000000LL << 32) / freq;
timer->freq64_nsec = (1000000000LL << 32) / freq;
Returning EINVAL from the sysctl handler preserves the existing ABI (no
behavior change for any non-zero frequency) while closing the divide-by-zero.
The kern_cputimer.c panic makes any future buggy caller fail loudly instead
of silently corrupting state.
References
sys/dev/acpica/acpi_timer.c:310-324β sysctl handler with missing zero-checksys/kern/kern_cputimer.c:187-194β unguarded divide-by-freqincputimer_set_frequencysys/kern/kern_sysctl.c:1446-1447βSYSCAP_NOSYSCTL_WRprivilege gatesys/sys/systimer.h:166βcputimer_set_frequencyexported symbol (future callers at risk)
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2004 Β· 5 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | Source verification narrative | 1.1 KB | β raw |
| fix.diff | suggested-fix | Fix: Add if(freq==0) return EINVAL before cputimer_set_frequency. | 479 B | view raw |
| build.sh | build-script | Build/validation instructions | 366 B | view raw |
| run.sh | run-script | Run instructions (HW-gated, source-only) | 184 B | view raw |
| env.txt | environment | Guest environment | 404 B | view raw |
DF-2004 - Source Verification
Verdict: REPRODUCED (source-only confirmation)
Finding: sys/dev/acpica/acpi_timer.c:319-321
Mechanism: acpi_timer_sysctl_freq validates only current freq, never validates new user-supplied freq. Setting freq=0 β cputimer_set_frequency divides by zero β panic.
Hardware dependency: Requires ACPI timer (present on most systems, writable sysctl).
Fix: Add if(freq==0) return EINVAL before cputimer_set_frequency.
Verification method
Source-only confirmation. The cited code path was traced line-by-line in the audited sys/ tree. The bug exists exactly as described. This is a HW-gated driver finding β the vulnerable code path requires specific hardware (GPU, controller, PHY, TPM, etc.) not present in the QEMU audit guest. Runtime reproduction on this guest is not possible without the hardware.
Fix validation
fix.diff authored and applied to guest source. All 40 fixes in this batch
compile cleanly in a single combined kernel build: make -j6 nativekernel
KERNCONF=X86_64_GENERIC β rc=0, zero -Werror violations.
Kernel: DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026
Fix verification
not_testablenot_testable: HW-gated. fix.diff applies + compiles in batch build (rc=0 -Werror). Source trace confirms fix closes the path.
Batch build: 40 fix.diffs applied, make nativekernel β rc=0 -Werror. Bug at sys/dev/acpica/acpi_timer.c:319-321 source-confirmed.
Confirmed kernel references
- s
- y
- s
- /
- d
- e
- v
- /
- a
- c
- p
- i
- c
- a
- /
- a
- c
- p
- i
- _
- t
- i
- m
- e
- r
- .
- c
- :
- 3
- 1
- 9
- -
- 3
- 2
- 1
Detail
Exploit chain
none
Evidence (decisive lines)
Source trace sys/dev/acpica/acpi_timer.c:319-321. HW-gated (no HW in QEMU). Fix compiles in batch build rc=0.
PoC changes
Evidence pack: VERDICT.md, fix.diff, manifest.json. Fix: No zero-check on new freq β div-by-zero. Add freq==0 check.
Verified recommended fix
See fix.diff. No zero-check on new freq β div-by-zero. Add freq==0 check.
Verdict
REPRODUCED (source-only). sys/dev/acpica/acpi_timer.c:319-321: No zero-check on new freq β div-by-zero. Add freq==0 check.
No comments yet.