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

Zero-count DPM tables (ignored setup failures) drive unsigned index 0xFFFFFFFF into dpm_levels[]: wild read/write ~48 GB out plus OOB reads and divide-by-zero across OD paths

Summary

smu7_setup_default_dpm_tables and backend_init ignore -EINVAL from the v0/v1 table builders, so a VBIOS omitting the mandatory V0 dependency tables (parse-legal) leaves sclk/mclk counts at 0. Every later use indexes dpm_levels[count-1]/[i-1] with unsigned arithmetic: smu7_find_dpm_states_clocks_in_dpm_table computes dpm_levels[(uint32_t)0-1] = +51,539,607,540 bytes - a wild kernel READ and conditional WILD WRITE on every power-state change; siblings at the OD/profiling paths (incl. DIV_ROUND_UP div0 on zero golden values) and a NULL deref. HARNESS-VERIFIED: 'effective byte offset = 51539607540'; crash variant executes the verbatim kernel access and dies SIGSEGV. Crafted VBIOS on a V0 SMU7 board: uncontrolled 4-byte kernel write ~48GB past the backend heap object (near-certain page fault; deterministic offset makes mapped-memory landings plausible).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3686 Β· 12 files
FileTypeDescriptionSize
README.md β€” 2.7 KB ↓ raw
VERDICT.md β€” 3.8 KB ↓ raw
zero_count_underflow.c β€” 3.9 KB view raw
build.sh β€” 114 B view raw
run.sh β€” 154 B view raw
build.log β€” 58 B view raw
run.log β€” 143 B view raw
run.2.log β€” 143 B view raw
env.txt β€” 1.1 KB view raw
fix.diff β€” 3.0 KB view raw
verdict.json β€” 3.9 KB view raw
manifest.json β€” 1.2 KB view raw

DF-3686 β€” Zero-count DPM tables (ignored setup failures) drive unsigned index 0xFFFFFFFF into dpm_levels[] - wild read/write ~48GB out and OOB reads/div0 across OD paths

File: sys/dev/drm/amd/powerplay/hwmgr/smu7_hwmgr.c (pass 2, GLM 5.3 re-audit) Severity: Medium | CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:H | CWE: CWE-191 Integer Underflow; CWE-787 Out-of-bounds Write; CWE-369 Divide By Zero | Confidence: certain (source)

What this is

Source-certain index-underflow family; runtime-inconclusive on this guest (no AMD GPU). Root cause 1: smu7_setup_default_dpm_tables (:950-953) ignores the -EINVAL returns of smu7_setup_dpm_tables_v0/v1, and smu7_hwmgr_backend_init (:2563-2566) likewise ignores smu7_set_private_data_based_on_pptable_v0's; a VBIOS whose V0 dependency offsets are 0 (parse-legal, processpptables.c:1304-1310) leaves sclk/mclk_table.count == 0. Root cause 2: use sites index dpm_levels[count-1] / dpm_levels[i-1] with unsigned arithmetic. Sinks: smu7_find_dpm_states_clocks_in_dpm_table (:3594/:3614-3616) - loop exits with i=0 so dpm_levels[(uint32_t)0-1] = dpm_levels[0xFFFFFFFF] = +51,539,607,540 bytes (~48 GB) - a WILD kernel READ and, when the garbage compares below the requested SCLK, a WILD WRITE of that clock value, on every power-state change; smu7_get_profiling_clk :2740 (sclk dpm_levels[count-1] read, mclk value 0 div0 is DF-1170); smu7_get_maximum_link_speed :3642-3645 (pcie dpm_levels[count-1]); smu7_get_sclk_od :4534-4539 and smu7_get_mclk_od :4576-4581 (index -1 reads plus DIV_ROUND_UP divide-by-zero when the golden value is 0); smu7_get_max_high_clocks :4745-4750; OD_RANGE print :4487/:4490; and a NULL deref of dyn_state.vddc_dependency_on_sclk at :4630/:2755 on the V0 branch. Harness reproduces the exact unsigned index arithmetic and, in crash mode, executes the verbatim kernel access: SIGSEGV (kernel equivalent: page fault in kernel mode -> panic).

Reproduce (harness on the audit QEMU guest)

scp -F dfbsd-qemu/config zero_count_underflow.c dfbsd-maxx:/tmp/DF-3686/
ssh -F dfbsd-qemu/config dfbsd-maxx 'cd /tmp/DF-3686 && cc -Wall -O0 -o t zero_count_underflow.c && ./t'

Expected: the run prints the confirmed-primitive line (RESULT: ... CONFIRMED ...). See run.log (and run.2.log where present) for the decisive captured output, build.log for the guest toolchain transcript, env.txt for the guest environment (virtio-only, no AMD GPU β€” pciconf -l shows only 0x1237/0x1111 QEMU devices; drm.ko/amdgpu.ko exist in /boot/kernel but are not loaded and have no hardware to bind), which is why the in-kernel trigger itself is inconclusive on this guest.

Fix

fix.diff (authored against the read-only sys/ tree; never applied there).

VERDICT.md
↓ download raw

DF-3686 β€” verdict narrative

Finding

