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

Unbounded sprintf loops in smu7_print_clock_levels overflow the 4096-byte sysfs page buffer once the VBIOS DPM count exceeds ~233

Summary

The print loops run to the unvalidated VBIOS-driven count (up to 255, DF-1166 premise) emitting one line per level into the raw sysfs/kernfs page buffer; measured with the exact format strings: 255 PP_PCIE or u32-clock PP_SCLK lines = 4481 bytes > PAGE_SIZE(4096), and the per-line dpm_levels[i] reads for i >= 8 are themselves OOB reads of dpm_levels[8]. HARNESS-VERIFIED: 'PP_PCIE count=255 -> 4481 bytes OVERFLOWS PAGE_SIZE(4096)'. Once a crafted VBIOS sets count > ~233, an UNPRIVILEGED read of pp_dpm_pcie/pp_dpm_sclk/pp_od_clk_voltage overflows the kernfs heap page by ~385 bytes with attacker-influenced text.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3688 Β· 11 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
VERDICT.md β€” 3.0 KB ↓ raw
sprintf_budget.c β€” 2.3 KB view raw
build.sh β€” 108 B view raw
run.sh β€” 154 B view raw
build.log β€” 69 B view raw
run.log β€” 619 B view raw
env.txt β€” 1.1 KB view raw
fix.diff β€” 1.8 KB view raw
verdict.json β€” 2.9 KB view raw
manifest.json β€” 970 B view raw

DF-3688 β€” Unbounded sprintf loop in smu7_print_clock_levels overflows the 4096-byte sysfs page buffer once the VBIOS DPM count exceeds ~233

File: sys/dev/drm/amd/powerplay/hwmgr/smu7_hwmgr.c (pass 2, GLM 5.3 re-audit) Severity: Low | CVSS: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:H | CWE: CWE-787 Out-of-bounds Write; CWE-120 Buffer Copy without Checking Size of Input | Confidence: certain (source)

What this is

Source-certain output-budget overflow, precondition shared with DF-1166 (VBIOS dep count > 8 is unvalidated); runtime-inconclusive on this guest (no AMD GPU). smu7_print_clock_levels sprintf()s one line per DPM level into the caller's sysfs page buffer (amdgpu_pm.c amdgpu_get_pp_dpm_sclk/mclk/pcie pass the raw 4096-byte kernfs buf; pp_od_clk_voltage likewise). The loops run to sclk_table->count / mclk_table->count / pcie_table->count / odn_*->num_of_pl - all ultimately the unvalidated VBIOS u8 count (up to 255). Measured on the guest with the exact format strings: 255 PP_PCIE lines = 4481 bytes and 255 PP_SCLK lines with u32-scale clocks = 4481 bytes, both overflowing PAGE_SIZE; the per-line reads dpm_levels[i] for i >= 8 are themselves OOB reads of the dpm_levels[8] arrays. Impact: heap overflow of the kernfs page on an UNPRIVILEGED sysfs read once the count is corrupted by a crafted VBIOS; ~385 bytes past the page.

Reproduce (harness on the audit QEMU guest)

scp -F dfbsd-qemu/config sprintf_budget.c dfbsd-maxx:/tmp/DF-3688/
ssh -F dfbsd-qemu/config dfbsd-maxx 'cd /tmp/DF-3688 && cc -Wall -O0 -o t sprintf_budget.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-3688 β€” verdict narrative

Finding

Source-certain output-budget overflow, precondition shared with DF-1166 (VBIOS dep count > 8 is unvalidated); runtime-inconclusive on this guest (no AMD GPU). smu7_print_clock_levels sprintf()s one line per DPM level into the caller's sysfs page buffer (amdgpu_pm.c amdgpu_get_pp_dpm_sclk/mclk/pcie pass the raw 4096-byte kernfs buf; pp_od_clk_voltage likewise). The loops run to sclk_table->count / mclk_table->count / pcie_table->count / odn_*->num_of_pl - all ultimately the unvalidated VBIOS u8 count (up to 255). Measured on the guest with the exact format strings: 255 PP_PCIE lines = 4481 bytes and 255 PP_SCLK lines with u32-scale clocks = 4481 bytes, both overflowing PAGE_SIZE; the per-line reads dpm_levels[i] for i >= 8 are themselves OOB reads of the dpm_levels[8] arrays. Impact: heap overflow of the kernfs page on an UNPRIVILEGED sysfs read once the count is corrupted by a crafted VBIOS; ~385 bytes past the page.

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): sprintf_budget.c reproduces the exact kernel code shape and/or arithmetic with 1:1 struct layouts. Output in run.log. 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-3688/fix.diff
↓ fix.diffper-fix-DF-3688

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log: 'PP_SCLK count=255, u32 clocks -> 4481 bytes OVERFLOWS PAGE_SIZE(4096)', 'PP_PCIE count=255 -> 4481 bytes OVERFLOWS PAGE_SIZE(4096)'; VERDICT.md; fix.diff

PoC changes

No seed; wrote a budget harness replaying the exact format strings with measured line lengths (compile at -O0; -O2 sees the UB in the index math and misoptimizes unrelated accounting).

Verified recommended fix

Cap every print loop at MAX_REGULAR_DPM_NUMBER (the dpm_levels array bound) and at the odn entries bound; the root count validation is DF-1166's fix.

Verdict

Source-certain output-budget overflow, precondition shared with DF-1166 (VBIOS dep count > 8 is unvalidated); runtime-inconclusive on this guest (no AMD GPU). smu7_print_clock_levels sprintf()s one line per DPM level into the caller's sysfs page buffer (amdgpu_pm.c amdgpu_get_pp_dpm_sclk/mclk/pcie pass the raw 4096-byte kernfs buf; pp_od_clk_voltage likewise). The loops run to sclk_table->count / mclk_table->count / pcie_table->count / odn_*->num_of_pl - all ultimately the unvalidated VBIOS u8 count (up to 255). Measured on the guest with the exact format strings: 255 PP_PCIE lines = 4481 bytes and 255 PP_SCLK lines with u32-scale clocks = 4481 bytes, both overflowing PAGE_SIZE; the per-line reads dpm_levels[i] for i >= 8 are themselves OOB reads of the dpm_levels[8] arrays. Impact: heap overflow of the kernfs page on an UNPRIVILEGED sysfs read once the count is corrupted by a crafted VBIOS; ~385 bytes past the page.