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

VBIOS usVoltageIndex (USHORT 0..65535) OOB heap write in sumo_construct_vid_mapping_table β€” up to 5 chosen-offset 16-bit-pair writes into/after the kv_power_info heap object

Summary

sumo_construct_vid_mapping_table stores vid_7bit/vid_2bit at entries[usVoltageIndex] where entries[] has 4 slots but usVoltageIndex is an unvalidated USHORT from the VBIOS. No bounds check exists; index>=4 writes at entries+4*idx..+4*idx+2, any 4-byte-aligned slot in entries+[4..262142]. vid_mapping_table sits ~160 bytes into the kzalloc'd ~1.3KB kv_power_info whose fields include SMC SRAM write offsets, graphics_level[8], and level counts; beyond ~1.3KB the writes hit adjacent slab objects. Executed unconditionally at every Kaveri/Kabini/Mullins boot. Values semi-chosen. VERIFIED: harness proves the write set (entries+1200..+1210 payload 0xBEEF/0xCAFE/0x1234 for indices 300-302) and SIGSEGV at +262140 on the verbatim store. VBIOS-image control (physical flash/supply-chain/passthrough) -> boot-time heap OOB write with chosen offset and semi-chosen u16 data.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3887 Β· 11 files
FileTypeDescriptionSize
vid_mapping_oob_write.c β€” 6.1 KB view raw
build.sh β€” 95 B view raw
run.sh β€” 116 B view raw
build.log β€” 107 B view raw
run.log β€” 654 B view raw
run.2.log β€” 88 B view raw
env.txt β€” 561 B view raw
fix.diff β€” 810 B view raw
VERDICT.md β€” 5.2 KB ↓ raw
README.md β€” 1.8 KB ↓ raw
verdict.json β€” 3.4 KB view raw

DF-3887 β€” VBIOS u16 voltage-index OOB heap write in sumo_construct_vid_mapping_table

File: sys/dev/drm/amd/amdgpu/kv_dpm.c:167-170 (function :159-191, reached from kv_parse_sys_info_table :2613) Class: CWE-787 out-of-bounds write (VBIOS-controlled index into a 4-slot array) Verdict: inconclusive at runtime on this guest (no AMD GPU β€” hardware-gated); source-certain; harness proves the exact write set + SIGSEGV on the verbatim access.

Build (on the DragonFly guest, as root)

mkdir -p /tmp/DF-3887 && cp vid_mapping_oob_write.c /tmp/DF-3887/h.c
cd /tmp/DF-3887 && cc -Wall -O0 -o t h.c

Run

./t          # accounting mode β€” exact byte offsets written outside entries[]
./t crash    # verbatim kernel write with usVoltageIndex=65535 -> SIGSEGV (exit 139)

Expected

  • accounting: OOB WRITE: entries+1200 = 0x012c, entries+1202 = 0xbeef, entries+1204 = 0x012d, entries+1206 = 0xcafe, entries+1208 = 0x012e, entries+1210 = 0x1234 β€” six 16-bit OOB writes for usVoltageIndex 300..302; the fully-VBIOS-controlled usVoltageID (0xBEEF/0xCAFE/0x1234) and the index itself are the payload. Max index 65535 β†’ write window entries + [4 .. 262142] bytes, versus a 16-byte array inside a ~1.3 KB kv_power_info heap object.
  • crash: Segmentation fault (core dumped), exit=139 (kernel equivalent: page fault in kernel mode β†’ panic).
  • The entries-4 = 0x0002 line in run.log is the in-struct num_entries field being legally written by the compaction loop β€” not corruption.

Kernel trigger

KV/KV-based APU (CHIP_KAVERI/KABINI/MULLINS) with a crafted VBIOS whose IntegratedSystemInfo V1_8 sAvail_SCLK[i].usVoltageIndex >= 4 β€” executed unconditionally at every kv_dpm_init (driver load / boot).

VERDICT.md
↓ download raw

DF-3887 β€” verdict narrative

