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

vega12_pp_tables_uninitialize NULL-deref on second pptable_fini: hwmgr_hw_init err1/err2 already freed hwmgr->pptable while pm_en/dpm_enabled stay set, so the next hwmgr_hw_fini (second pp_table sysfs write via amd_powerplay_reset, driver teardown, GPU reset) faults at NULL+16 β€” kernel panic

Summary

The fini derefs the cached pptable unguarded while hw_init's error paths already fini'd it once (pm_en stays stale-true, dpm_enabled never cleared on error) - a root pp_table write whose re-init fails arms the next fini to fault at NULL+16. Harness reproduced (SIGSEGV at the kernel-equivalent offset); NULL-guard fix compiled into a full -Werror amdgpu.ko. Secondary kzalloc leak on the err: path documented. Root-gated panic, DF-4546 convention.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4951 Β· 13 files
FileTypeDescriptionSize
vega12_dfini_null.c β€” 16.4 KB view raw
build.sh β€” 124 B view raw
run.sh β€” 100 B view raw
run.log β€” 2.1 KB view raw
run.stability.log β€” 20 B view raw
fix_run.log β€” 2.8 KB view raw
fix.diff β€” 1005 B view raw
fix_build.log β€” 1.2 MB ↓ download
env.txt β€” 691 B view raw
README.md β€” 3.1 KB ↓ raw
VERDICT.md β€” 4.3 KB ↓ raw
manifest.json β€” 1.9 KB view raw
verdict.json β€” 5.3 KB view raw

DF-4951 β€” vega12_processpptables: double pptable_fini NULL deref

File: sys/dev/drm/amd/powerplay/hwmgr/vega12_processpptables.c Class: CWE-476 (NULL pointer dereference) β€” kernel panic, root/hw-failure precondition Severity: Low Β· Bucket: base:drm

What

vega12_pp_tables_uninitialize() (vega12_processpptables.c:296-320) caches pp_table_info = hwmgr->pptable and immediately dereferences it (kfree(pp_table_info->power_saving_clock_max), :301) with no NULL guard. The caller contract makes hwmgr->pptable == NULL a reachable state: hwmgr_hw_init()'s error paths err1:/err2: (hwmgr.c:257-262) already invoke pptable_fini() once β€” freeing the pptable and NULLing hwmgr->pptable β€” before returning an error, while hwmgr->pm_en stays TRUE (it is only cleared when pptable funcs are missing, hwmgr.c:216-219) and pm.dpm_enabled retains its stale boot-time TRUE (nothing clears it on the error path).

