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

Unchecked smu8 power-state casts: NULL deref (panic) when VBIOS powerplay table has no Boot-classified state β€” DF-4143 family twin for smu8

Summary

The cast_smu8_power_state helpers return NULL whenever the state's magic doesn't match, and smu8 dereferences the results unchecked at four sibling sites. psm_init_power_table only stamps hwmgr->request_ps/current_ps with the smu8 magic by memcpy'ing a Boot-classified state; nothing requires such a state to exist. With a crafted VBIOS lacking the Boot flag, init completes with current_ps magic-0, and the first runtime transition dereferences NULL at offset 0x30 -> panic. Also exposes the ps->level==0 -> levels[0xFFFFFFFF] underflow in get_performance_level. Crafted-VBIOS local DoS tied to firmware. NULL-guard x4 fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4169 Β· 21 files
FileTypeDescriptionSize
README.md β€” 2.2 KB ↓ raw
VERDICT.md β€” 9.1 KB ↓ raw
env.txt β€” 1.3 KB view raw
fix.diff β€” 1.9 KB view raw
build.log β€” 2.7 KB view raw
run.log β€” 10.1 KB view raw
run.2.log β€” 10.1 KB view raw
run.3.log β€” 10.1 KB view raw
verdict.json β€” 6.8 KB view raw
manifest.json β€” 2.4 KB view raw
rig/gen_rig.sh β€” 7.2 KB view raw
rig/check_verbatim.sh β€” 5.5 KB view raw
rig/rig_shims.h β€” 7.0 KB view raw
rig/smu8_rig.c β€” 23.7 KB view raw
rig/Makefile β€” 448 B ↓ download
rig/build.sh β€” 509 B view raw
rig/run.sh β€” 1.2 KB view raw
rig/smu8_slice.inc β€” 9.3 KB ↓ download
rig/smu8_slice_fixed.inc β€” 9.5 KB ↓ download
rig/smu8_slice_verbatim.h β€” 36.7 KB view raw
rig/rig_fix.diff β€” 1.8 KB view raw

