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

vid_mapping num_entries==0 count-1 underflow in kv_convert_vid2_to_vid7 β€” entries[0xFFFFFFFF] wild read (~+16 GiB) at boot via kv_construct_max_power_limits_table

Summary

kv_convert_vid2_to_vid7 falls back to the firmware mapping table when the SCLK dependency subtable is absent (parse-legal) and returns entries[num_entries-1].vid_7bit when nothing matches. num_entries is legitimately 0 when every VBIOS sAvail_SCLK usVoltageID==0 while ulSupportedSCLK values are nonzero/increasing. The underflowed index reads entries + ~17.2 GB -> boot-time panic. Double precondition (all-zero usVoltageID + absent dependency subtable) keeps it Low. VERIFIED: harness prints the index/offset; crash mode SIGSEGV. Guard-the-fallback fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3890 Β· 11 files
FileTypeDescriptionSize
vid_mapping_zero_entries.c β€” 3.6 KB view raw
build.sh β€” 95 B view raw
run.sh β€” 116 B view raw
build.log β€” 107 B view raw
run.log β€” 254 B view raw
run.2.log β€” 76 B view raw
env.txt β€” 561 B view raw
fix.diff β€” 805 B view raw
VERDICT.md β€” 3.7 KB ↓ raw
README.md β€” 1.8 KB ↓ raw
verdict.json β€” 2.8 KB view raw

DF-3890 β€” vid_mapping_table->entries[num_entries - 1] with num_entries == 0 β†’ wild read (~+16 GB) in kv_convert_vid2_to_vid7

File: sys/dev/drm/amd/amdgpu/kv_dpm.c:90 (primary sink; identical pattern at :114 in kv_convert_vid7_to_vid2) Class: CWE-191 unsigned count-1 underflow β†’ wild OOB read (boot-time panic) Verdict: inconclusive at runtime on this guest (no AMD GPU); source-certain; harness proves index/offset and SIGSEGV on the verbatim read.

Build (on the DragonFly guest, as root)

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

Run

./t          # accounting β€” index and byte offset
./t crash    # verbatim read -> SIGSEGV (exit 139)

Expected

  • accounting: num_entries = 0; entries[4294967295]; byte offset 4294967295 * 4 = 17179869180 (~17.2 GB) past the 16-byte array.
  • crash: Segmentation fault, exit=139 (kernel equivalent: page fault in kernel mode β†’ panic).
  1. sAvail_SCLK[] has strictly-increasing nonzero ulSupportedSCLK (so num_max_dpm_entries > 0 and the guard at kv_dpm.c:2040 passes) but usVoltageID == 0 on every entry β†’ sumo_construct_vid_mapping_table leaves every vid_7bit zero β†’ compaction breaks at i == 0 β†’ num_entries = 0 (kv_dpm.c:185-190).
  2. PowerPlayInfo has no VddcDependencyOnSCLK subtable (count == 0) β†’ the else branch at kv_dpm.c:86-91 is taken.

Boot path: kv_dpm_init β†’ kv_parse_sys_info_table (:2617) β†’ kv_construct_max_power_limits_table (:2045) β†’ kv_convert_2bit_index_to_voltage (:690) β†’ kv_convert_vid2_to_vid7 (:90). Also reached later from kv_init_graphics_levels (:2453) in the same count == 0 configuration.

VERDICT.md
↓ download raw

DF-3890 β€” verdict narrative

Finding

kv_convert_vid2_to_vid7 (kv_dpm.c:72-92) falls back to the firmware-built mapping table when vddc_dependency_on_sclk.count == 0 (no SCLK dep subtable in the PowerPlayInfo header β€” amdgpu_parse_clk_voltage_dep_table is only invoked when usVddcDependencyOnSCLKOffset != 0, amdgpu_dpm.c:384-395):

} else {
        for (i = 0; i < vid_mapping_table->num_entries; i++) {
                if (vid_mapping_table->entries[i].vid_2bit == vid_2bit)
                        return vid_mapping_table->entries[i].vid_7bit;
        }
        return vid_mapping_table->entries[vid_mapping_table->num_entries - 1].vid_7bit;  /* :90 */
}

