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

Unchecked nonClockInfoIndex (u8) and clockInfoIndex (u8 x ucEntrySize) in sumo_parse_power_table index past the NonClockInfoArray/ClockInfoArray in the kmalloc'd BIOS image: heap OOB read up to ~6.1 KB / ~65 KB β€” OOB bytes ingested into rps->class/class2/caps/vclk driving boot_ps/UVD/thermal selection β€” sumo twin of DF-1401/1306/1140

Summary

The parse takes nonClockInfoIndex (u8 0..255) against 24-byte nonClockInfo entries - up to ~6.1KB past; clock_array_index x ucEntrySize multiplies TWO untrusted u8s - up to ~65KB past; the state walk advances by 2+ucNumDPMLevels per state with no end-of-image bound. The arrays live in the kmalloc'd ROM copy: far indices page-fault, near indices silently copy adjacent kernel-heap bytes into rps->class/class2/caps/vclk, which drive which ps becomes boot_ps/uvd_ps and UVD clock programming (kernleak class). Harness-proven (fault at exactly the predicted bios+21874; fixed rejects). Bounds-check fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4190 Β· 10 files
FileTypeDescriptionSize
harness.c trigger-source verbatim replica; mode 4190 = nonClockInfoIndex=250 -> OOB read past bios image -> SIGSEGV at predicted address 34.8 KB view raw
build.sh build-script cc -O2 -Wall; harness_fixed with -DFIX_ALL 65 B view raw
run.sh run-script all modes, baseline + fixed 120 B view raw
build.log build-log final successful builds (guest cc 8.3) 1018 B view raw
run.log run-log decisive run with bios-relative OOB address computation 3.5 KB view raw
fix.diff suggested-fix bounds-check nonClockInfoIndex and clockInfoIndex against ucNumEntries with clean teardown 1.3 KB view raw
radeon_fix_apply.log apply-log patch(1) hunks on guest /usr/src (hunks 4-5 = this fix) 808 B view raw
radeon_module_build.log fix-build-log radeon.ko build with all four fixes, -Werror, MAKE_RC=0 139.5 KB view raw
env.txt environment guest uname, cc, no radeon loaded 568 B view raw
VERDICT.md verdict full narrative incl. clockInfoIndex*ucEntrySize reach and state-walk overrun 4.1 KB ↓ raw
VERDICT.md verdict full narrative incl. clockInfoIndex*ucEntrySize reach and state-walk overrun
↓ download raw

DF-4190 β€” VERDICT

Status: REPRODUCED (logic-level harness; kernel-level repro hardware-gated) Impact: kernel heap OOB read (crash when far out; silent kernel-heap disclosure into power-state fields when the neighbouring pages are mapped) Confidence: certain

Root cause

sumo_parse_power_table() (sys/dev/drm/radeon/sumo_dpm.c:1455-1528) trusts three u8 indices from the VBIOS PowerPlayInfo image without any bounds check:

non_clock_array_index = power_state->v2.nonClockInfoIndex;          /* :1495 u8 0..255 */
non_clock_info = (struct _ATOM_PPLIB_NONCLOCK_INFO *)
        &non_clock_info_array->nonClockInfo[non_clock_array_index]; /* :1496-1497 OOB */
