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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1297 Β· 13 files
FileTypeDescriptionSize
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
README.md readme build/run/expected + why -O0
↓ download 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.

VERDICT.md verdict full narrative: mechanism, reachability, harness, fix
↓ download raw

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_MicrocodeFanControl is set (ci_smumgr.c:2134) β€” set by the VBIOS fan-table presence at process_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

fixed

validated

module build rc=0 + harness
↓ fix.diffn/a (module build)

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.