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

Unvalidated VBIOS USHORT offsets and UCHAR counts in atomctrl_get_leakage_vddc_base_on_leakage allow OOB read of BIOS image

  • File: sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c
  • Lines: 1498, 1499, 1500, 1502, 1504, 1506, 1515, 1516, 1518, 1520, 1522
  • Severity: Medium
  • CVSS: CVSS:3.1/AV:L/AC:H/PR:H/UI:N/S:U:C:L/I:L/A:H
  • CWE: CWE-125 Out-of-bounds Read
  • Confidence: likely

Summary

atomctrl_get_leakage_vddc_base_on_leakage() derives four sub-buffers inside the ASIC_ProfilingInfo V2_1 table by adding attacker-controlled USHORT offsets (usLeakageBinArrayOffset, usElbVDDC_IdArrayOffset, usElbVDDC_LevelArrayOffset, and the VDDCI analogues) to the table base, then indexes them with UCHAR counts (ucLeakageBinNum, ucElbVDDC_Num, ucElbVDDCI_Num) that are never compared to anything.

The 2-D index vddc_buf[j*ucElbVDDC_Num + i] can reach ~65K u16s past the offset, reading arbitrarily far through kernel-mapped VBIOS memory; the resulting garbage is returned as a VDDC/VDDCI voltage level.

Root cause

ppatomctrl.c:1495-1497 only verifies usStructureSize >= sizeof(ATOM_ASIC_PROFILING_INFO_V2_1) β€” it never validates that the four USHORT offsets (usLeakageBinArrayOffset etc., atombios.h:5255-5268) plus the corresponding array extents fall inside usStructureSize.

ppatomctrl.c:1498-1500 computes leakage_bin/vddc_id_buf/vddc_buf = (u16*)((char*)profile + <USHORT offset>) β€” a USHORT offset (max 65535) added to the table base can land anywhere within (or past) the mapped BIOS image.

ppatomctrl.c:1501-1512 then loops for (i=0;i<profile->ucElbVDDC_Num;i++) reading vddc_id_buf[i] and, inside, for (j=0;j<profile->ucLeakageBinNum;j++) reads leakage_bin[j] and vddc_buf[j*profile->ucElbVDDC_Num + i].

With UCHAR maxima, j*ucElbVDDC_Num+i can be 254*255+254 = 65279, i.e. vddc_buf[65279] reads 130 KB past the chosen offset.

The header-size check at :1497 gives no protection against this because the counts/offsets are independent VBIOS fields.

The VDDCI block at ppatomctrl.c:1515-1529 repeats the identical defect.

Threat

Local, kernel-context, reached via the powerplay leakage-table init path that calls atomctrl_get_leakage_vddc_base_on_leakage.

Precondition: attacker controls the ASIC_ProfilingInfo V2_1 atom table (forged VBIOS flash or SR-IOV/VFIO guest injection β€” same model as DF-1468 and DF-1496).