...
        for (j = 0; j < power_state->v2.ucNumDPMLevels; j++) {
                clock_array_index = idx[j];                          /* :1509 u8 */
                ...
                clock_info = (union pplib_clock_info *)
                        ((u8 *)&clock_info_array->clockInfo[0] +
                         (clock_array_index * clock_info_array->ucEntrySize)); /* :1513-1515 OOB */
  • nonClockInfo[idx] strides 24 bytes (sizeof(ATOM_PPLIB_NONCLOCK_INFO), pptable.h:298-309): idx 255 reads up to ~6.1 KB past the array.
  • clockInfo[idx] strides ucEntrySize β€” itself a u8 from the image (up to 255), so a single index reaches up to ~65 KB past the clock array.
  • the state walk itself (power_state_offset += 2 + ucNumDPMLevels, :1524) advances by up to 257 bytes per state with no end-of-image bound, and idx[j] (:1509) reads past the state entry whenever ucNumDPMLevels exceeds the real array.

The arrays live inside atom_context->bios, a kmalloc'd copy of the GPU ROM image; the reads run past the end of that heap allocation. Values fetched OOB flow into rps->class/class2/caps/vclk/dclk (:1412-1418) β€” which drive control flow (BOOT/UVD/THERMAL classification β†’ which ps becomes boot_ps, thermal-state patching) and hardware programming (radeon_set_uvd_clocks). With a far index the read hits an unmapped page and panics; with a near index it silently copies adjacent kernel-heap bytes into state fields (kernleak class, same scoping as the DF-1401/1306/1140 twins).

Trigger / threat model

Crafted VBIOS ROM on a Sumo/Ontario/Palm APU (flashed card, replaced OPROM, VM GPU ROM passthrough). Parse happens at driver load; no privilege needed beyond the attacker controlling the ROM.

Harness proof (mode 4190)

16 KB crafted BIOS image, NonClockInfoArray placed near its end, state with nonClockInfoIndex = 250:

bios image: 16384 bytes; nonClockInfo[250] lands at bios+21874 (5490 bytes PAST the end)
*** SIGNAL 11 at address 0x8004c55b2 ***   == the predicted OOB address

The fault address equals the computed &nonClockInfo[250] exactly β€” the verbatim kernel index math dereferenced past the image buffer. (In the kernel the same read at the same relative offset faults identically or, when the adjacent heap is mapped, silently ingests neighbour bytes.)

Fix validation

  1. harness_fixed 4190 (-DFIX_DF4190: reject nonClockInfoIndex >= ucNumEntries): "invalid nonClockInfoIndex 250", parse returns -22, exit 0, no OOB.
  2. fix.diff (both nonClockInfoIndex and clockInfoIndex bounds checks, with clean teardown) applied with the other three sumo fixes on guest /usr/src (radeon_fix_apply.log hunks 4 & 5): radeon.ko builds under -Werror, MAKE_RC=0.

Kernel references

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

fixed
baseline reproduced→ patch + rebuild →patched clean

harness_fixed 4190 rejects the crafted index (DRM_ERROR, -EINVAL, exit 0); combined fix builds radeon.ko under -Werror (MAKE_RC=0)

run.log fixed section; radeon_fix_apply.log hunks 4-5; radeon_module_build.log MAKE_RC=0
↓ fix.diffper-fix-DF-4190

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log '===== mode 4190 =====': 'nonClockInfo[250] lands at bios+21874 (5490 bytes PAST the end of the image)' then 'SIGNAL 11 at address 0x8004c55b2' (the same predicted address); fixed section: 'invalid nonClockInfoIndex 250', exit=0

PoC changes

wrote harness from scratch (no seed): crafted bios image + verbatim parse replica

Verified recommended fix

bounds-check nonClockInfoIndex and clockInfoIndex against the respective ucNumEntries (and ucEntrySize sanity) with clean teardown of already-parsed states, as in fix.diff

Verdict

Reproduced at logic level with the verbatim replica (harness mode 4190): nonClockInfoIndex (u8 from the VBIOS, never bounds-checked) indexes nonClockInfo[250] = 24250 bytes into a NonClockInfoArray placed near the end of the 16 KB bios image; the read lands 5490 bytes past the kmalloc'd buffer and SIGSEGVs at exactly the predicted address (kernel: page-fault panic for far indices; silent ingestion of adjacent kernel-heap bytes into rps->class/class2/caps/vclk for near indices β€” kernleak class). clockInfoIndexucEntrySize (up to ~65 KB) and the unbounded power_state_offset walk are the same unchecked family in the same function. Sumo twin of DF-1401 (trinity) / DF-1306 (kv) / DF-1140 (si). Kernel-level repro hardware-gated (no ATI APU on guest). Fix proven: harness_fixed rejects the index; radeon.ko builds with the fix under -Werror.