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

VBIOS-derived zero clocks reach dfixed_div as divisors in the rs690 watermark math: igp_sideport_mclk == 0 (no zero fallback) and ulDefaultEngineClock == 0 (pm.sclk == 0) β†’ integer-divide-fault panic at boot/first bandwidth update

Summary

ulBootUpSidePortClock is copied with no zero fallback (unlike igp_system_mclk) - a VBIOS reporting sideport present with mclk 0 makes rs690.c:372 divide by zero; independently radeon_atombios stores default_sclk with no fallback, so pm.sclk==0 makes every guard's max_bandwidth 0 and :388 divides 1000 by sclk==0. Crafted/corrupt option ROM panics at boot/first modeset - trusted-VBIOS model, availability only. Harness scenarios SIGFPE + shipped-binary disassembly prove both sinks; WITH_FIX build survives. Treat-zero-as-absent + safe-watermark fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4583 Β· 3 files
FileTypeDescriptionSize
README.md β€” 3.3 KB ↓ raw
fix.diff β€” 1.9 KB view raw
verdict.json β€” 4.3 KB view raw

DF-4583 β€” VBIOS zero clock values reach dfixed_div as divisors in the rs690 watermark math (kernel divide-by-zero)

Sibling pack of DF-4582 (same harness, same function, different attacker model). The PoC sources, build and run scripts live in ../DF-4582/; this pack adds the scenario map and the fix for the two VBIOS-derived zero-divisor sinks.

The two sinks (rs690_crtc_bandwidth_compute, sys/dev/drm/radeon/rs690.c)

  1. sideport mclk == 0 β€” rs690_pm_info() (rs690.c:80/94) copies ulBootUpMemoryClock (crev 1) or ulBootUpSidePortClock (crev 2) verbatim from the VBIOS IntegratedSystemInfo table into rdev->pm.igp_sideport_mclk; neither field has a zero fallback (unlike igp_system_mclk, which has one at rs690.c:96-101). When the VBIOS also reports sideport memory present (radeon_atombios_sideport_present, rs690.c:164), the compute path runs b.full = dfixed_div(igp_sideport_mclk, 1000) β†’ 0, then read_delay_latency = dfixed_div(read_delay_latency, b) β€” divide by zero (rs690.c:371-372).
  2. default engine clock == 0 β€” radeon_atombios_get_clock_info (radeon_atombios.c:1240-1241) copies ulDefaultEngineClock into rdev->clock.default_sclk with no zero fallback (contrast usMaxPixelClock at :1269-1271 and vco_freq at :1285-1287 which do get fallbacks). radeon_update_bandwidth_info then sets rdev->pm.sclk = 0, core_bandwidth = pm.sclk/16 = 0; every max_bandwidth replacement guard (&& bandwidth.full, rs690.c:366-380) keeps it at 0, and sclk.full = dfixed_div(a, sclk) at rs690.c:388 divides by zero.

Attacker model

Crafted or corrupt VBIOS (GPU option ROM reflash / card with attacker ROM / broken BIOS image). Effect: kernel panic at boot or at the first modeset/bandwidth update. Availability only β€” no memory unsafety.