num_entries is produced by sumo_construct_vid_mapping_table (:159-191) as the index of the first zero vid_7bit run after compaction. If every usVoltageID in the VBIOS sAvail_SCLK[] is 0 (while ulSupportedSCLK values are nonzero and strictly increasing β€” needed so num_max_dpm_entries > 0), all vid_7bit stay 0, the compaction breaks at i == 0, and num_entries == 0. Then :90 evaluates entries[(u32)0 - 1] = entries[0xFFFFFFFF] = entries + 17,179,869,180 bytes (~16 GiB) β€” a wild read, page fault β†’ panic.

Reach (boot, before any power-state machinery): kv_dpm_init (:2848) β†’ kv_parse_sys_info_table (:2617) β†’ kv_construct_max_power_limits_table (:2040-2046: num_max_dpm_entries > 0 is checked, num_entries is not) β†’ kv_convert_2bit_index_to_voltage (:690) β†’ sink. Also later from kv_init_graphics_levels else-branch (:2453, kv_set_vid).

The identical entries[num_entries - 1] pattern exists at :114 (kv_convert_vid7_to_vid2); its callers currently take the count != 0 branch (kv_init_graphics_levels :2433), so :90 is the live instance.

Distinct from DF-3887 (same source table, but that is the write with an out-of-range usVoltageIndex; this is the read underflow when the table ends up empty) and from DF-1268/1269 (count overflow / parse indices).

Root cause (kernel references)

What was verified, and how

  1. Source-level certainty. Full-file pass-2 audit; compaction logic walked for the all-zero-usVoltageID case; the count == 0 else-branch selection verified against amdgpu_parse_extended_power_table.
  2. Harness on the guest (DragonFly 6.5-DEVELOPMENT x86_64, base cc 8.3): vid_mapping_zero_entries.c replicates the function verbatim; accounting mode prints entries[4294967295] / +17179869180 bytes (~17.2 GB); crash mode executes the verbatim read against a single-page mapping β†’ SIGSEGV exit=139 (kernel equivalent: page fault in kernel mode β†’ panic).
  3. Hardware absence. Guest is virtio-only; trigger needs a KV APU + crafted VBIOS. Recorded inconclusive, impact none on this guest; source + harness certain.

Fix validation

fix.diff guards num_entries != 0 at both :90 and :114 (returning 0 otherwise). Not built on the guest (no AMD hardware) β†’ fix_status: not_testable.

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).

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

Confirmed kernel references

Detail

Exploit chain

crafted VBIOS (nonzero increasing sAvail_SCLK SCLKs, all usVoltageID=0, no SCLK dep subtable) -> boot -> kv_parse_sys_info_table -> kv_convert_2bit_index_to_voltage -> kv_convert_vid2_to_vid7 wild read +16 GiB -> kernel page fault -> panic (boot-time DoS).

Evidence (decisive lines)

run.log: 'num_entries = 0', 'entries[4294967295]', '+17179869180 bytes (~17.2 GB)'; run.2.log: crash mode Segmentation fault exit=139; VERDICT.md; fix.diff

PoC changes

No seed (pass-2 self-authored); single guest build+run cycle succeeded.

Verified recommended fix

Guard num_entries != 0 before the entries[num_entries-1] fallback returns in kv_convert_vid2_to_vid7 (:90) and kv_convert_vid7_to_vid2 (:114).

Verdict

Source-certain count-1 underflow wild read; runtime-inconclusive on this guest (no AMD GPU). kv_convert_vid2_to_vid7 (kv_dpm.c:90) returns vid_mapping_table->entries[num_entries - 1].vid_7bit when no entry matches; num_entries comes from sumo_construct_vid_mapping_table (:185-190) and is legitimately 0 when every VBIOS sAvail_SCLK usVoltageID == 0 (while nonzero strictly-increasing ulSupportedSCLK keeps num_max_dpm_entries > 0 so the :2040 guard passes). With vddc_dependency_on_sclk.count == 0 (no SCLK dep subtable, amdgpu_dpm.c:384-395), the else branch runs at boot: kv_dpm_init -> kv_parse_sys_info_table (:2617) -> kv_construct_max_power_limits_table (:2045) -> kv_convert_2bit_index_to_voltage (:690) -> entries[0xFFFFFFFF] = +17,179,869,180 bytes (~16 GiB) -> page fault -> panic. Identical latent pattern at :114. Harness prints index/offset and crash mode SIGSEGVs on the verbatim read. Distinct from DF-3887 (write with out-of-range index) and DF-1268/1269.