# DF-4761 — vega10 powerplay fan-RPM arithmetic overflows 32 bits (`60 * xclk * 10000`), producing wrong fan telemetry and a wrong TACH target; div-by-zero in the set path requires crafted VBIOS + root

* File: `sys/dev/drm/amd/powerplay/hwmgr/vega10_thermal.c:115` and `:325`
* Severity: Info (correctness/hardening; no memory-safety impact;
  reachability requires an AMD Vega10-class GPU and, for the interesting
  variants, root via hwmon)
* Status: **untested** (the audit guest has no AMD GPU — QEMU/virtio
  hardware only; the arithmetic is provable from source)

## What

```c
*speed = 60 * crystal_clock_freq * 10000 / tach_period;   /* :115, u32 math */
...
tach_period = 60 * crystal_clock_freq * 10000 / (8 * speed); /* :325 */
```

`amdgpu_asic_get_xclk()` returns the crystal clock in kHz (25 MHz-class,
e.g. 25000 for Vega10). `60 * 25000 * 10000 = 1.5e10` **exceeds
`UINT32_MAX` (4.29e9)**; the u32 numerator wraps to 2,115,098,112 before
the division, so:

* the reported fan RPM (`vega10_fan_ctrl_get_fan_speed_rpm`, the non-SMC
  path) is systematically wrong (~12% class error at typical RPM) —
  telemetry only;
* the TACH target programmed on the set path (`set_fan_speed_rpm`) is
  derived from the same wrapped constant — fan regulation reference is
  skewed (hardware correctness/reliability, no kernel memory impact);
* the set path divides by `8 * speed`: `8 * speed == 0` (mod 2^32)
  requires `speed == 2^29` exactly, which the `ulMinRPM/ulMaxRPM` clamp
  (lines 314-318, values from the card's VBIOS ATOM fan table) makes
  unreachable on real firmware — a div-by-zero needs a crafted VBIOS
  *and* a root hwmon write. The get path's `tach_period == 0` is guarded
  (line 110-111) and `usMaxFanRPM != 0` guards the percent path (80-84).

All writes in the file are masked register fields (`REG_SET_FIELD`) or
named PPTable members — no attacker-driven indexing, no copyin, no
allocations, no work items (the thermal IRQ registration the task
mentions lives outside this file). The same expression exists verbatim
in upstream Linux (long-standing port), where it is also unguarded.

## Recommended fix

Compute in 64 bits: `*speed = 60 * (uint64_t)crystal_clock_freq * 10000 /
tach_period;` (and likewise for `tach_period` on the set path), or
precompute `60 * xclk / 8` once as u64.