Evidence

  • Shipped binary (/boot/kernel/radeon.ko disassembly): 0x5adb9: div %r8 where r8 = (mclk << 16) >> 12 (i.e. mclkΒ·16, built at 0x5adac-0x5adb6) β€” r8 == 0 exactly when igp_sideport_mclk == 0 β‡’ #DE in the shipped code (sink 1 confirmed in the real binary). The 1000/sclk sink is the same structure (runtime div %rsi at 0x5ae6c following shl $0xc of max_bandwidthΒ·16).
  • Guest userland, -O0 (harness scenario 1 and 2, ../DF-4582/rs690_div0.c): both killed by SIGFPE (signal 8) β€” source semantics divide by zero (see DF-4582 run logs).
  • Guest kernel module, kernel flags: scenario 1 survived in the harness build because gcc UB-transformed that particular chain in the harness context (documented in DF-4582/VERDICT.md); the shipped-binary disassembly above is the ground truth that the real radeon.ko keeps the runtime division. Scenario 0 (DF-4582) panicked the guest with Fatal trap 18, proving the harness faithfully reproduces the dfixed_div trap when the division survives to runtime.
  • Fix validation: ../DF-4582 WITH_FIX build (guards mirror this pack's fix.diff) survives scenarios 1 and 2 with DF4582-FIX: zero divisor avoided at rdl-mclk / 1000/sclk β€” bailing with safe watermark, module unloads cleanly.

Reproduce (userland semantics; guest has no RS690 hardware)

cd ../DF-4582
cc -O0 -o rs690_div0 rs690_div0.c
./rs690_div0 1    # sideport mclk 0  -> SIGFPE
./rs690_div0 2    # pm.sclk 0        -> SIGFPE

Fix verification

inconclusive
baseline no→ patch + rebuild →patched clean

Guard-level validation only: the WITH_FIX harness build demonstrably avoids both zero divisors ('DF4582-FIX: zero divisor avoided at rdl-mclk / 1000/sclk') and survives, and the guards are value-based (independent of how the zero arises). The unpatched KLD harness did not panic on scenario 1 in its particular -O2 codegen (gcc UB-transformed that chain there β€” see DF-4582 VERDICT), while the shipped radeon.ko disassembly (0x5adb9) proves the real binary keeps the runtime division, so a hardware baseline was not executable and fix_status is honestly inconclusive rather than fixed.

fix.diff (zero sideport mclk treated as no-sideport with UMA latency + zero max_bandwidth bail); ../DF-4582/fix_run.log.
↓ fix.diffper-fix-DF-4583

Confirmed kernel references

Detail

Exploit chain

crafted/corrupt GPU VBIOS (option-ROM reflash or malicious card) -> rs690_pm_info parses IntegratedSystemInfo with sideport-present flag and zero boot clocks (rs690.c:80/94/164) or radeon_atombios_get_clock_info stores default_sclk=0 (radeon_atombios.c:1240-1241, no zero fallback unlike :1269-1271) -> first bandwidth update (boot modeset / pm profile change) -> rs690_crtc_bandwidth_compute divides by the zero fixed-point value (rs690.c:371-372 or :386-388) -> do_div by zero -> integer divide fault -> kernel panic.

Evidence (decisive lines)

../DF-4582/run.2.log (SIGFPE x2 for scenarios 1 and 2 at -O0); ../DF-4582/objdump_radeon_ko.txt (0x5adb9 div %r8, divisor r8=(mclk<<16)>>12 == 0 iff mclk==0, in the shipped binary); ../DF-4582/fix_run.log ('zero divisor avoided at rdl-mclk / 1000/sclk' with survival and clean unload in the guarded build); fix.diff in this pack.

PoC changes

No seed; shares the DF-4582 harness (scenarios 1 and 2 map to the two sinks).

Verified recommended fix

rs690.c: treat igp_sideport_mclk==0 as 'no sideport' (UMA read-delay latency) and bail with the safe watermark when max_bandwidth (pm.sclk-derived) is zero; optionally add zero fallbacks where radeon_atombios_get_clock_info stores default_sclk/default_mclk.

Verdict

Two VBIOS-derived zero-divisor sinks in rs690_crtc_bandwidth_compute() panic the kernel via dfixed_div: (1) igp_sideport_mclk==0 (copied verbatim from the VBIOS IntegratedSystemInfo ulBootUpSidePortClock/ulBootUpMemoryClock by rs690_pm_info, no zero fallback) with sideport reported present divides by zero at rs690.c:371-372; (2) ulDefaultEngineClock==0 gives rdev->pm.sclk==0 and a zero max_bandwidth that every '&& bandwidth.full' guard preserves, dividing by zero at rs690.c:386-388. Evidence: -O0 verbatim harness kills both scenarios with SIGFPE (run.2.log); the SHIPPED /boot/kernel/radeon.ko contains the runtime division for sink 1 (0x5adac-0x5adb9: r8=(mclk<<16)>>12, div %r8 β€” zero exactly when mclk==0) proving the real binary does not fold it away; the WITH_FIX harness build guards both sinks and survives. End-to-end execution needs RS690-class hardware with the crafted VBIOS (absent in the QEMU guest), and the KLD harness scenario-1 happened to be UB-transformed by gcc in that build context (documented), so overall status is inconclusive/likely rather than reproduced.