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/*vddcivoltage 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
*vddcon a config where the bytes are mapped, demonstrating attacker-influenced voltage selection.
Recommended fix
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.
Related findings
- 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)
PoC verification
Evidence pack
findings/poc/DF-1498 Β· 10 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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 β 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 infix_apply.log). - Compiled with
-Werroras part ofmake -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
-Werrorcflags β 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
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- p
- o
- w
- e
- r
- p
- l
- a
- y
- /
- h
- w
- m
- g
- r
- /
- p
- p
- a
- t
- o
- m
- c
- t
- r
- l
- .
- c
- :
- 1
- 4
- 9
- 5
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- p
- o
- w
- e
- r
- p
- l
- a
- y
- /
- h
- w
- m
- g
- r
- /
- p
- p
- a
- t
- o
- m
- c
- t
- r
- l
- .
- c
- :
- 1
- 4
- 9
- 8
- s
- y
- s
- /
- d
- e
- v
- /
- d
- r
- m
- /
- a
- m
- d
- /
- p
- o
- w
- e
- r
- p
- l
- a
- y
- /
- h
- w
- m
- g
- r
- /
- p
- p
- a
- t
- o
- m
- c
- t
- r
- l
- .
- c
- :
- 1
- 5
- 1
- 5
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.
No comments yet.