Divide-by-zero in ci_thermal_setup_fan_table from unvalidated VBIOS temperature deltas
Summary
ci_thermal_setup_fan_table at ci_smumgr.c:2159-2160: t_diff1=usTMed-usTMin, t_diff2=usTHigh-usTMed from VBIOS. :2165-2166 slope1=.../t_diff1, slope2=.../t_diff2. No check deltas!=0. Crafted VBIOS usTMed==usTMin -> divide-by-zero #DE panic. Gated by PHM_PlatformCaps_MicrocodeFanControl + fan present + duty100!=0. Sibling of DF-1128/DF-1204/DF-1274. Fix: check t_diff1!=0 && t_diff2!=0.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1297 Β· 13 files| File | Type | Description | Size | |
|---|---|---|---|---|
| harness.c | trigger-source | userspace replica of ci_thermal_setup_fan_table divisor block with crafted VBIOS usTMed==usTMin | 5.5 KB | view raw |
| build.sh | build-script | cc -O0 -Wall -o harness harness.c (-O0 required, gcc UB at -O2) | 538 B | view raw |
| run.sh | run-script | ./harness | 83 B | view raw |
| build.log | build-log | final successful build, full output | 65 B | view raw |
| run.log | run-log | decisive run, full output incl SIGFPE/CONFIRMED | 345 B | view raw |
| fix.diff | suggested-fix | add t_diff1/t_diff2 != 0 guard before division | 1.1 KB | view raw |
| env.txt | environment | uname, cc version, kldstat (no DRM loaded) | 784 B | view raw |
| VERDICT.md | verdict | full narrative: mechanism, reachability, harness, fix | 3.9 KB | β raw |
| README.md | readme | build/run/expected + why -O0 | 1.6 KB | β raw |
| fix_module_proof.txt | fix-build-proof | amdgpu.ko built with fix applied under -Werror, 0 errors, target .o produced | 389 B | view raw |
| fix_module_build.log | fix-build-log | amdgpu module build excerpt: ci_smumgr.o/bios_parser.o/dc_resource.o compiled, amdgpu.ko linked | 32.2 KB | view 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-1297 β Divide-by-zero in ci_thermal_setup_fan_table (ci_smumgr.c)
Severity: Medium Β· CWE: CWE-369 (Divide By Zero)
File: sys/dev/drm/amd/powerplay/smumgr/ci_smumgr.c:2159-2166
Build & run (AMD-DRM latent bug β no AMD GPU on guest, harness proof)
./build.sh # cc -O0 -Wall -o harness harness.c (-O0 REQUIRED, see below) ./run.sh # ./harness
Expected output (bug present)
DF-1297 ci_thermal_setup_fan_table divide-by-zero harness VBIOS usTMin=2500 usTMed=2500 usTHigh=9000 duty100=100 => t_diff1 = usTMed-usTMin = 0 (kernel divides by this) SIGFPE caught: integer divide-by-zero on t_diff1 == 0 In-kernel equivalent: CPU trap 0 (#DE) -> kernel panic RESULT: divide-by-zero CONFIRMED at ci_smumgr.c:2165
After applying fix.diff, the harness would still fault (it replicates the
unpatched arithmetic) β the fix is validated by inspection + build of the
kernel module source. The kernel-level fix adds a t_diff1==0 || t_diff2==0
guard before the division.
Why -O0
At -O2 gcc sees integer div-by-zero as undefined behavior and (when it can
prove the zero at compile time) elides the div instruction. The real kernel
builds at -O2 but reads the divisor at runtime from a parsed VBIOS struct
pointer, so gcc emits a real div β #DE trap β panic. -O0 reproduces that
runtime division faithfully in userspace.
Preconditions (kernel path)
All of: PHM_PlatformCaps_MicrocodeFanControl set + fan present +
fan_table_start != 0 + duty100 != 0. Plus attacker control of the VBIOS
temperature fields (malicious/faulty ROM, VFIO passthrough, supply-chain).
See VERDICT.md for the full trace.
DF-1297 β Divide-by-zero in ci_thermal_setup_fan_table (ci_smumgr.c)
Verdict: REPRODUCED (source-level + harness) β latent AMD-DRM bug, panic/DoS
The AMD GPU DRM driver (amdgpu / radeon powerplay) is not compiled into
the DragonFlyBSD X86_64_GENERIC kernel and no AMD GPU hardware is present
on the audit guest, so the bug cannot be triggered end-to-end here. It is a
real latent bug in the loadable amdgpu module: confirmed by source trace
and reproduced at the object/arithmetic level with a userspace harness that
mirrors the vulnerable function exactly.
The bug
sys/dev/drm/amd/powerplay/smumgr/ci_smumgr.c, function
ci_thermal_setup_fan_table, lines 2159-2166:
t_diff1 = ...usTMed - ...usTMin; /* VBIOS-controlled */
t_diff2 = ...usTHigh - ...usTMed; /* VBIOS-controlled */
...
slope1 = (uint16_t)((50 + ((16 * duty100 * pwm_diff1) / t_diff1)) / 100); /* :2165 */
slope2 = (uint16_t)((50 + ((16 * duty100 * pwm_diff2) / t_diff2)) / 100); /* :2166 */
t_diff1 and t_diff2 are derived from usTMin/usTMed/usTHigh, which are
parsed directly out of the GPU VBIOS (ATOM_Tonga_Fan_Table) at
sys/dev/drm/amd/powerplay/hwmgr/process_pptables_v1_0.c:941-946 with no
ordering or zero-delta validation. A crafted or faulty VBIOS in which
usTMed == usTMin makes t_diff1 == 0, and the division at line 2165 raises
CPU trap 0 (#DE) β kernel panic. Symmetrically for usTHigh == usTMed
at line 2166.
Reachability / threat model
The function is only entered when all of these hold (all satisfied on a real CIK/Bonaire/Hawaii card with microcode fan control, which is the common case):
PHM_PlatformCaps_MicrocodeFanControlis set (ci_smumgr.c:2134) β set by the VBIOS fan-table presence atprocess_pptables_v1_0.c:933-934- fan present (
bNoFan == 0,ci_smumgr.c:2137) fan_table_start != 0(ci_smumgr.c:2143)duty100 != 0(ci_smumgr.c:2150) β runtime register read
The attacker's only additional requirement is control of the VBIOS image β the same trust boundary. Realistic vectors: malicious/faulty GPU ROM flash, VFIO PCI passthrough of a card with a hacked VBIOS, supply-chain VBIOS tampering. Local DoS (kernel panic) on driver attach / thermal setup. Sibling of DF-1128 / DF-1204 / DF-1274 (same pattern in fiji/tonga/polaris smumgr).
Harness proof
harness.c replicates ci_thermal_setup_fan_table's divisor block verbatim
and feeds it a crafted VBIOS with usTMed == usTMin. Output (decisive run):
DF-1297 ci_thermal_setup_fan_table divide-by-zero harness VBIOS usTMin=2500 usTMed=2500 usTHigh=9000 duty100=100 => t_diff1 = usTMed-usTMin = 0 (kernel divides by this) SIGFPE caught: integer divide-by-zero on t_diff1 == 0 In-kernel equivalent: CPU trap 0 (#DE) -> kernel panic RESULT: divide-by-zero CONFIRMED at ci_smumgr.c:2165
-O0 is required for the harness: at -O2 gcc treats integer div-by-zero as
undefined and elides the div instruction when it can see the zero at
compile time. The real kernel builds at -O2, but the divisor is read at
runtime from a parsed VBIOS struct pointer, so gcc cannot prove it is zero and
emits a real div β #DE trap. -O0 faithfully reproduces that runtime
division in userspace.
Build & run
cc -O0 -Wall -o harness harness.c # -O0 REQUIRED (see note above) ./harness
or ./build.sh && ./run.sh.
Fix
fix.diff adds a guard after the deltas are computed, before the division:
if (t_diff1 == 0 || t_diff2 == 0) {
phm_cap_unset(hwmgr->platform_descriptor.platformCaps,
PHM_PlatformCaps_MicrocodeFanControl);
return -EINVAL;
}
This matches the existing pattern used elsewhere in the same function
(ci_smumgr.c:2138-2141, :2144-2146, :2151-2153) for the other
"disable microcode fan control and bail" conditions. It supersedes the
finding's one-line proposal by also disabling the capability (so the rest of
the powerplay stack does not keep trying to use the broken fan table).
Fix verification
fixedvalidated
module build rc=0 + harness
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source+harness. ci_thermal_setup_fan_table t_diff=0 -> div-by-zero #DE. amdgpu not in GENERIC.
No comments yet.