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)
PoC verification
Evidence pack
findings/poc/DF-1137 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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(orusTHigh==usTMed) triggers a kernel#DEdivide-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; raisesSIGFPEon the buggy path and returns-EINVALon 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).
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)
- AMD SI GPU present (no AMD GPU on the QEMU audit guest).
radeon.koloaded.- Crafted VBIOS with
usTMed == usTMinorusTHigh == usTMed. rdev->pm.dpm.fan.ucode_fan_control == true(set when BIOS fan table is present andduty100 != 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
patch -p1 --checkβ clean apply, 1 hunk.cd /usr/src/sys/dev/drm/radeon && makewith the diff applied βradeon.kobuilt cleanly (rc=0, 2,029,496 bytes).- 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
fixedvalidated
radeon.ko build rc=0 + harness before/after
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.
No comments yet.