Any subsequent hwmgr_hw_fini() then re-enters the function and faults at NULL+offset. Reachable triggers after one failed powerplay re-init:

  • a second root write to /sys/class/drm/card0/device/pp_table (amdgpu_pm.c:463 β†’ amd_powerplay.c:676 pp_dpm_set_pp_table, gated only on hwmgr && hwmgr->pm_en β†’ amd_powerplay_reset β†’ hwmgr_hw_fini);
  • driver teardown / GPU reset paths that reach pptable_fini (the pp IP block's status.hw was set at boot and is not cleared by amd_powerplay_reset).

The failing re-init itself is any hwmgr_hw_init failure at err1/err2 (backend_init / psm_init_power_state_table / phm_setup_asic / enable_dsm failure β€” e.g. SMU task timeout after a pp_table swap, the classic sleep/wake or table-upload hiccup).

Reproduce (lifted-copy harness; in-kernel path needs a Vega12 ASIC)

./build.sh   # cc -O2 -Wall -o vega12_dfini_null vega12_dfini_null.c
./run.sh     # ./vega12_dfini_null

Expected (run.log): - CASE 1 BOOT-OK (healthy boot + healthy reset): clean exit. - CASE 2 BUG (write#1 β†’ pptable_init OK, backend FAILS β†’ err1 fini inside init; write#2 β†’ fini again): SIGSEGV fault address 0x10 (power_saving_clock_max at phm_ppt_v3_information+16 β€” kernel-x86-64 offset) = the kernel panic-equivalent. - CASE 3 CONTROL (write#1 fails at the header check β†’ err: path leaks the fresh kzalloc but does NOT fini; write#2 frees it): survives β€” proving the crash specifically requires the err1/err2 (fini-inside-init) failure and demonstrating the secondary kmem-leak defect on the err: path.

Patched build: cc -O2 -Wall -DPATCHED ... β†’ CASE 2/4 survive (fix_run.log).

Fix validation

  • git apply --check fix.diff OK (host tree and guest /usr/src).
  • Full amdgpu.ko module rebuild with the fix under the real kernel toolchain, -Werror, -j8: RC=0 in 24 s, vega12_processpptables.o recompiled at fix_build.log:346-347, 0 diagnostics (fix_build.log).
  • Guest /usr/src restored and md5-verified afterwards.

Artifacts

vega12_dfini_null.c Β· build.sh Β· run.sh Β· build (in run.log header) Β· run.log Β· run.stability.log Β· fix_run.log Β· fix.diff Β· fix_build.log Β· env.txt Β· VERDICT.md Β· manifest.json Β· verdict.json

VERDICT.md
↓ download raw

VERDICT β€” DF-4951

Reproduced: YES (lifted-copy harness on the audit guest; in-kernel path hardware-gated β€” no Vega12 ASIC)

Root cause (proven, path:line)

  • vega12_processpptables.c:296-301 β€” vega12_pp_tables_uninitialize() takes pp_table_info = hwmgr->pptable and dereferences it at the very first statement (kfree(pp_table_info->power_saving_clock_max)) with no NULL guard.
  • hwmgr.c:222-231 + 257-262 β€” hwmgr_hw_init() calls pptable_init (:222) then backend_init (:229); on backend/power-state/ asic/DSM failure it jumps to err1:/err2: which already call pptable_fini() before returning the error. After such a failure hwmgr->pptable == NULL.
  • hwmgr.c:216-219 β€” pm_en is not cleared on these error paths (only when pptable/hwmgr funcs are missing); amdgpu_pm.c / amd_powerplay.c never clear pm.dpm_enabled on the error path either.
  • amd_powerplay.c:676-707 β€” pp_dpm_set_pp_table (sysfs pp_table write, root-only) gates only on hwmgr && hwmgr->pm_en (:680-681) and calls amd_powerplay_reset (:697) β†’ hwmgr_hw_fini (:665) β†’ pptable_fini (hwmgr.c:280-281) β†’ NULL deref at phm_ppt_v3_information+16 (hwmgr.h:579-594 layout, x86-64).
  • The pp IP block's status.hw (amdgpu_device.c:1572) was set at boot and is not cleared by amd_powerplay_reset, so driver teardown after a failed reset re-enters the same path.

Harness result (run.log)

  • CASE 1 BOOT-OK: healthy init + healthy pp_table reset β†’ clean.
  • CASE 2 BUG: boot OK β†’ root pp_table write#1 whose re-init fails at backend_init (err1 β†’ fini inside init, hwmgr->pptable = NULL) β†’ root pp_table write#2 (or teardown) β†’ hwmgr_hw_fini β†’ vega12_pp_tables_uninitialize β†’ SIGSEGV at fault address 0x10 (power_saving_clock_max = pptable+16) β€” the exact kernel-equivalent fault (kfree(NULL->power_saving_clock_max)).
  • CASE 3 CONTROL: write#1 failing at the header check takes the err: path (hwmgr.c:223-224) which does NOT fini β€” write#2 then frees the leaked (non-NULL) pptable cleanly. This isolates the crash to the err1/err2 failure mode and simultaneously evidences the secondary defect: hwmgr->pptable (kzalloc'd at vega12:272) leaks on every pptable_init failure (the err: path never finis); a root loop of pp_table writes with a header-invalid table grows kmem unboundedly (~small alloc per iteration). Not separately verified as a finding β€” noted here and folded conceptually into the fix discussion.
  • PATCHED (fix.diff NULL guard, -DPATCHED): CASE 2/4 survive, no fault (fix_run.log).

Impact

Kernel NULL-deref panic (CWE-476), fixed fault offset (+16 read then kfree of garbage-free NULL β€” no data control). Precondition: one failed powerplay re-init (SMU/backend hiccup after a pp_table upload or during runtime re-init) plus a second root-triggered fini (pp_table write, rmmod/teardown, GPU reset). Root-or-failing-hardware gated β†’ Low, consistent with the DF-4546 convention for pre-conditioned panics. No unprivileged path: the sysfs node is 0644 root-writable, and the failing re-init itself requires either root's table or an SMU failure.

Fix validation

  • git apply --check OK on the read-only host tree and on guest /usr/src.
  • Full amdgpu.ko rebuild with fix.diff applied, real kernel toolchain, -Werror, -j8: RC=0, 0 diagnostics, 24 s (fix_build.log; vega12_processpptables.o at :346-347).
  • Harness PATCHED control: the previously-fatal CASE 2 sequence survives.
  • Guest /usr/src restored (git apply -R), md5-verified against the pristine tree (085571ce1e8f9c2fafdd71e4e3b2cc95), module objdir cleaned.

fix.diff: early-return NULL guard in vega12_pp_tables_uninitialize (11 added lines, no behavior change for the healthy path). Longer term, hwmgr.c's err: path should also fini (or init's early returns should free) to close the leak β€” out of this file's scope, noted for the maintainer.

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_testable
baseline reproduced→ patch + rebuild →patched clean

Patched kernel not runnable on the guest (no Vega12 ASIC). fix.diff is git-apply-able (host + guest), compiles clean in a full amdgpu.ko module build under the real kernel toolchain with -Werror (fix_build.log, RC=0, 0 diagnostics), and the harness PATCHED control survives the previously-fatal sequence.

fix.diff; fix_build.log (full module rebuild RC=0, vega12_processpptables.o recompiled at log:346-347, 0 'error:' lines); fix_run.log (PATCHED cases 2/4 survive, PATCHED_RUN_RC=0); guest /usr/src restored + md5-verified (085571ce1e8f9c2fafdd71e4e3b2cc95)
↓ fix.diffper-fix-DF-4951

Confirmed kernel references

Detail

Exploit chain

root pp_table sysfs write #1 (or runtime powerplay re-init) fails at backend_init/err2 -> pptable_fini already ran, hwmgr->pptable=NULL, pm_en/dpm_enabled stale true -> root pp_table write #2 / rmmod / GPU reset -> hwmgr_hw_fini -> vega12_pp_tables_uninitialize -> kfree(NULL->power_saving_clock_max) load at NULL+16 -> kernel panic. DoS only: fixed NULL offset, no data control; requires one failed re-init plus a root-triggered (or hardware-failure) second fini.

Evidence (decisive lines)

run.log (CASE 2: '>>> SIGSEGV: fault address 0x10 (power_saving_clock_max = pptable + 0x10)' + 'PANIC-CLASS FAULT REPRODUCED'; CASE 1 clean; CASE 3 survives showing err:-path leak); run.stability.log (repeat runs); fix_run.log (PATCHED: case 2 + case 4 survive, PATCHED_RUN_RC=0); fix_build.log (full amdgpu.ko rebuild with fix.diff, -Werror, RC=0, vega12_processpptables.o at :346); fix.diff (git apply --check OK)

PoC changes

authored fresh this run (pass 2); no seed PoC existed

Verified recommended fix

NULL-guard early return in vega12_pp_tables_uninitialize (fix.diff); separately, hwmgr_hw_init's err: path should fini the partially initialized pptable to close the leak

Verdict

vega12_pp_tables_uninitialize (vega12_processpptables.c:296-301) dereferences hwmgr->pptable with no NULL guard at its first statement. hwmgr_hw_init's err1/err2 error paths (hwmgr.c:257-262) already invoke pptable_fini once, leaving hwmgr->pptable NULL, while hwmgr->pm_en stays TRUE (hwmgr.c:216-219 only clears it for missing funcs) and pm.dpm_enabled is never cleared on error; a subsequent hwmgr_hw_fini -- second pp_table sysfs write (amdgpu_pm.c:463 -> amd_powerplay.c:676/:697 amd_powerplay_reset, gated only on pm_en), driver teardown or GPU reset (pp IP block status.hw not cleared by the reset path) -- re-enters the function and faults at phm_ppt_v3_information+16 (hwmgr.h:579-594). Reproduced on the audit guest with a faithful lifted-copy harness: CASE 2 (pp_table write whose re-init fails at backend_init -> fini-inside-init, then a second write) SIGSEGVs at fault address 0x10, the exact kernel-equivalent NULL deref; CASE 1 (healthy) and CASE 3 (header-check failure on the err: path, which leaks but never finis) survive, isolating the crash to the err1/err2 mode and evidencing the secondary kmem-leak (vega12:272 kzalloc never freed on init failure); PATCHED build with the fix.diff NULL guard survives. In-kernel path hardware-gated (no Vega12 ASIC on the QEMU guest).