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)
PoC verification
Evidence pack
findings/poc/DF-3890 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 offset4294967295 * 4 = 17179869180(~17.2 GB) past the 16-byte array. - crash:
Segmentation fault, exit=139 (kernel equivalent: page fault in kernel mode β panic).
Kernel trigger (all VBIOS-controlled, parse-legal)
sAvail_SCLK[]has strictly-increasing nonzeroulSupportedSCLK(sonum_max_dpm_entries > 0and the guard at kv_dpm.c:2040 passes) butusVoltageID == 0on every entry βsumo_construct_vid_mapping_tableleaves everyvid_7bitzero β compaction breaks ati == 0βnum_entries = 0(kv_dpm.c:185-190).- 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.
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)
sys/dev/drm/amd/amdgpu/kv_dpm.c:90β underflowed index readsys/dev/drm/amd/amdgpu/kv_dpm.c:114β identical pattern (latent)sys/dev/drm/amd/amdgpu/kv_dpm.c:185-190β num_entries can legitimately be 0sys/dev/drm/amd/amdgpu/kv_dpm.c:2045/:690β reachable call chainsys/dev/drm/amd/amdgpu/amdgpu_dpm.c:384-395β count==0 is parse-legal
What was verified, and how
- Source-level certainty. Full-file pass-2 audit; compaction logic walked
for the all-zero-
usVoltageIDcase; thecount == 0else-branch selection verified againstamdgpu_parse_extended_power_table. - Harness on the guest (DragonFly 6.5-DEVELOPMENT x86_64, base cc 8.3):
vid_mapping_zero_entries.creplicates the function verbatim; accounting mode printsentries[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). - 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_testablefix.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
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.
No comments yet.