# DF-4886 VERDICT

**Status: untested** (latent/static; the guest has no AMD GPU — `pciconf -l`
shows no AMD/ATI device, env.txt) — same treatment as the DF-1913/DF-4831
family findings this belongs to.

## What is certain (by inspection)

`phm_start_thermal_controller` (hardwaremanager.c:234-253) is the **only**
public dispatcher in `sys/dev/drm/amd/powerplay/hwmgr/hardwaremanager.c`
that does not begin with `PHM_FUNC_CHECK(hwmgr)` (macro at :33-37, used by
all ~25 siblings). It:

- :238 — derefs `hwmgr->adev` with `hwmgr` itself unchecked;
- :240 — derefs `hwmgr->hwmgr_func->get_thermal_temperature_range` with
  `hwmgr_func` unchecked (this is the exact load that NULL-faults);
- :249-250 — stores `range.min/max` through the unchecked `adev`.

## Reachability trace (killed as *reachable*, kept as *latent*)

1. Callers: `hwmgr_hw_init` (hwmgr.c:249), `hwmgr_resume` (hwmgr.c:321).
2. hw_init path: a NULL `hwmgr_func` crashes earlier at the DF-1913 site
   (hwmgr.c:216 `!hwmgr->hwmgr_func->backend_init`) — our site is shadowed.
3. resume path: gated only by `!hwmgr || !pm_en` (hwmgr.c:309-311). `pm_en`
   is set in `amd_powerplay_create` (amd_powerplay.c:51) and flipped false
   only by hw_init's failure; but reaching resume at all requires
   `pp_early_init` → `hwmgr_early_init` (hwmgr.c:80-177) to have succeeded,
   and every family case there assigns `hwmgr_func` (CI/CZ/VI→smu7_*, RV→
   smu10, AI→vega10/12/20) or returns -EINVAL, which `pp_early_init`
   propagates (amd_powerplay.c:85-87), aborting the probe.
4. Conclusion: **no in-tree path delivers NULL `hwmgr_func` to this
   wrapper**. The hole arms only for a future chip family added to
   `hwmgr_early_init` without an initializer, or under prior memory
   corruption. Hence Info / latent hardening, mirroring how DF-1913 and
   DF-4831 were graded.

## Impact ceiling

NULL-deref panic (local DoS) — the out-of-range writes at :249-250 are to a
fixed offset of `adev->pm.dpm.thermal` with fixed kernel-produced values
(`range` = backend-filled {0..80000}); no attacker-controlled write.

## Fix

One line: `PHM_FUNC_CHECK(hwmgr);` at the top of the function (plus, if
desired, `if (!adev) return -EINVAL;` after :238). A git-apply-able
`fix.diff` is included for completeness even though no runtime validation
was possible.

## Negative results recorded while in-file (pass-2 depth)

- `phm_get_clock_info` :382 `hardwareActivityPerformanceLevels - 1` index
  underflow — killed: every backend assigns a nonzero constant
  (`{SMU7,SMU8,SMU10,VEGA10,VEGA12,VEGA20}_MAX_HARDWARE_POWERLEVELS`) during
  backend_init (smu7_hwmgr.c:2576, smu8_hwmgr.c:1107, smu10_hwmgr.c:503,
  vega10_hwmgr.c:883, vega12_hwmgr.c:404, vega20_hwmgr.c:414) and all
  amd_powerplay.c callers gate on `pm_en` (which implies backend_init ran).
  *Cross-file lead, not this file's bug:* vega10_get_performance_level
  (vega10_hwmgr.c:4915-4917) clamps with
  `index > count-1 ? count-1 : index` — with `performance_level_count==0`
  the clamp underflows to 0xFFFFFFFF and passes any index through; index is
  still ≤7 from this wrapper, so at most an uninitialized-array read. Filed
  for the vega10_hwmgr.c owner, not here.
- set_pp_table lead (amd_powerplay.c:693 `memcpy(hardcode_pp_table, buf,
  size)`): **hardwaremanager.c does not participate** — pp_dpm_set_pp_table
  copies the table directly in amd_powerplay.c and only afterwards calls
  hwmgr_hw_fini/init + hwmgr_handle_task; no hardwaremanager wrapper takes
  a size or pointer to the pp table (verified: the only phm_* on that path
  are phm_setup_asic/phm_enable_dsm/phm_start_thermal_controller — all
  no-arg table-free dispatches).
- Unchecked pass-through params (`clock`, `clocks`, `clock_ranges`,
  `pstate1/2`, `level`, `equal`) in the other wrappers — killed: all
  callers are amd_powerplay.c pp_* functions that NULL-check inputs before
  calling (e.g. pp_display_clock_voltage_request amd_powerplay.c:1173-1181
  checks `!clock`; pp_set_watermarks_for_clocks_ranges checks
  `!clock_ranges`), and the objects are kernel-internal dpm structs, never
  user pointers.
- Thermal range capping (:237-250): `range` defaults {0, 80000}; only the
  backend can widen it; values land in adev->pm.dpm.thermal for display
  only. No user-controlled source. Killed.
- u32 kHz clock values (`min_dcef_deep_sleep_set_clk`, display clock
  requests): kernel-computed by the DC layer, pass-through only; wrap
  arithmetic in backends is a backend concern (vega10_* audited clean on
  these). Killed.

## 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.
