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

pptable init reports SUCCESS with NULL OD arrays: revision gate (ucODTableRevision != 1) skips all OD copies and five copy-helper -ENOMEM returns are ignored β†’ unconditional NULL-deref in vega20_od8_set_feature_capabilities, kernel panic at amdgpu driver load

Summary

Two independent gaps leave the OD arrays NULL while init returns 0: the revision gate silently skips all copies for any other legal revision, and the five copy-helper -ENOMEM returns are dropped. Ordering guarantees the crash: pptable_init runs before backend_init, whose first array access dereferences NULL. Harness-reproduced on guest: crafted rev=2 passes every header check, init prints SUCCESS with NULL arrays, consumer SIGSEGVs at 0x0; the ENOMEM variant faults identically; the PATCHED init refuses (-EINVAL) with no fault. Deterministic load-time panic (DoS), also reachable under plain memory pressure.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4545 Β· 14 files
FileTypeDescriptionSize
od_null_deref.c β€” 9.9 KB view raw
build.sh β€” 116 B view raw
run.sh β€” 96 B view raw
build.log β€” 17 B view raw
run.log β€” 1003 B view raw
run.2.log β€” 861 B view raw
fix_run.log β€” 1003 B view raw
fix.diff β€” 2.9 KB view raw
fix_build.log β€” 8.6 KB view raw
env.txt β€” 335 B view raw
README.md β€” 2.9 KB ↓ raw
VERDICT.md β€” 3.1 KB ↓ raw
manifest.json β€” 1.3 KB view raw
verdict.json β€” 4.3 KB view raw

DF-4545 β€” vega20 pptable init reports SUCCESS with NULL OD arrays β†’ NULL-deref panic at driver load

Severity: Medium (crafted-VBIOS deterministic kernel panic) | Confidence: certain File: sys/dev/drm/amd/powerplay/hwmgr/vega20_processpptables.c:823,835-846,870-877,880-891

What

Two independent gaps leave hwmgr->pptable->od_feature_capabilities / od_settings_max / od_settings_min NULL while vega20_pp_tables_initialize() returns 0 (success):

  1. The revision gate (:823): the three OD copies run only when OverDrive8Table.ucODTableRevision == 1. Any other legal value (0, 2, 3, ...) skips them entirely β€” no error, arrays stay NULL. The PowerSaving clock copies (:864-878) have the same shape.
  2. Ignored return codes (CWE-252): the five copy calls (:835-846, :870-877) drop -ENOMEM on the floor, so an allocation failure also yields NULL arrays with a "successful" init.

vega20_pp_tables_initialize (hwmgr.c:222) runs before backend_init (hwmgr.c:229); vega20_hwmgr_backend_init β†’ vega20_enable_dpm_tasks β†’ vega20_od8_initialize_default_settings (vega20_hwmgr.c:1598) β†’ vega20_od8_set_feature_capabilities, whose first array access (vega20_hwmgr.c:906, od_feature_capabilities[0] with GNLD_DPM_GFXCLK enabled on real silicon) dereferences NULL β†’ kernel panic during amdgpu driver load. The fill loop (:1219) would deref NULL likewise.

Same untrusted-firmware threat model as knowns DF-1689/DF-1690 but a different defect class (NULL deref / skipped-init-reported-as-success vs OOB read of the blob). Note also :857 reads ODSettingsMax[ATOM_VEGA20_ODSETTING_POWERPERCENTAGE] unconditionally even when the revision gate skipped parsing β€” garbage TDPODLimit can set the PowerControl cap.

Reproduce (lifted-copy harness)

./build.sh && ./run.sh

Expected (run.log): - CONTROL rev=1 healthy alloc β†’ init SUCCESS, consumer completes, no fault - VARIANT A crafted ucODTableRevision=2 β†’ check_powerplay_tables PASSES (format rev 11, structuresize>0, smcPPTable.Version=3), init returns 0 with od_feature_capabilities=0x0, consumer faults: SIGSEGV at address 0x0 β€” panic-class - VARIANT B rev=1 + injected kzalloc failure (the ignored -ENOMEM) β†’ same fault at 0x0 - PATCHED (fix.diff: checked returns + refuse-to-succeed-with-NULL) β†’ init returns -EINVAL, consumer never runs, no fault

Fix validation

  • fix.diff β€” git apply --check OK; return codes checked + NULL guard that fails init with -EINVAL
  • full amdgpu.ko rebuild with the patch under the real kernel toolchain (-Werror): RC=0 (fix_build.log); guest /usr/src restored
  • harness PATCHED control: crafted rev=2 refused at init, no fault

Files

  • od_null_deref.c (sha256 97242d7d577fb00d2cc1ea71b6a54a0758c8caa2a5df42f3b659601a1fac0739)
  • build.sh / run.sh, build.log / run.log / run.2.log / fix_run.log
  • fix.diff, fix_build.log, env.txt, VERDICT.md, manifest.json, verdict.json
VERDICT.md
↓ download raw

VERDICT β€” DF-4545

