OOB read in ci_parse_power_table's VCE state fill: clk_idx (ucClockInfoIndex & 0x3f) indexes clockInfo[] without the ucNumEntries bound used everywhere else
Summary
The main parse loop bounds clock_array_index against ucNumEntries; the subsequent RADEON_MAX_VCE_LEVELS fill loop does not. clk_idx comes from the VBIOS VCE state table masked to 6 bits and ucEntrySize is a raw u8, giving up to 63*255 = 16065 bytes past clockInfo[] inside/beyond the heap BIOS image copy. Guest harness: 63*255 = 16065 bytes offset with ucNumEntries=1. Crafted VBIOS -> up-to-16KB OOB kernel heap read at ci_dpm_init; wild-read panic is the realistic ceiling (CI-specific; no amdgpu-twin filed).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3650 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | doc | 884 B | β raw | |
| VERDICT.md | doc | 3.1 KB | β raw | |
| fix.diff | doc | 534 B | view raw | |
| env.txt | doc | 799 B | view raw | |
| verdict.json | doc | 3.3 KB | view raw | |
| manifest.json | doc | 1.4 KB | view raw | |
| harness.c | doc | 5.7 KB | view raw | |
| build.sh | doc | 65 B | view raw | |
| run.sh | doc | 89 B | view raw | |
| build.log | log | 0 B | β download | |
| run.log | log | 556 B | view raw | |
| run.2.log | log | 556 B | view raw |
DF-3650 β OOB read in ci_parse_power_table's VCE state fill: clk_idx (ucClockInfoIndex & 0x3f) indexes clockInfo[] without the ucNumEntries bound used everywhere else
File: sys/dev/drm/radeon/ci_dpm.c (pass 2, GLM 5.3 re-audit)
Severity: Medium | CWE-125 Out-of-bounds Read | confidence: certain
Reproduce
This pack carries a userland logic harness (harness.c) built and run ON THE
AUDIT GUEST with the exact kernel types; decisive section: [E]
./build.sh ./run.sh
Expected decisive line: clk_idx=63 * entrySize=255 = 16065 bytes past clockInfo[0] (ucNumEntries=1)
The kernel code path itself requires a Sea Islands radeon GPU + crafted VBIOS; the guest is virtio-only (no AMD PCI device, no /dev/dri, no drm kld) β see env.txt.
Full narrative: VERDICT.md. Proposed fix: fix.diff (authored against the read-only sys/ tree; never applied there).
DF-3650 β verdict narrative
Finding
OOB read in ci_parse_power_table's VCE state fill: clk_idx (ucClockInfoIndex & 0x3f) indexes clockInfo[] without the ucNumEntries bound used everywhere else
Kernel references
sys/dev/drm/radeon/ci_dpm.c:5637sys/dev/drm/radeon/ci_dpm.c:5654sys/dev/drm/radeon/ci_dpm.c:5656sys/dev/drm/radeon/ci_dpm.c:5657sys/dev/drm/radeon/r600_dpm.c:1127
Was it reproduced?
No β not on the kernel path. The audit guest is virtio-only (0 AMD PCI devices, /dev/dri absent, no radeon/drm kld; env.txt), so sys/dev/drm/radeon/ci_dpm.c can never execute there. Per the pass-2 instruction for this hardware-gated file, the verdict is inconclusive (source-level certainty certain) rather than reproduced.
Logic-level proof (guest harness)
harness.c models the exact loop/index/division with the exact kernel types and was compiled and executed on the audit guest (cc -O2 -Wall, DragonFly 6.5-DEVELOPMENT). Decisive output, stable across 2 runs (run.log, run.2.log):
clk_idx=63 * entrySize=255 = 16065 bytes past clockInfo[0] (ucNumEntries=1)
Root cause and reasoning
Source-certain OOB read, CI-specific (the si twin has no VCE DPM states and the amdgpu audit's DF-3619..3625 do not cover it). After the per-state loop, ci_parse_power_table fills rdev->pm.dpm.vce_states[]: clock_array_index = vce_states[i].clk_idx (ci_dpm.c:5656) and clock_info = &clock_info_array->clockInfo[clock_array_index * clock_info_array->ucEntrySize] (5657-5658). The main loop above bounds the very same index against clock_info_array->ucNumEntries (5637-5638); the VCE loop does not. clk_idx originates from the VBIOS VCE state table masked to 6 bits (r600_dpm.c:1127, ucClockInfoIndex & 0x3f, so 0..63) and ucEntrySize is a raw u8 (up to 255), giving a read up to ~16KB past clockInfo[] inside (or beyond) the heap-allocated ATOM BIOS image copy - harness section [E]: 63*255 = 16065 bytes. Within the allocation the leaked bytes flow into vce_states[].sclk/mclk (power-management only, no userspace exposure found); past its end it is a wild read -> panic. Reachable whenever the VBIOS carries an extended header with a VCE table whose state records reference out-of-range clock info indices. Kernel path unreachable on this GPU-less guest; inconclusive overall.
Exploitability
Crafted VBIOS with an extended header + VCE state table whose ucClockInfoIndex values point past the (small) clock info array -> up-to-16KB OOB read from the kernel heap bios image during ci_dpm_init; values only feed power management, so realistic impact is a wild-read panic (DoS), info-pollution at best.
Recommended fix
Apply the same clock_array_index >= clock_info_array->ucNumEntries bound (as at ci_dpm.c:5637) to the VCE state fill loop. See fix.diff.
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_testableFix validation would require a CI-class GPU with a crafted VBIOS (baseline panic/corruption) and a single-fix kernel build; the audit guest is virtio-only, so the fix is source-reviewed only (fix.diff).
fix.diff in this pack; not applied to sys/ (read-only audit target).
Confirmed kernel references
Detail
Exploit chain
Crafted VBIOS with an extended header + VCE state table whose ucClockInfoIndex values point past the (small) clock info array -> up-to-16KB OOB read from the kernel heap bios image during ci_dpm_init; values only feed power management, so realistic impact is a wild-read panic (DoS), info-pollution at best.
Evidence (decisive lines)
VERDICT.md (full source trace); harness.c + run.log/run.2.log section [E] (clk_idx=63 * entrySize=255 = 16065 bytes past clockInfo[0] (ucNumEntries=1)); env.txt (guest has no AMD GPU: pciconf 0x1002 count=0, /dev/dri absent, no drm kld); fix.diff
PoC changes
n/a β no seeded PoC for pass-2; harness written fresh (models the exact kernel types; divisors made volatile so -O2 cannot fold the #DE)
Verified recommended fix
Apply the same clock_array_index >= clock_info_array->ucNumEntries bound (as at ci_dpm.c:5637) to the VCE state fill loop.
Verdict
Source-certain OOB read, CI-specific (the si twin has no VCE DPM states and the amdgpu audit's DF-3619..3625 do not cover it). After the per-state loop, ci_parse_power_table fills rdev->pm.dpm.vce_states[]: clock_array_index = vce_states[i].clk_idx (ci_dpm.c:5656) and clock_info = &clock_info_array->clockInfo[clock_array_index * clock_info_array->ucEntrySize] (5657-5658). The main loop above bounds the very same index against clock_info_array->ucNumEntries (5637-5638); the VCE loop does not. clk_idx originates from the VBIOS VCE state table masked to 6 bits (r600_dpm.c:1127, ucClockInfoIndex & 0x3f, so 0..63) and ucEntrySize is a raw u8 (up to 255), giving a read up to ~16KB past clockInfo[] inside (or beyond) the heap-allocated ATOM BIOS image copy - harness section [E]: 63*255 = 16065 bytes. Within the allocation the leaked bytes flow into vce_states[].sclk/mclk (power-management only, no userspace exposure found); past its end it is a wild read -> panic. Reachable whenever the VBIOS carries an extended header with a VCE table whose state records reference out-of-range clock info indices. Kernel path unreachable on this GPU-less guest; inconclusive overall.
No comments yet.