phm_start_thermal_controller is the only phm_* dispatcher in hardwaremanager.c without PHM_FUNC_CHECK β unchecked hwmgr->adev (:238), hwmgr->hwmgr_func (:240) and stores through unchecked adev (:249-250): latent kernel NULL-deref (DF-1913/DF-4831 family)
Summary
The sole unguarded wrapper among ~25 siblings - derefs hwmgr, hwmgr_func, and stores through adev with no checks. Reachability dead today (shadowed by DF-1913's earlier deref on the init path; the resume path is pm_en-gated and every chip case assigns hwmgr_func or aborts). One-line PHM_FUNC_CHECK fix. Also: the set_pp_table lead does NOT pass through this file (copies directly in amd_powerplay.c) and the vega10_hwmgr.c:4915 clamp-underflow lead logged.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-4886 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 2.5 KB | β raw | |
| VERDICT.md | β | 4.8 KB | β raw | |
| trigger_sketch.c | β | 1.0 KB | view raw | |
| fix.diff | β | 558 B | view raw | |
| verdict.json | β | 3.5 KB | view raw | |
| manifest.json | β | 1.2 KB | view raw | |
| env.txt | β | 333 B | view raw |
DF-4886 β phm_start_thermal_controller: the only phm_* dispatcher in hardwaremanager.c without PHM_FUNC_CHECK (DF-1913/DF-4831 family)
File: sys/dev/drm/amd/powerplay/hwmgr/hardwaremanager.c:234-253
What
Every public wrapper in hardwaremanager.c opens with PHM_FUNC_CHECK(hwmgr)
(hardwaremanager.c:33-37: if ((hw) == NULL || (hw)->hwmgr_func == NULL)
return -EINVAL;) β every one except phm_start_thermal_controller, which:
- dereferences
hwmgrat :238 (struct amdgpu_device *adev = hwmgr->adev;) with no NULL test onhwmgritself; - dereferences
hwmgr->hwmgr_funcat :240 (if (hwmgr->hwmgr_func->get_thermal_temperature_range)) with no NULL test onhwmgr_func; - writes through the unchecked
adevat :249-250 (adev->pm.dpm.thermal.min_temp/max_temp = ...).
A NULL hwmgr_func (or NULL hwmgr) reaching this function is an
immediate kernel NULL-pointer dereference β panic (local DoS class at
worst; no controllable write).
Why latent (not reachable today β proven)
- Callers of
phm_start_thermal_controller:hwmgr_hw_init(hwmgr/hwmgr.c:249) andhwmgr_resume(hwmgr/hwmgr.c:321). - On the
hwmgr_hw_initpath, the DF-1913 site (hwmgr.c:214-216,!hwmgr->hwmgr_func->backend_initderef) fires first for any NULLhwmgr_funcβ the wrapper's missing guard is shadowed. hwmgr_resumegates only on!hwmgr || !hwmgr->pm_en(hwmgr.c:309-311), butpm_encan only be true afterpp_early_initβhwmgr_early_init(hwmgr.c:80-177) succeeded, and every chip-family case there assignshwmgr->hwmgr_funcviasmu7/smu8/vega10/vega12/vega20/smu10 _init_function_pointers()/*_hwmgr_init()(smu7_hwmgr.c:5140, smu8_hwmgr.c:2005, vega10_hwmgr.c:4992, vega12_hwmgr.c:2422, vega20_hwmgr.c:3604, smu10_hwmgr.c:1225) β unknown ASICs hit adefault: return -EINVALandpp_early_initpropagates it (amd_powerplay.c:85-87), aborting the probe before hw_init/resume can run.
So: same class as DF-1913 (Info) and DF-4831 (Info) β a latent
NULL-discipline hole that only a future chip path leaving hwmgr_func
NULL with pm_en true (or memory corruption) could arm. Mirror-the-guard
one-line fix.
Reproduce
Not runnable on the audit guest: requires an AMD GPU driven by powerplay (guest PCI tree has no AMD device β see env.txt). Static finding, code-certain by inspection.
Fix
Add PHM_FUNC_CHECK(hwmgr); as the first statement of
phm_start_thermal_controller (and optionally NULL-check adev).
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->adevwithhwmgritself unchecked; - :240 β derefs
hwmgr->hwmgr_func->get_thermal_temperature_rangewithhwmgr_funcunchecked (this is the exact load that NULL-faults); - :249-250 β stores
range.min/maxthrough the uncheckedadev.
Reachability trace (killed as reachable, kept as latent)
- Callers:
hwmgr_hw_init(hwmgr.c:249),hwmgr_resume(hwmgr.c:321). - hw_init path: a NULL
hwmgr_funccrashes earlier at the DF-1913 site (hwmgr.c:216!hwmgr->hwmgr_func->backend_init) β our site is shadowed. - resume path: gated only by
!hwmgr || !pm_en(hwmgr.c:309-311).pm_enis set inamd_powerplay_create(amd_powerplay.c:51) and flipped false only by hw_init's failure; but reaching resume at all requirespp_early_initβhwmgr_early_init(hwmgr.c:80-177) to have succeeded, and every family case there assignshwmgr_func(CI/CZ/VIβsmu7_*, RVβ smu10, AIβvega10/12/20) or returns -EINVAL, whichpp_early_initpropagates (amd_powerplay.c:85-87), aborting the probe. - Conclusion: no in-tree path delivers NULL
hwmgr_functo this wrapper. The hole arms only for a future chip family added tohwmgr_early_initwithout 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:382hardwareActivityPerformanceLevels - 1index 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 onpm_en(which implies backend_init ran). Cross-file lead, not this file's bug: vega10_get_performance_level (vega10_hwmgr.c:4915-4917) clamps withindex > count-1 ? count-1 : indexβ withperformance_level_count==0the 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):
rangedefaults {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.
Fix verification
not_testablefix validation not possible: latent hardware-gated finding (no AMD GPU on the audit guest); fix is a trivial one-line guard mirroring every sibling wrapper - static review only
fix.diff (git-apply-able vs read-only sys/ tree; never applied)
Confirmed kernel references
- sys/dev/drm/amd/powerplay/hwmgr/hardwaremanager.c:33
- sys/dev/drm/amd/powerplay/hwmgr/hardwaremanager.c:238
- sys/dev/drm/amd/powerplay/hwmgr/hardwaremanager.c:240
- sys/dev/drm/amd/powerplay/hwmgr/hardwaremanager.c:249
- sys/dev/drm/amd/powerplay/hwmgr/hwmgr.c:216
- sys/dev/drm/amd/powerplay/hwmgr/hwmgr.c:249
- sys/dev/drm/amd/powerplay/hwmgr/hwmgr.c:321
- sys/dev/drm/amd/powerplay/amd_powerplay.c:85
Detail
Evidence (decisive lines)
README.md (guard-vs-sibling analysis with path:line); VERDICT.md (reachability trace + pass-2 negative results incl. the set_pp_table non-participation proof and the vega10_get_performance_level count==0 cross-file lead); trigger_sketch.c; fix.diff
PoC changes
sketch only; kernel-internal NULL-discipline hole, no userspace trigger exists
Verified recommended fix
add PHM_FUNC_CHECK(hwmgr) as the first statement of phm_start_thermal_controller (one line, matches all sibling wrappers)
Verdict
Not runnable on the audit guest (no AMD GPU / powerplay device; PCI tree contains no AMD device). Code-certain by inspection: phm_start_thermal_controller (hardwaremanager.c:234-253) is the ONLY public phm_* dispatcher in hardwaremanager.c that omits PHM_FUNC_CHECK (macro at :33-37, used by all ~25 siblings) - it derefs hwmgr->adev at :238 with hwmgr unchecked, derefs hwmgr->hwmgr_func->get_thermal_temperature_range at :240 with hwmgr_func unchecked, and stores through the unchecked adev at :249-250. Latent, DF-1913/DF-4831 family: the hw_init caller (hwmgr.c:249) is shadowed by the DF-1913 deref at hwmgr.c:216 which fires first on any NULL hwmgr_func; the resume caller (hwmgr.c:321) is gated on pm_en, which requires hwmgr_early_init (hwmgr.c:80-177) to have succeeded - and every chip-family case there assigns hwmgr_func (smu7_hwmgr.c:5140, smu8_hwmgr.c:2005, vega10_hwmgr.c:4992, vega12_hwmgr.c:2422, vega20_hwmgr.c:3604, smu10_hwmgr.c:1225) or returns -EINVAL which pp_early_init propagates (amd_powerplay.c:85-87), aborting the probe. Impact ceiling is a NULL-deref panic (no attacker-controlled write); arms only for a future chip path leaving hwmgr_func NULL with pm_en true. One-line mirror-the-guard fix.
No comments yet.