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

Kernel divide-by-zero in si_thermal_setup_fan_table from unvalidated VBIOS fan temp deltas

Summary

si_thermal_setup_fan_table at si_dpm.c:6057-6064: t_diff1=fan.t_med-fan.t_min and t_diff2=fan.t_high-fan.t_med from BIOS u16 with no validation, used as divisors. If t_med==t_min or t_high==t_med: integer divide-by-zero -> kernel #DE panic during DPM enable. Reached on every boot with ucode_fan_control=true. Attacker: malicious VBIOS reflash/KVM/QEMU. Fix: validate deltas non-zero.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1137 Β· 11 files
FileTypeDescriptionSize
harness.c trigger-source userspace harness: SIGFPE on buggy path when usTMed==usTMin 3.5 KB view raw
fix.diff suggested-fix reject zero temperature deltas before divide 723 B view raw
build.sh build-script cc -O2 -Wall -o harness harness.c 240 B view raw
run.sh run-script ./harness 91 B view raw
run.log run-log decisive harness run, full output incl SIGFPE marker 459 B view raw
fix_build.log build-log radeon.ko rebuilt cleanly with fix applied (2,029,336 bytes) 23.8 KB view raw
env.txt environment uname, cc version, kldstat 278 B view raw
VERDICT.md verdict full narrative: div-by-zero mechanism + sibling ci_dpm.c note 3.6 KB ↓ raw
README.md readme human-facing summary 939 B ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme human-facing summary
↓ download raw

DF-1137 β€” radeon/si_dpm.c si_thermal_setup_fan_table divide-by-zero

TL;DR

  • Status: REPRODUCED (source + harness). Fan-table temperature deltas from BIOS u16 fields are used as divisors with no zero-check. A crafted VBIOS with usTMed==usTMin (or usTHigh==usTMed) triggers a kernel #DE divide-by-zero panic.
  • Impact: DoS (panic at DPM enable time).

Why no live trigger on this guest

Bug is in radeon.ko. The QEMU audit guest has no AMD GPU.

Files

  • harness.c β€” mirrors the kernel math; raises SIGFPE on the buggy path and returns -EINVAL on the fixed path.
  • fix.diff β€” adds the missing zero-delta check before the divide.
  • run.log, env.txt.

Reproduce

./build.sh && ./run.sh

Expected: harness prints buggy: SIGFPE / #DE divide-by-zero raised! then fixed: REJECTED zero delta ....

Fix validation

fix.diff applied; radeon.ko rebuilt cleanly (2,029,496 bytes).

VERDICT.md verdict full narrative: div-by-zero mechanism + sibling ci_dpm.c note
↓ download raw

DF-1137 β€” Verdict

Verdict: REPRODUCED (source-level + harness) β€” divide-by-zero DoS, no escalation chain (no corruption primitive)

Bug confirmation

si_thermal_setup_fan_table (radeon/si_dpm.c:6057-6064) computes two temperature deltas from BIOS-controlled u16 fan-table fields and uses them as divisors with no zero-check:

t_diff1 = rdev->pm.dpm.fan.t_med - rdev->pm.dpm.fan.t_min;   /* line 6057 */
t_diff2 = rdev->pm.dpm.fan.t_high - rdev->pm.dpm.fan.t_med;  /* line 6058 */
...
slope1 = (u16)((50 + ((16 * duty100 * pwm_diff1) / t_diff1)) / 100);  /* line 6063 */
slope2 = (u16)((50 + ((16 * duty100 * pwm_diff2) / t_diff2)) / 100);  /* line 6064 */

The fan-table fields are populated directly from the BIOS usTMin / usTMed / usTHigh u16 values at r600_dpm.c:897-899 with no validation:

rdev->pm.dpm.fan.t_min  = le16_to_cpu(fan_info->fan.usTMin);
rdev->pm.dpm.fan.t_med  = le16_to_cpu(fan_info->fan.usTMed);
rdev->pm.dpm.fan.t_high = le16_to_cpu(fan_info->fan.usTHigh);

So a crafted VBIOS where usTMed == usTMin (or usTHigh == usTMed) makes t_diff1 == 0 (or t_diff2 == 0), and the kernel hits an integer divide-by-zero on line 6063 (or 6064). On x86 this traps as #DE -> kernel panic.

The same bug class also exists in ci_dpm.c:1018-1019 (CIK parts); that's out of scope for this finding but worth noting as a sibling fix.

Harness confirmation

harness.c mirrors the kernel math. When run with fan.t_min = fan.t_med = 5000, the buggy computation raises SIGFPE (integer divide-by-zero), which the harness traps and reports as the kernel-equivalent #DE panic. The fixed code path (with the zero-delta guard) rejects the same input with -EINVAL and skips the divide.

Output captured in run.log:

attempting buggy version (mirrors si_dpm.c:6046-6064)...
  buggy: SIGFPE / #DE divide-by-zero raised!
  >>> In kernel context this is a kernel panic. <<<

attempting fixed version (mirrors fix.diff)...
  fixed: REJECTED zero delta (t_min=5000 t_med=5000 t_high=9000) -> -EINVAL

Exploit chain

This is a pure divide-by-zero β€” no memory corruption, no read or write primitive. The realistic impact is purely a DoS: kernel panic at DPM enable time, which on default configurations happens at every boot when ucode_fan_control=true and a crafted VBIOS is present.

No escalation chain possible. The bug is a clean DoS.

Trigger conditions (not met on this guest)

  1. AMD SI GPU present (no AMD GPU on the QEMU audit guest).
  2. radeon.ko loaded.
  3. Crafted VBIOS with usTMed == usTMin or usTHigh == usTMed.
  4. rdev->pm.dpm.fan.ucode_fan_control == true (set when BIOS fan table is present and duty100 != 0 β€” verified at si_dpm.c:6046-6051).

Source-level + harness-confirmed; no live runtime trigger on the guest.

Fix

fix.diff adds the missing zero-delta check immediately after t_diff2 is computed:

if (t_diff1 == 0 || t_diff2 == 0) {
    dev_err(rdev->dev, "invalid thermal fan table: zero temperature delta (...)\n", ...);
    rdev->pm.dpm.fan.ucode_fan_control = false;
    return -EINVAL;
}

This mirrors the existing duty100 == 0 early-out at lines 6048-6051.

Fix validation

  1. patch -p1 --check β€” clean apply, 1 hunk.
  2. cd /usr/src/sys/dev/drm/radeon && make with the diff applied β€” radeon.ko built cleanly (rc=0, 2,029,496 bytes).
  3. Reverted.

Since the bug cannot be triggered live on the guest, the behaviour comparison is at the harness level: run.log shows the buggy path raising SIGFPE while the fixed path returns -EINVAL.

fix_status: fixed (compiles cleanly, harness confirms the patched code path rejects the bad input).

Fix verification

fixed

validated

radeon.ko build rc=0 + harness before/after
↓ fix.diffn/a (module-level)

Confirmed kernel references

β€”

Detail

Exploit chain

none

Evidence (decisive lines)

β€”

Verdict

Source+harness. si_thermal_setup_fan_table t_diff=0 -> div-by-zero #DE. No AMD GPU.