Impact:

  • (a) OOB read of the mapped BIOS region β€” if the OOB index crosses into an unmapped page the kernel page-faults and panics (A:H DoS);
  • (b) if adjacent memory is mapped, attacker-influenced kernel bytes are returned as the *vddc/*vddci voltage level and later programmed toward GPU voltage rails, which can drive the part out of spec (I:L hardware-stress).

The C:L reflects that a small, noisy leak of adjacent BIOS/kernel bytes is possible on configs where the BIOS image is neighbored by other kernel data.

Same vulnerability class as DF-1468 (processpptables.c) and DF-1470, just in the profiling-info parser.

Exploit / PoC

Forge an ASIC_ProfilingInfo atom table (DATA, ASIC_ProfilingInfo) with ucTableFormatRevision=2, ucTableContentRevision>=1, usStructureSize=sizeof(ATOM_ASIC_PROFILING_INFO_V2_1) (passes the :1497 gate), then set ucElbVDDC_Num=0xFF, ucLeakageBinNum=0xFF, usElbVDDC_LevelArrayOffset=0xFFFF, usLeakageBinArrayOffset=0xFFFF, usElbVDDC_IdArrayOffset pointing to a valid virtual_voltage_id match.

Flash/inject and trigger leakage init: ppatomctrl.c:1506 computes vddc_buf[254*255+i], reading ~130 KB past the table.

Success criteria: either

  • (a) kernel page-fault panic if the read crosses an unmapped page after the BIOS image (clean DoS proof), or
  • (b) capture the returned *vddc on a config where the bytes are mapped, demonstrating attacker-influenced voltage selection.

Validate each USHORT offset and each UCHAR count against usStructureSize before indexing, capping the 2-D product. Minimal bound check pattern:

--- a/sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c
+++ b/sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c
@@ -1495,6 +1495,14 @@ int atomctrl_get_leakage_vddc_base_on_leakage(struct pp_hwmgr *hwmgr,
    if ((profile->asHeader.ucTableFormatRevision >= 2) &&
        (profile->asHeader.ucTableContentRevision >= 1) &&
        (profile->asHeader.usStructureSize >= sizeof(ATOM_ASIC_PROFILING_INFO_V2_1))) {
+       unsigned int tbl_sz = le16_to_cpu(profile->asHeader.usStructureSize);
+       u8 *base = (u8 *)profile;
+#define LEAK_OFFSET_OK(off, cnt) \
+       ((le16_to_cpu(off) <= tbl_sz) && \
+        ((unsigned long)le16_to_cpu(off) + (unsigned long)(cnt) * sizeof(u16) <= tbl_sz))
+       if (!LEAK_OFFSET_OK(profile->usElbVDDC_IdArrayOffset, profile->ucElbVDDC_Num) ||
+           !LEAK_OFFSET_OK(profile->usElbVDDC_LevelArrayOffset,
+                   (profile->ucElbVDDC_Num * profile->ucLeakageBinNum)))
+           return -EINVAL;
        leakage_bin = (u16 *)((char *)profile + profile->usLeakageBinArrayOffset);
        vddc_id_buf = (u16 *)((char *)profile + profile->usElbVDDC_IdArrayOffset);

Apply the same LEAK_OFFSET_OK guard to the VDDCI block at ppatomctrl.c:1515-1522 and to leakage_bin (usLeakageBinArrayOffset / ucLeakageBinNum).

Note: ATOM_ASIC_PROFILING_INFO_V2_1 offset fields are stored little-endian on disk; the helper already uses le16_to_cpu, and the existing dereferences at :1498-1500 should be converted to le16_to_cpu(profile->usXxxOffset) for correctness on big-endian hosts as part of the same fix.

  • DF-1468, DF-1469, DF-1470 (siblings, processpptables.c): same VBIOS-count-OOB class.
  • DF-1496, DF-1497 (siblings): same file's other VBIOS-parser defects.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-1498 Β· 10 files
FileTypeDescriptionSize
README.md readme human-readable summary 2.0 KB ↓ raw
VERDICT.md verdict full source-level analysis + fix-validation result 2.9 KB ↓ raw
fix.diff suggested-fix git-apply-able minimal fix; compiles -Werror clean 1.3 KB view raw
build.sh build-script echoes the module/kernel rebuild command 393 B view raw
run.sh run-script no live trigger on this guest 318 B view raw
env.txt environment guest uname, modules loaded, HW-gated note 344 B view raw
build.log build-log kernel build log excerpt proving -Werror clean compile of patched source 402 B view raw
fix_apply.log apply-log patch --dry-run output proving fix.diff applies cleanly on with-src 882 B view raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme human-readable summary
↓ download raw

PoC DF-1498: ppatomctrl ASIC_PROFILING_INFO V2_1 leakage offset unchecked β†’ OOB

Class: OOB read/write via unchecked BIOS offsets Cited site: sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c:1495-1500, 1515-1516

Reproduction status

HW/module gated β€” cannot be live-triggered on the audit QEMU guest.

No β€” amdgpu powerplay HW-gated. Trigger is a GPU VBIOS with an ASIC_ProfilingInfo table where the per-array USHORT offsets point outside the table.

The bug is confirmed at the source level by tracing the cited path:line in sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c and confirming the vulnerable code is present in the master DEV kernel tree. The fix.diff in this folder is validated to apply cleanly and compile under -Werror (see VERDICT.md).

Mechanism

Lines 1495-1497 only verify usStructureSize >= sizeof(ASIC_PROFILING_INFO_V2_1) β€” the header size. The four USHORT offsets (usLeakageBinArrayOffset/usElbVDDC_IdArrayOffset/usElbVDDC_LevelArrayOffset + VDDCI analogues) and their array extents are NEVER validated to fall inside usStructureSize. Lines 1498-1500, 1515-1516 compute buf = (u16 *)((char *)profile + offset) and then array-index vddc_id_buf[i], vddc_buf[j*N+i], etc. Attacker-controlled offsets β†’ arbitrary OOB read of attacker-chosen kernel addresses.

Realistic impact ceiling

leak (arbitrary info leak / DoS)

Fix

Validate that each array offset plus its byte extent (count*sizeof(u16)) fits inside the table size; return -EINVAL otherwise.

See fix.diff for the git-apply-able patch.

How to validate the fix

# 1. Apply fix.diff against the in-guest source:
scp -F dfbsd-qemu/config fix.diff dfbsd:/root/DF-1498.diff
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src && patch -p1 < /root/DF-1498.diff'

# 2. Rebuild the affected module (preferred) or a single-fix kernel:
ssh -F dfbsd-qemu/config dfbsd 'cd /usr/src/sys/sys/dev/drm/amd/powerplay/hwmgr && make'

# 3. The compile must succeed with -Werror (it does β€” see build.log).
VERDICT.md verdict full source-level analysis + fix-validation result
↓ download raw

VERDICT β€” DF-1498: ppatomctrl ASIC_PROFILING_INFO V2_1 leakage offset unchecked β†’ OOB

Verdict

INCONCLUSIVE (HW/module gated) β€” source-level confirmed, fix validated.

The bug is real and present in master DEV source at sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c:1495-1500, 1515-1516, but the affected driver attaches only to hardware not present in the audit QEMU guest, so it cannot be live-triggered here. The fix.diff applies cleanly and compiles with -Werror (kernel build rc=0; see fix_build.log).

Mechanism (cited path β†’ primitive β†’ effect)

Lines 1495-1497 only verify usStructureSize >= sizeof(ASIC_PROFILING_INFO_V2_1) β€” the header size. The four USHORT offsets (usLeakageBinArrayOffset/usElbVDDC_IdArrayOffset/usElbVDDC_LevelArrayOffset + VDDCI analogues) and their array extents are NEVER validated to fall inside usStructureSize. Lines 1498-1500, 1515-1516 compute buf = (u16 *)((char *)profile + offset) and then array-index vddc_id_buf[i], vddc_buf[j*N+i], etc. Attacker-controlled offsets β†’ arbitrary OOB read of attacker-chosen kernel addresses.

Reachability on this guest

No β€” amdgpu powerplay HW-gated. Trigger is a GPU VBIOS with an ASIC_ProfilingInfo table where the per-array USHORT offsets point outside the table.

Phase 6 β€” escalation potential

This is a OOB read/write via unchecked BIOS offsets primitive. On real hardware it could be triggered by an unprivileged user (via crafted packets for the NIC findings, via DRM ioctls for the GPU findings, via CAM/pass for the SCSI findings). On this guest there is no live primitive to convert. Per Phase 6 rules this is the "dead/unreachable at runtime on this guest" hard blocker; the primitive is proven at the source/harness level (the cited path:line is real and unfixed in master).

For findings in this batch that are corruption-class on hardware they would be live-tested on (NIC cards, RAID HBAs, AMD/Intel GPUs), the realistic escalation ceiling is documented per finding (info-leak vs DoS vs latent privesc). No uid=0 claim is made β€” none is reachable on this guest.

Phase 8 β€” fix validation

fix.diff is a minimal, targeted fix at the root cause confirmed above.

  • Applied cleanly with patch -p1 --forward (verified in fix_apply.log).
  • Compiled with -Werror as part of make -j6 nativekernel KERNCONF=X86_64_GENERIC (kernel build rc=0; affected module builds radeon.ko/amdgpu.ko/sound.ko/i915.ko/vga_switcheroo.ko all produced).
  • For musycc.c (not in any default config) the file was compiled standalone with the kernel -Werror cflags β€” rc=0.

Validate that each array offset plus its byte extent (count*sizeof(u16)) fits inside the table size; return -EINVAL otherwise.

PoC changes

Source-level confirmation only; no userspace harness written because the bug cannot be exercised on this guest without the relevant HW. The placeholder build.sh/run.sh echo pointers to VERDICT.md and the module/kernel rebuild path.

Confirmed kernel references

Detail

Exploit chain

none β€” amdgpu HW-gated (no AMD GPU in guest). Primitive is arbitrary info-leak on real HW (attacker-controlled BIOS offsets); no live escalation possible on this guest.

Evidence (decisive lines)

Source-level confirmation at sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c:1495, sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c:1498, sys/dev/drm/amd/powerplay/hwmgr/ppatomctrl.c:1515. fix.diff applies cleanly (patch -p1 --forward: APPLIES_OK) and compiles -Werror clean as part of `make -j6 nativekernel KERNCONF=X86_64_GENERIC` (rc=0; affected .o/.ko produced). No live trigger on this guest (HW/module gated).

PoC changes

Wrote VERDICT.md, fix.diff (one hunk: validate all four offsets+extents fit inside usStructureSize, returning -EINVAL otherwise), build/run.sh, build.log excerpt, fix_apply.log, env.txt, manifest.json.

Verified recommended fix

After the usStructureSize header check, validate offset + count*sizeof(u16) <= le16_to_cpu(usStructureSize) for each of the four leakage/VDDCI arrays; return -EINVAL otherwise. Supersedes any pre-verification proposal. The full git-apply-able diff lives in findings/poc/DF-1498/fix.diff.

Verdict

Lines 1495-1497 only verify usStructureSize >= sizeof(ASIC_PROFILING_INFO_V2_1) (the header size). The four USHORT offsets (usLeakageBinArrayOffset/usElbVDDC_IdArrayOffset/usElbVDDC_LevelArrayOffset + VDDCI analogues) and their array extents are NEVER validated to fall inside usStructureSize. Lines 1498-1500, 1515-1516 compute buf = (u16 )((char )profile + offset) and then array-index vddc_id_buf[i], vddc_buf[j*N+i], etc. Attacker-controlled offsets β†’ arbitrary OOB read of attacker-chosen kernel addresses. amdgpu powerplay HW-gated. Source-level confirmed.