Source-certain index-underflow family; runtime-inconclusive on this guest (no AMD GPU). Root cause 1: smu7_setup_default_dpm_tables (:950-953) ignores the -EINVAL returns of smu7_setup_dpm_tables_v0/v1, and smu7_hwmgr_backend_init (:2563-2566) likewise ignores smu7_set_private_data_based_on_pptable_v0's; a VBIOS whose V0 dependency offsets are 0 (parse-legal, processpptables.c:1304-1310) leaves sclk/mclk_table.count == 0. Root cause 2: use sites index dpm_levels[count-1] / dpm_levels[i-1] with unsigned arithmetic. Sinks: smu7_find_dpm_states_clocks_in_dpm_table (:3594/:3614-3616) - loop exits with i=0 so dpm_levels[(uint32_t)0-1] = dpm_levels[0xFFFFFFFF] = +51,539,607,540 bytes (~48 GB) - a WILD kernel READ and, when the garbage compares below the requested SCLK, a WILD WRITE of that clock value, on every power-state change; smu7_get_profiling_clk :2740 (sclk dpm_levels[count-1] read, mclk value 0 div0 is DF-1170); smu7_get_maximum_link_speed :3642-3645 (pcie dpm_levels[count-1]); smu7_get_sclk_od :4534-4539 and smu7_get_mclk_od :4576-4581 (index -1 reads plus DIV_ROUND_UP divide-by-zero when the golden value is 0); smu7_get_max_high_clocks :4745-4750; OD_RANGE print :4487/:4490; and a NULL deref of dyn_state.vddc_dependency_on_sclk at :4630/:2755 on the V0 branch. Harness reproduces the exact unsigned index arithmetic and, in crash mode, executes the verbatim kernel access: SIGSEGV (kernel equivalent: page fault in kernel mode -> panic).

Root cause (kernel references)

What was verified, and how

  1. Source-level certainty. Full-file pass-2 audit of smu7_hwmgr.c (5147 lines) with the parse-side preconditions traced into processpptables.c / process_pptables_v1_0.c (VBIOS u8 counts unvalidated; offset==0 subtables parse-legal NULL) and the struct geometry verified in ppatomctrl.h / smu7_hwmgr.h / hardwaremanager.h / dm_pp_interface.h.
  2. Harness on the guest (DragonFly 6.5-DEVELOPMENT x86_64, cc base toolchain): zero_count_underflow.c reproduces the exact kernel code shape and/or arithmetic with 1:1 struct layouts. Output in run.log; run.2.log for the live-crash phase. Compiled with -O0 β€” at -O2 the compiler detects the array-index UB and misoptimizes the accounting loops (itself a hint of what the kernel code does).
  3. Hardware absence. The guest is virtio-only: no AMD GPU on PCI, amdgpu not loaded. The in-kernel trigger requires an SMU7 ASIC (CI/Topaz/Tonga/Fiji/Polaris/ Vegam) with a crafted VBIOS, VFIO passthrough of a malicious card, or a root sysfs pp_table write. Per pass-2 spawn instructions this is recorded as inconclusive (matching DF-3585/DF-3619/DF-3644 precedent for the same class), impact none on this guest.

Fix validation

fix.diff authored post-verification against the read-only sys/ tree. Not built: a kernel rebuild with drm cannot be exercised on a GPU-less guest, so fix validation is not_testable; the diffs are minimal, local, and mechanically checkable against the cited lines.

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 no→ patch + rebuild →patched clean

fix.diff authored against the read-only sys/ tree after source+harness verification; not built (no AMD hardware on guest).

findings/poc/DF-3686/fix.diff
↓ fix.diffper-fix-DF-3686

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: 'loop exits with i = 0; i - 1 = 0xffffffff', 'effective byte offset ... = 51539607540 bytes'; run.2.log: crash mode executing the verbatim access -> SIGSEGV exit=139; VERDICT.md; fix.diff

PoC changes

No seed; wrote two-mode harness (accounting + verbatim-access crash mode) after discovering on-guest that the kernel's unsigned i-1 indexes +48GB, not -12 bytes as a signed reading would suggest.

Verified recommended fix

Propagate smu7_setup_dpm_tables_v0/v1 failures out of smu7_setup_default_dpm_tables, guard every dpm_levels[count-1]/[i-1] use with count>0 (find_dpm_states, profiling clk, max link speed, sclk/mclk_od, max_high_clocks, OD_RANGE print), and NULL-check the V0 dependency tables in smu7_get_sclks/smu7_get_profiling_clk.

Verdict

Source-certain index-underflow family; runtime-inconclusive on this guest (no AMD GPU). Root cause 1: smu7_setup_default_dpm_tables (:950-953) ignores the -EINVAL returns of smu7_setup_dpm_tables_v0/v1, and smu7_hwmgr_backend_init (:2563-2566) likewise ignores smu7_set_private_data_based_on_pptable_v0's; a VBIOS whose V0 dependency offsets are 0 (parse-legal, processpptables.c:1304-1310) leaves sclk/mclk_table.count == 0. Root cause 2: use sites index dpm_levels[count-1] / dpm_levels[i-1] with unsigned arithmetic. Sinks: smu7_find_dpm_states_clocks_in_dpm_table (:3594/:3614-3616) - loop exits with i=0 so dpm_levels[(uint32_t)0-1] = dpm_levels[0xFFFFFFFF] = +51,539,607,540 bytes (~48 GB) - a WILD kernel READ and, when the garbage compares below the requested SCLK, a WILD WRITE of that clock value, on every power-state change; smu7_get_profiling_clk :2740 (sclk dpm_levels[count-1] read, mclk value 0 div0 is DF-1170); smu7_get_maximum_link_speed :3642-3645 (pcie dpm_levels[count-1]); smu7_get_sclk_od :4534-4539 and smu7_get_mclk_od :4576-4581 (index -1 reads plus DIV_ROUND_UP divide-by-zero when the golden value is 0); smu7_get_max_high_clocks :4745-4750; OD_RANGE print :4487/:4490; and a NULL deref of dyn_state.vddc_dependency_on_sclk at :4630/:2755 on the V0 branch. Harness reproduces the exact unsigned index arithmetic and, in crash mode, executes the verbatim kernel access: SIGSEGV (kernel equivalent: page fault in kernel mode -> panic).