DF-4169 β€” smu8 power-state casts unchecked: NULL deref when VBIOS has no Boot-classified state

  • File: sys/dev/drm/amd/powerplay/hwmgr/smu8_hwmgr.c primary site: smu8_apply_state_adjust_rules :1041-1045 cast β†’ :1051 NULL+0x30 WRITE, :1066 NULL+0x40 read sibling sites: smu8_dpm_get_sclk :1316-1321 (NULL+0x4/0x44; levels[0xFFFFFFFF] when level==0), smu8_get_current_shallow_sleep_clocks :1598-1601 (levels[0xFFFFFFFF]), smu8_get_performance_level :1569-1572 (clamp defeat β†’ unclamped levels[index])
  • Window creator: sys/dev/drm/amd/powerplay/hwmgr/pp_psm.c psm_init_power_state_table (kzalloc'd request_ps/current_ps only receive the smu8 magic via memcpy of a Boot-classified state; no Boot state in the VBIOS powerplay table => magic stays 0)
  • Class: NULL pointer dereference + level==0 index underflow, CWE-476/CWE-125. Hardware-gated (Carrizo/Bristol Ridge SMU8 APU, crafted VBIOS powerplay table). DF-4143 family twin for smu8.

Verification (2026-10-15, verbatim-rig evidence class)

REPRODUCED in byte-identical production code on the DragonFly guest (no AMD GPU β€” the in-kernel path is ASIC-gated; see VERDICT.md Β§1):

cd rig && sh build.sh /usr/src    # 26 verbatim blocks checker-proven, builds rig_base + rig_fixed
sh run.sh                         # baseline + fixed (this pack's fix.diff) over 11 modes

Expected (see run.log):

  • baseline 4169-* modes: SIGSEGV classified β€” WRITE at NULL+0x30 (smu8_apply_state_adjust_rules :1051), READ at NULL+0x4 / NULL+0x44 (smu8_dpm_get_sclk), ~64 GB wild reads (levels[0xFFFFFFFF], :1321/:1601), OOB read 324 B past the 320 B state allocation (smu8_get_performance_level clamp defeat, :1571-1572);
  • baseline 4169-boot-chain-control: no fault (Boot flag present β‡’ magic stamped) β€” isolates the precondition;
  • fixed build: all 4169-* modes return -EINVAL, no fault; clean-valid output byte-identical to baseline; 4171-* modes still fault under this fix (orthogonal finding DF-4171).

Logs: build.log, run.log, run.2.log, run.3.log (three byte-identical runs). Full narrative: VERDICT.md. fix.diff authored against the read-only sys/ tree, A/B validated in the rig, never applied to sys/.

VERDICT.md
↓ download raw

DF-4169 VERDICT (verify run, 2026-10-15)

Status: REPRODUCED (verbatim-rig evidence class). Every defect the finding enumerates was demonstrated in byte-identical production code β€” the NULL WRITE at NULL+0x30 exactly as filed, two more NULL reads, the literal levels[0xFFFFFFFF] wild reads (~64 GB) from level==0, and the smu8_get_performance_level clamp-defeat OOB read β€” and the pack's fix.diff was A/B-validated in the same rig: fixed (all guarded modes return -EINVAL, clean-valid byte-identical). In-kernel execution is AMD-ASIC-gated on this guest (no AMD GPU β€” proven, env.txt).

1. Method

The guest cannot attach amdgpu (0 vendors 0x1002, no /dev/dri). Following the DF-4165/4247/4388/4396 evidence class, the actual production functions were compiled into a guest-side harness:

  • rig/gen_rig.sh extracts the cited smu8_hwmgr.c functions byte-identical: :49-97 (smu8_magic, cast_smu8_power_state, cast_const_smu8_power_state, smu8_get_eclk_level), :99-127, :129-158 (level helpers), :248-252 (smu8_convert_8Bit_index_to_voltage), :1037-1080 smu8_apply_state_adjust_rules, :1251-1278, :1303-1322 smu8_dpm_get_sclk, :1556-1593 smu8_get_performance_level, :1595-1604 smu8_get_current_shallow_sleep_clocks, :1846-1874 β€” plus 16 header slices (power_state.h 26-187, hardwaremanager.h caps/platform descriptor/ PP_Clocks/PHM_PerformanceLevel, hwmgr.h magic enum + dependency tables, smu8_hwmgr.h state structs, cz_ppsmc.h message ids, kgd_pp_interface.h forced-level enum).
  • rig/check_verbatim.sh re-verified all 26 blocks byte-identical β€” run on the guest against /usr/src/sys, the tree its running kernel was compiled from (build.log: ALL VERBATIM CHECKS PASSED; guest /usr/src smu8_hwmgr.c md5 a0cb59b723434a78e88bf0580d5a5508 == repo).
  • The fixed build is the same functions + exactly this pack's fix.diff (applied to a copy of the pristine full file with patch --fuzz=0; proven bidirectionally: forward apply no rejects, reverse apply restores the pristine file byte-exactly; the rig applies a header-normalized copy whose hunk bodies are cmp-identical to the pack's fix.diff β€” only the ---/+++ path lines differ, checker-proven).
  • Harness shims (rig/rig_shims.h) touch no defect path: recording shims for smum_send_msg_to_smc_with_parameter / smu8_nbdpm_pstate_enable_\ disable / smu8_enable_disable_uvd_dpm; minimized struct pp_hwmgr / phm_dynamic_state_info / smu8_hwmgr / amd_pp_display_configuration with production names/types for every field the functions and the fix consult; power states allocated exactly as pp_psm.c does (kzalloc β‡’ ZEROED, size = sizeof(pp_power_state) + sizeof(smu8_power_state) β€” the production ps_size formula), VCE/UVD tables exactly as processpptables.c does. Allocations are guard-backed: end-anchored (first byte past the object faults) and lead-guard (first byte before the object faults); the fault classifier prints address, READ/WRITE (x86 #PF error bit from mc_err), and the class.

2. Observed (run.log; byte-identical in run.2.log/run.3.log, md5-equal)

Layout: need_dfs_bypass at offset 0x30 (48) β€” the finding's cited offset, confirmed; action at 0x40; levels at 68; smu8_power_level=16B; levels[0xFFFFFFFF] = state+0x1000000034 (64.0 GB).

Baseline β€” verbatim production code, per finding site:

mode observation verbatim site
4169-boot-chain psm loop over a 2-state VBIOS with no Boot flag leaves request_ps/current_ps magic=0 (kzalloc'd, never memcpy'd); first smu8_dpm_get_sclk β†’ SIGSEGV READ at NULL+0x4 (smu8_ps->level through the NULL cast) :1316β†’:1321; chain pp_psm.c:53-94
4169-boot-chain-control same VBIOS WITH one Boot state β†’ magic stamped, get_sclk(high)=60000, no fault β€” the precondition is exactly the Boot flag pp_psm.c:89-94
4169-null-adjust-rules SIGSEGV WRITE at NULL+0x30 β€” smu8_ps->need_dfs_bypass = true through the NULL cast (exactly the filed "NULL at offset 0x30") :1041/:1044 β†’ :1051
4169-null-get-sclk SIGSEGV READ at NULL+0x44 β€” smu8_ps->levels[0].engineClock :1316 β†’ :1319
4169-level0-get-sclk magic-valid state with level==0 β†’ read at state+68719476900 (~64 GB) = levels[level-1] = levels[0xFFFFFFFF] (uint32 wrap) :1321
4169-level0-perf-unclamped level==0 defeats the :1571 clamp (9 > 0xFFFFFFFF false) β†’ levels[9] read 324 B past the end of the 320 B state allocation (12 B past the smu8 tail) :1571-1572
4169-level0-shallow-sleep read at state+68719476906 (~64 GB) β€” literal ps->levels[ps->level - 1] with level==0 :1601
clean-valid all five functions on a valid 2-level state: rc=0, values exactly as computed (get_sclk 30000/60000, vddc 1525, msgs [0x20,0x1]/[0x1c,0x1]) β€”

Fixed (this pack's fix.diff, identical inputs): every 4169 mode returns -EINVAL (rc=-22; get_sclk returns it through its uint32_t return as 4294967274) without faulting; boot-chain still returns no clock but no NULL deref; clean-valid output byte-identical to baseline. The two DF-4171 modes still fault under this fix (orthogonality, see DF-4171's pack). Fix A/B validated.

3. Primitive and impact

  1. Kernel NULL-page WRITE (:1051, NULL+0x30) and NULL reads (:1066 NULL+0x40 via smu8_current_ps->action, :1319 NULL+0x44): kernel-mode fault on the unmapped null page β‡’ fatal trap β‡’ kernel panic. Impact class: panic (local DoS), exactly as filed.
  2. Wild ~64 GB reads (levels[0xFFFFFFFF], :1321/:1601): guaranteed unmapped in kernel space β‡’ fatal trap β‡’ panic. (Not an info leak: the value would have to be mapped and returned; the fault is the outcome.)
  3. OOB read past the power-state allocation (:1571-1572 clamp defeat): reads adjacent kernel heap (up to index unclamped) into level->coreClock β€” a PHM_PerformanceLevel that sysfs/DPM queries return to userland; a bounded heap over-read with a userland-visible sink in-kernel (in the rig: guard-detected at +324/+320).

Threat model (family's established one): crafted/corrupted GPU VBIOS (Carrizo/Bristol Ridge SMU8 β€” the smu8_hwmgr.c hwmgr binds to CZ APUs) parsed at driver attach, or root re-parsing a crafted blob via /sys/class/drm/card0/device/pp_table. No unprivileged userland trigger on stock hardware β€” same class as DF-4143/4247. Demonstrated out-of-kernel context (rig); the in-kernel consequence (NULL write β‡’ panic) follows from the verbatim instruction stream.

4. Fix validation

rig_fixed = same verbatim slice + this pack's fix.diff (checker-proven identity, bidirectional patch proof). All 4169 modes: no fault β€” guards return -EINVAL for NULL casts and level==0; clean-valid: byte-identical to baseline. The 4171 modes faulting under this build prove the two findings' fixes are orthogonal and that the 4169 modes are not artifacts. A rebuilt-kernel validation was not performed: the fix sites execute in-kernel only on AMD ASICs absent from this guest; per the verbatim-rig protocol the fixed slice was A/B-validated in the same rig. fix.diff is authored against the pristine read-only sys/ tree and was never applied there (guest /usr/src verified pristine by md5 before and after: a0cb59b723434a78e88bf0580d5a5508).

5. Honest limitations

  • Demonstrated with production instructions and production struct layouts in a userland mapping, not the kernel heap; the harness builds the precondition (zeroed/level-0 states) directly rather than by parsing a full crafted ATOM ROM (the psm loop that produces the zeroed request_ps/current_ps is harness-reimplemented from pp_psm.c:76-95 and driven with/without the Boot flag β€” the control run isolates the precondition exactly).
  • smu8_dpm_get_sclk returns uint32_t, so the fixed build's -EINVAL is observable as 4294967274 (production return-type quirk; callers at this call site only log/format the value).
  • The finding's summary attributes the levels[0xFFFFFFFF] underflow to get_performance_level; measured, :1571 in that function manifests as clamp defeat (unclamped levels[index] OOB), while the literal levels[level-1] 0xFFFFFFFF wrap occurs at :1321 and :1601. Both variants demonstrated; both covered by the fix's level == 0 guards.

6. Bottom line

The finding is verified end to end: the four unchecked cast sites and the level==0 arithmetic reproduce in byte-identical production code at the exact filed offsets (NULL+0x30 WRITE, NULL+0x4/0x40/0x44 reads, ~64 GB wild reads, clamp-defeat OOB read), the precondition (no Boot-classified state β‡’ zeroed request_ps/current_ps) is isolated by a control run, and the pack's fix.diff neutralizes every demonstrated mode on identical input while leaving valid-state behavior byte-identical.

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

fixed
baseline reproduced→ patch + rebuild →patched clean

A/B validated in the same rig: rig_fixed = same verbatim slice + this pack's fix.diff (forward apply with --fuzz=0, no rejects; reverse apply restores the pristine file byte-exactly; hunk bodies cmp-identical to the pack's fix.diff). On identical inputs every demonstrated 4169 mode is neutralized (rc=-EINVAL, no fault, no NULL write, no wild read, no OOB read) while clean-valid output stays byte-identical to baseline; the DF-4171 modes still fault under this fix, proving orthogonality (they are a different finding). Rebuilt-kernel validation not performed: the fix sites execute in-kernel only on AMD ASICs absent from this guest (verbatim-rig protocol, DF-4165/4247/4388/4396).

run.log FIXED sections: '4169-null-adjust-rules rc=-22 ... returned WITHOUT faulting (guard active in fixed build)'; '4169-level0-perf-unclamped rc=-22 coreClock=0'; clean-valid lines identical to baseline; check output in build.log: 'ALL VERBATIM CHECKS PASSED' incl. 'smu8_slice_fixed.inc == pristine file + fix.diff (function blocks)' and 'reverse-applying fix.diff restores the pristine file byte-exactly'
↓ fix.diffper-fix-DF-4169

Confirmed kernel references

Detail

Exploit chain

crafted/corrupted VBIOS (flashed ROM or vfio-pci romfile) on a Carrizo/Bristol Ridge SMU8 APU -> amdgpu attach -> pptable parse produces no Boot-classified state -> psm_init_power_table leaves request_ps/current_ps zeroed -> driver init completes -> first runtime power-state transition (smu8_apply_state_adjust_rules) or sysfs clock query (smu8_dpm_get_sclk) dereferences the NULL cast -> kernel-mode fault on the unmapped null page -> panic (local DoS). level==0 states additionally give ~64GB wild reads and clamp-defeat OOB reads past the state allocation. No unprivileged userland trigger on stock hardware; no content control beyond the fault.

Evidence (decisive lines)

findings/poc/DF-4169/run.log: '[BASELINE] MODE 4169-null-adjust-rules -> !! SIGSEGV (addr=0x30, WRITE) => NULL DEREF: WRITE at NULL+0x30'; 'MODE 4169-level0-get-sclk -> fault 68719476900 bytes from the state base (~64GB): WILD levels[0xFFFFFFFF]-STYLE READ'; 'MODE 4169-level0-perf-unclamped -> fault 324 bytes PAST THE END of the 320B object'; '[FIXED]' sections show rc=-22/no fault for every 4169 mode; build.log: 26 verbatim blocks ALL PASSED on the guest against /usr/src; run.2/run.3.log md5-identical

PoC changes

Seed pack had no runnable PoC ('not runnable here'). Built the verbatim rig per the DF-4388 pattern: gen_rig.sh/check_verbatim.sh extract and guest-verify 26 byte-identical blocks (10 function slices incl. all four cited cast sites + their verbatim callees, 16 header slices); rig_shims.h minimizes pp_hwmgr/dyn_state/smu8_hwmgr with production field names/types, recording shims for the three off-defect-path SMU helpers, guard-backed kzalloc shims (end-anchored + lead-guard); smu8_rig.c drives 11 modes with a read/write-classifying fault handler (DragonFly mc_err). Fixed build applies the pack's fix.diff to a pristine copy (header-normalized, hunk bodies cmp-identical) and re-extracts the patched functions; both directions patch-proven.

Verified recommended fix

NULL-guard the four unchecked cast sites and bail on level==0: apply fix.diff (return -EINVAL when smu8_ps/smu8_current_ps/ps is NULL or ps->level==0 in smu8_apply_state_adjust_rules, smu8_dpm_get_sclk, smu8_get_performance_level, smu8_get_current_shallow_sleep_clocks).

Verdict

Every defect the finding enumerates was demonstrated in byte-identical production smu8_hwmgr.c code on the DragonFly guest (verbatim-rig evidence class; in-kernel execution is AMD-ASIC-gated, no AMD GPU in the guest). The finding's exact NULL+0x30 write was reproduced: with a VBIOS power table lacking any Boot-classified state, psm_init_power_table (pp_psm.c:53-94) leaves hwmgr->request_ps/current_ps kzalloc-zeroed (magic=0), cast_smu8_power_state returns NULL, and smu8_apply_state_adjust_rules:1051 executes 'smu8_ps->need_dfs_bypass = true' as a kernel NULL-page WRITE at exactly offset 0x30 (fault classifier: SIGSEGV addr=0x30 WRITE) -- in-kernel a fatal trap/panic. Sibling sites confirmed the same way: NULL+0x4 and NULL+0x44 reads in smu8_dpm_get_sclk (:1321/:1319), the literal levels[0xFFFFFFFF] wild reads ~64GB out (:1321, :1601) when a magic-valid state has level==0, and the smu8_get_performance_level clamp defeat (:1571: 'index > 0xFFFFFFFF' is never true, so index passes unclamped) reading 324B past the 320B state allocation (:1572). A Boot-flag control run isolates the precondition exactly, and the pack's fix.diff was A/B-validated in the same rig: all guarded modes return -EINVAL without faulting, clean-valid output byte-identical, and the DF-4171 modes keep faulting under it (orthogonality proof). Three runs byte-identical.