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) | 65 B | view raw |
| run.sh | run-script | ./harness | 89 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 | 4.3 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 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
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.