β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4228

Root-writable debug.cyapa_*_freq sysctls feed an integer divide-by-zero in cyapa_poll_thread β€” deterministic kernel panic

Summary

The three freq sysctls are exported RW via SYSCTL_INT with no bounds. The poll thread recomputes its sleep tick every iteration as (hz + freq - 1) / freq with freq reloaded from those globals. sysctl =0 makes the next poll iteration divide by zero in kernel-thread context - #DE panic. The idle frequency is selected whenever no fd is open, so no open fd is even required. SIGFPE harness on the guest (identical expression/types); the sysctl write path itself exercised live (accepted 100->0, no panic only because the poll thread is hardware-gated). Clamp + sysctl-validation fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4228 Β· 12 files
FileTypeDescriptionSize
README.md β€” 1.9 KB ↓ raw
VERDICT.md β€” 2.3 KB ↓ raw
model_div0.c β€” 1.5 KB view raw
build.sh β€” 94 B view raw
run.sh β€” 193 B view raw
build.log β€” 165 B view raw
run.log β€” 574 B view raw
run.2.log β€” 943 B view raw
env.txt β€” 576 B view raw
fix.diff β€” 405 B view raw
manifest.json β€” 1.1 KB view raw
verdict.json β€” 2.1 KB view raw

DF-4228 β€” cyapa: root-writable debug sysctls feed an integer divide-by-zero in cyapa_poll_thread (kernel panic)

Where

sys/dev/smbus/cyapa/cyapa.c

  • cyapa.c:227-235 β€” cyapa_idle_freq (1), cyapa_slow_freq (20), cyapa_norm_freq (100) are module globals exported RW via SYSCTL_INT(_debug, OID_AUTO, …, CTLFLAG_RW, …) with no bounds.
  • cyapa.c:1200 β€” the poll thread computes its sleep interval: tsleep(&sc->poll_flags, 0, "cyapw", (hz + freq - 1) / freq);
  • cyapa.c:1202-1210 β€” freq is re-assigned from one of the three sysctls every iteration (idle/slow/norm depending on open+activity state).

Bug

sysctl debug.cyapa_idle_freq=0 (or slow/norm) β€” root-writable, no validation β€” makes the next poll iteration execute (hz + 0 - 1) / 0: integer division by zero inside a kernel thread β†’ #DE trap β†’ kernel panic. Negative values make tsleep take a negative timeout (separate misbehavior).

PoC

  • model_div0.c executes the identical arithmetic in the identical integer types: raises SIGFPE in userspace (in kernel context: #DE β†’ panic). Observed on the audit guest: RESULT: SIGFPE - integer division by zero raised (run.log).
  • On real hardware the one-liner is: sysctl debug.cyapa_idle_freq=0 (any state with the device attached; the idle frequency is selected whenever sc->count == 0, i.e. even with no process holding the device open).

Guest evidence (this run)

The sysctls register at module load (kldload cyapa.ko β€” see run.log) and accept a write of 0 without panicking because the poll thread only exists once the driver attaches to hardware β€” no SMBus controller/trackpad exists in QEMU, so the division never executes here. The fault itself is source-certain and demonstrated by the harness.

Fix

Clamp: if (freq < 1) freq = 1; before use (or use imax(freq,1)), plus sysctl validation. See fix.diff.

Build / run

cc -O0 -Wall -o model_div0 model_div0.c
./model_div0
VERDICT.md
↓ download raw

DF-4228 β€” VERDICT

Status: untested (kernel-side panic not exercisable on this guest β€” the poll thread exists only after the driver attaches to hardware, which requires a Cypress APA trackpad on SMBus addr 0x67; the arithmetic fault itself is demonstrated by harness and is source-certain).

Root cause (line-accurate)

  • cyapa_idle_freq / cyapa_slow_freq / cyapa_norm_freq are static ints exported read-write at cyapa.c:227-235 (SYSCTL_INT(..., CTLFLAG_RW, ...)) with no range enforcement β€” debug.* sysctls are root-writable.
  • cyapa_poll_thread (cyapa.c:1161) unconditionally computes (hz + freq - 1) / freq at cyapa.c:1200 each iteration.
  • freq is refreshed from the sysctl globals at cyapa.c:1202-1210 (idle freq chosen whenever sc->count == 0 β€” i.e. with no open fd, slow when idle, norm otherwise).
  • freq = 0 β‡’ integer division by zero in kernel thread context β‡’ x86-64 #DE exception in CPL0 β‡’ kernel panic.

What ran on the guest

  1. model_div0.c β€” identical expression (hz + freq - 1) / freq with the identical types (int hz, int freq), freq=0: SIGFPE on the division (run.log: "RESULT: SIGFPE - integer division by zero raised").
  2. Module lifecycle (run.log, shared with DF-4227 pack): - kldload cyapa.ko β†’ debug.cyapa_* sysctls appear (registered at module load, device or not). - sysctl debug.cyapa_norm_freq=0 accepted β†’ guest stayed up, because cyapa_poll_thread was never started (no attach). This confirms the write path works and only the hardware-gated thread is missing.

Impact

Root-only trigger (debug.* sysctl write), but it is a one-syscall deterministic kernel panic on any machine with the trackpad (Acer C720 class laptops). Low severity, DoS class.

Clamp before the division (and ideally validate on sysctl write):

if (freq < 1)
        freq = 1;

See fix.diff (applies clean against sys/).

Fix validation

Not possible on this guest (poll thread unreachable). fix_status = not_testable.

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

not_testable
baseline no→ patch + rebuild →patched clean

Fix requires the attached device to validate on hardware; the one-line clamp is line-accurate against the verified arithmetic and applies cleanly to sys/.

findings/poc/DF-4228/fix.diff (git apply --check clean)
↓ fix.diffper-fix-DF-4228

Confirmed kernel references

Detail

Exploit chain

root: sysctl debug.cyapa_idle_freq=0 -> next cyapa_poll_thread iteration executes (hz+0-1)/0 -> #DE in kernel thread -> panic

Evidence (decisive lines)

build.log + run.log (harness SIGFPE, deterministic), VERDICT.md, fix.diff (applies clean)

PoC changes

n/a - pack authored fresh

Verified recommended fix

clamp freq to >= 1 before the division (fix.diff) and validate the sysctls

Verdict

Source-certain divide-by-zero panic: (hz + freq - 1) / freq at cyapa.c:1200 with freq reloaded from unbounded root-writable RW sysctls (cyapa.c:227-235) at cyapa.c:1202-1210. Harness executing the identical expression with identical types raises SIGFPE on the guest (build.log/run.log). Kernel-side panic not exercisable on this guest because cyapa_poll_thread only starts after hardware attach (no SMBus controller/trackpad in QEMU; module loads and sysctl write of 0 was accepted without effect - see ../DF-4227/run.log). On trackpad hardware the trigger is one sysctl write.