Finding

sumo_construct_vid_mapping_table() (sys/dev/drm/amd/amdgpu/kv_dpm.c:159-191) fills pi->sys_info.vid_mapping_table.entries[] directly from the VBIOS:

for (i = 0; i < SUMO_MAX_HARDWARE_POWERLEVELS; i++) {          /* :165 */
        if (table[i].ulSupportedSCLK != 0) {                    /* :166 */
                vid_mapping_table->entries[table[i].usVoltageIndex].vid_7bit =   /* :167 */
                        table[i].usVoltageID;
                vid_mapping_table->entries[table[i].usVoltageIndex].vid_2bit =   /* :169 */
                        table[i].usVoltageIndex;
        }
}

entries[] is struct sumo_vid_mapping_entry entries[SUMO_MAX_NUMBER_VOLTAGES] (kv_dpm.h) β€” 4 slots Γ— 4 bytes = 16 bytes. usVoltageIndex is a USHORT (0..65535) from ATOM_AVAILABLE_SCLK_LIST (sys/dev/drm/amd/include/atombios.h:5705-5710), copied verbatim out of the card's VBIOS ATOM_INTEGRATED_SYSTEM_INFO_V1_8.sAvail_SCLK[5] (atombios.h:6172). There is no bounds check anywhere between the VBIOS image and the array store. A VBIOS (flash-modified, supply-chain, or VM firmware-passthrough) with usVoltageIndex >= 4 executes up to 5 chosen-offset 16-bit-pair writes:

  • write 1 (value): usVoltageID β€” fully attacker-chosen 16-bit
  • write 2 (value): usVoltageIndex itself
  • offsets: entries + 4*usVoltageIndex and +2, i.e. any 4-byte-aligned slot in entries + [4 .. 262142]

vid_mapping_table lives ~160 bytes into the heap kv_power_info (kzalloc at kv_dpm.c:2797, ~1.3 KB: graphics_level[8], SMC offsets dpm_table_start/sram_end, level counts, boot levels...). The write window therefore covers both intra-object corruption (e.g. pi->dpm_table_start, which later becomes the destination offset of amdgpu_kv_copy_bytes_to_smc writes, and graphics_dpm_level_count) and, beyond ~1.3 KB, adjacent kernel heap objects β€” a genuine arbitrary-offset heap OOB write with (two-u16) attacker-controlled data, executed at every Kaveri/Kabini/Mullins boot (kv_dpm_init β†’ kv_parse_sys_info_table :2613).

This is distinct from pass-1 DF-1268 (unbounded counts overflowing the fixed graphics_level/uvd_level/... arrays via kv_init_graphics_levels/kv_populate_*) and DF-1269 (unvalidated indices/offsets in kv_parse_power_table causing OOB reads): the victim here is sys_info.vid_mapping_table.entries[], the input is the IGP table's sAvail_SCLK (not the powerplay tables), and the primitive is a write.

Root cause (kernel references)

What was verified, and how

  1. Source-level certainty. Full-file pass-2 audit; struct geometry verified in kv_dpm.h and atombios.h (array=4Γ—u16-pairs, index=USHORT); call path kv_dpm_init:2848 β†’ kv_parse_sys_info_table:2613 runs unconditionally for crev==8 IGP tables (the only supported revision, :2574-2577).
  2. Harness on the guest (DragonFly 6.5-DEVELOPMENT x86_64, base cc 8.3): vid_mapping_oob_write.c replicates the verbatim loop with 1:1 layouts. Accounting mode shows the exact OOB write set (entries+1200..+1210 for indices 300-302, payload 0xBEEF/0xCAFE/0x1234); crash mode executes the verbatim store with usVoltageIndex=65535 against a single page mapping β†’ write at +262140 β†’ SIGSEGV exit=139 (kernel equivalent: page fault in kernel mode β†’ panic; in the kernel the same store into mapped adjacent heap silently corrupts it instead).
  3. Hardware absence. The guest is virtio-only (pciconf: no 1002:xxxx device; amdgpu.ko present but unloadable/useless). The in-kernel trigger needs a Kaveri/Kabini/Mullins APU with a crafted VBIOS (physical flash / supply chain / VM GPU firmware). Per pass-2 spawn instructions this is recorded as inconclusive, impact none on this guest (source + harness: certain).