Reproduced: YES (lifted-copy harness on the audit guest; in-kernel path hardware-gated)

  • Root cause proven (vega20_processpptables.c):
  • :823 β€” the ucODTableRevision == 1 gate around ALL three OD copies; revision != 1 skips them silently.
  • :835-846 / :870-877 β€” the five copy-helper return codes are dropped (CWE-252); a -ENOMEM leaves the pointers NULL.
  • :889-891 β€” init returns result of append_vbios_pptable (0), so vega20_pp_tables_initialize reports SUCCESS with NULL od arrays.
  • Consumer proven (vega20_hwmgr.c): backend_init (hwmgr.c:229, after pptable_init at :222) β†’ vega20_enable_dpm_tasks β†’ vega20_od8_initialize_default_settings (:1598) β†’ vega20_od8_set_feature_capabilities β€” first deref od_feature_capabilities[ ATOM_VEGA20_ODFEATURE_GFXCLK_LIMITS] at :906, guarded only by smu_features[GNLD_DPM_GFXCLK].enabled (true on functioning silicon). No NULL check anywhere on the path (checked :906-:976 and :1216-1221).
  • Harness result (run.log): VARIANT A (crafted ucODTableRevision=2, all header checks pass, Version=3) β†’ init SUCCESS with od_feature_capabilities=0x0 β†’ consumer SIGSEGV at 0x0 β€” the kernel-equivalent panic. VARIANT B (rev=1, injected kzalloc failure) β†’ identical fault, proving the ignored -ENOMEM is equally fatal. CONTROL (rev=1 healthy) runs clean; PATCHED (fix.diff) refuses init (-EINVAL) and the consumer never runs.

Impact

Deterministic kernel panic (NULL-deref) at amdgpu driver load on a machine whose VBIOS carries an OverDrive8 revision other than 1 β€” a value the driver parses but never accepts, reporting success instead of rejecting the table. Same crafted-firmware precondition as DF-1689/DF-1690 (Medium per family convention); also a memory-pressure-induced variant. DoS only β€” NULL write at a fixed offset 0, no controllable data.

Fix validation

  • git apply --check OK
  • Full amdgpu.ko rebuild with the fix under the real kernel toolchain with -Werror: RC=0 (fix_build.log); /usr/src restored + verified
  • Harness PATCHED control: crafted table rejected at init, no fault

Kernel refs

sys/dev/drm/amd/powerplay/hwmgr/vega20_processpptables.c:823, sys/dev/drm/amd/powerplay/hwmgr/vega20_processpptables.c:835, sys/dev/drm/amd/powerplay/hwmgr/vega20_processpptables.c:839, sys/dev/drm/amd/powerplay/hwmgr/vega20_processpptables.c:843, sys/dev/drm/amd/powerplay/hwmgr/vega20_processpptables.c:870, sys/dev/drm/amd/powerplay/hwmgr/vega20_processpptables.c:874, sys/dev/drm/amd/powerplay/hwmgr/vega20_processpptables.c:889, sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:906, sys/dev/drm/amd/powerplay/hwmgr/vega20_hwmgr.c:1598, sys/dev/drm/amd/powerplay/hwmgr/hwmgr.c:222, sys/dev/drm/amd/powerplay/hwmgr/hwmgr.c:229

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 AMD ASIC). fix.diff is git-apply-able, compiles clean in a full amdgpu.ko module build under the real kernel toolchain with -Werror (fix_build.log, RC=0), and the harness PATCHED control rejects the crafted table at init with no consumer fault.

fix.diff; fix_build.log (module rebuild RC=0, 0 errors); run.log PATCHED case (init refuses -EINVAL, no fault)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT #0 x86_64 (amdgpu.ko toolchain validation)

Confirmed kernel references

Detail

Exploit chain

crafted VBIOS/vGPU ROM with OverDrive8Table.ucODTableRevision != 1 (or memory pressure during pptable init) -> pptable_init succeeds with NULL od arrays -> vega20_od8_set_feature_capabilities NULL-deref at driver load -> kernel panic (DoS); no data control, fixed NULL offset

Evidence (decisive lines)

run.log (VARIANT A/B: '>>> SIGSEGV: fault address 0x0 (od_feature_capabilities + 0)' + 'PANIC-CLASS FAULT REPRODUCED'; CONTROL clean; PATCHED refuses init, no fault); run.2.log (repeat); fix_build.log (full amdgpu.ko rebuild with fix.diff, -Werror, RC=0); fix.diff (git apply --check OK)

PoC changes

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

Verified recommended fix

check the five copy-helper return codes and fail init (-EINVAL) when the OD revision is unsupported instead of reporting success with NULL arrays (fix.diff)

Verdict

vega20 pptable init reports SUCCESS with NULL overdrive arrays: the ucODTableRevision==1 gate (vega20_processpptables.c:823) silently skips all three OD copies for any other legal revision value, and the five copy-helper return codes are ignored (:835-846, :870-877) so a kzalloc failure leaves the pointers NULL too; init then returns append_vbios_pptable's 0 (:889-891). backend_init runs right after (hwmgr.c:229 after :222) and vega20_od8_initialize_default_settings (vega20_hwmgr.c:1598) calls vega20_od8_set_feature_capabilities whose first array access (vega20_hwmgr.c:906, od_feature_capabilities[0]) dereferences NULL -> kernel panic at amdgpu driver load. Reproduced on the audit guest with a faithful lifted-copy harness: VARIANT A (crafted ucODTableRevision=2 that passes every header check) -> init SUCCESS with od_feature_capabilities=0x0 -> consumer SIGSEGV at address 0x0; VARIANT B (rev=1 with injected kzalloc failure, the dropped -ENOMEM) -> identical fault; CONTROL (rev=1 healthy) clean; PATCHED (fix.diff: checked returns + refuse-to-succeed-with-NULL) rejects the table at init (-EINVAL) and no fault occurs. In-kernel path hardware-gated (no AMD GPU on the guest).