Exploit chain (up to the hardware gate)

flash crafted VBIOS (physical/supply-chain) β†’ boot β†’ kv_dpm_init β†’ OOB write window [entries+4 .. entries+262142] inside/after the kv_power_info heap object β†’ corrupt dpm_table_start/sram_end (SMC SRAM write offsets used by every amdgpu_kv_copy_bytes_to_smc), graphics_dpm_level_count, or adjacent slab objects β†’ second-order controlled writes to SMU SRAM / kernel heap. Not exercisable on this guest.

Fix validation

fix.diff authored post-verification against the read-only sys/ tree (clamp usVoltageIndex < SUMO_MAX_NUMBER_VOLTAGES before both stores, mirroring the clock_array_index >= ucNumEntries check Linux already has at :2758). Not built: no AMD hardware on the guest β†’ fix_status: not_testable; the diff is minimal and mechanically checkable against :165-171.

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 to load the drm stack).

findings/poc/DF-3887/fix.diff
↓ fix.diffper-fix-DF-3887

Confirmed kernel references

Detail

Exploit chain

flash crafted VBIOS (physical/supply-chain) -> boot -> kv_dpm_init OOB write window [entries+4 .. entries+262142] -> corrupt pi->dpm_table_start/sram_end (destinations of later amdgpu_kv_copy_bytes_to_smc writes), graphics_dpm_level_count, or adjacent heap objects -> second-order controlled SMC-SRAM/heap writes. Not exercisable on the GPU-less guest.

Evidence (decisive lines)

run.log: 'OOB WRITE: entries+1200..+1210 = 0x012c/0xbeef/0x012d/0xcafe/0x012e/0x1234', '7 OOB 16-bit writes landed OUTSIDE the 16-byte entries array'; run.2.log: 'crash mode ... +65535*4 = 262140 bytes past it' then Segmentation fault exit=139; VERDICT.md; fix.diff

PoC changes

No seed (pass-2 self-authored). Added stddef.h for ptrdiff_t after first guest build failed; guest loop verified accounting+crash modes.

Verified recommended fix

Skip sAvail_SCLK entries whose usVoltageIndex >= SUMO_MAX_NUMBER_VOLTAGES in sumo_construct_vid_mapping_table (kv_dpm.c:166), mirroring the existing clock_array_index check at :2758.

Verdict

Source-certain heap OOB write; runtime-inconclusive on this guest (no AMD GPU). sumo_construct_vid_mapping_table (kv_dpm.c:167-170) stores two u16s at vid_mapping_table->entries[usVoltageIndex] where entries[] has 4 slots (kv_dpm.h SUMO_MAX_NUMBER_VOLTAGES) but usVoltageIndex is an unvalidated USHORT (0..65535) from the VBIOS sAvail_SCLK list (atombios.h:5705). A crafted VBIOS executes up to 5 chosen-offset 16-bit-pair writes in entries+[4..262142] - covering the ~1.3KB kv_power_info heap object (dpm_table_start/sram_end SMC write offsets, graphics_level[], level counts) and adjacent slab objects - at every KV boot via kv_dpm_init -> kv_parse_sys_info_table (:2613). Harness (1:1 layouts, verbatim loop) shows the exact OOB write set (entries+1200..+1210, payload 0xBEEF/0xCAFE/0x1234 for indices 300-302) and crash mode SIGSEGVs at +262140 (kernel equivalent: page fault -> panic, or silent adjacent-heap corruption on mapped pages). Distinct from pass-1 DF-1268 (count overflow of graphics_level/uvd_level arrays) and DF-1269 (kv_parse_power_table OOB reads): different victim array, different input table, write vs read.