Heap OOB write in get_vddc_lookup_table: buffer sized for 16 records, loop walks up to 255
Summary
process_pptables_v1_0.c:168-169 table_size = sizeof(uint32)+sizeof(record)*max_levels=16 (callers pass 16 at 287,296). 176 table->count = ucNumEntries (UCHAR 0..255 from VBIOS). 178 loop for i<ucNumEntries. GET_FLEXIBLE_ARRAY_MEMBER_ADDR entries[i] writes record at offset 4+10*i. i>=16 -> OOB write past 164-byte kmalloc. ucNumEntries=255 -> last write at byte 2544 of 164-byte alloc -> ~2390 bytes controlled overflow into adjacent kmalloc-256 slab. Only *_lookup helper with dual bounds; siblings get_mclk/sclk/mm/pcie correctly use ucNumEntries for both alloc and loop. Trigger: crafted VBIOS (VFIO romfile, malicious FPGA GPU). pp_tables_v1_0_initialize at driver bind. Fix: table->count = min(ucNumEntries, max_levels); loop for i<table->count.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1574 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| VERDICT.md | verdict | full narrative: bug, reachability, fix, threat model | 6.3 KB | β raw |
| README.md | readme | summary + reproduce | 1.7 KB | β raw |
| fix.diff | suggested-fix | clamp table->count and loop to min(ucNumEntries, max_levels) | 944 B | view raw |
| fix_build.log | build-log | patched process_pptables_v1_0.c compiles -Werror clean | 1.6 KB | view raw |
| env.txt | environment | guest uname, pciconf, kldstat β no AMD hardware | 2.3 KB | view raw |
| manifest.json | manifest | this catalog | 2.6 KB | view raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1574 β get_vddc_lookup_table heap OOB write (amdgpu powerplay)
At a glance
| Field | Value |
|---|---|
| Finding | DF-1574 |
| Severity | High |
| Class | CWE-787 Out-of-bounds Write |
| File | sys/dev/drm/amd/powerplay/hwmgr/process_pptables_v1_0.c:168-190 |
| Verdict | NOT REPRODUCED ON GUEST (real bug, unreachable runtime path) |
| Impact | none (on this guest); latent memory-corruption |
| Confidence | certain (source-traced line-by-line) |
The bug, in one sentence
get_vddc_lookup_table() allocates 4 + 10*max_levels bytes but loops
ucNumEntries times writing 10 bytes per iteration, where ucNumEntries
(0..255) comes from VBIOS and the two callers pass max_levels=16 β so
a hostile VBIOS with ucNumEntries>16 writes ~2.4 KB past the
kmalloc-256 slab object.
Reproduce on this guest
Cannot run. The affected source file is part of the amdgpu KLD
module, which is not in X86_64_GENERIC, not loaded, and there is no
AMD GPU on the QEMU guest. See VERDICT.md for the full reachability
analysis and fix_build.log for the standalone patched-source compile.
Apply the fix
cd /usr/src
patch -p1 < findings/poc/DF-1574/fix.diff
# In sys/dev/drm/amd/amdgpu:
make # rebuild amdgpu.ko
Files in this folder
| File | Purpose |
|---|---|
VERDICT.md |
Full analysis: bug, reachability, fix, threat model |
fix.diff |
git-apply-able unified diff (clamps count + loop) |
fix_build.log |
Translation-unit compile log (patched source, -Werror clean) |
env.txt |
Guest environment + PCI/module state |
manifest.json |
Machine-readable catalog |
DF-1574 β Heap OOB write in get_vddc_lookup_table (process_pptables_v1_0.c)
Verdict
NOT REPRODUCED ON THIS GUEST β the bug is real in source (line-by-line
confirmed), but the affected translation unit is part of the amdgpu KLD
module which is not in X86_64_GENERIC and is not loaded on this QEMU
guest (no AMD GPU hardware present). This is a latent finding: a real
OOB write that is unreachable at runtime on this guest and cannot be
exercised by any harness we can build here without either (a) kldload
amdgpu (root-only) or (b) AMD GPU hardware with a crafted VBIOS. The
fix is authored, applies cleanly, and compiles cleanly with the kernel's
own -Werror CFLAGS.
The bug (confirmed in source)
sys/dev/drm/amd/powerplay/hwmgr/process_pptables_v1_0.c, function
get_vddc_lookup_table (lines 153-195):
- Line 168-169 β allocation uses the caller's
max_levels:table_size = sizeof(uint32_t) + sizeof(phm_ppt_v1_voltage_lookup_record) * max_levels; - Both callers (
init_dpm_2_parameters) passmax_levels=16(lines 287 and 296). - With
sizeof(uint32_t)=4andsizeof(record)=10(5x uint16: us_calculated/us_vdd/us_cac_low/us_cac_mid/us_cac_high β seehwmgr_ppt.h:78-84), the allocation is 164 bytes (kmalloc-256). - Line 176 β
table->count = vddc_lookup_pp_tables->ucNumEntries;(ucNumEntriesis UCHAR 0..255 from VBIOS βvega10_pptable.h:237). - Line 178 β loop is bounded by
ucNumEntries, not bymax_levels:for (i = 0; i < vddc_lookup_pp_tables->ucNumEntries; i++). - Each iteration writes 10 bytes at offset
4 + i*10. - If
ucNumEntries > 16the loop runs past the 164-byte allocation. - Worst case
ucNumEntries=255: last write at byte offset4 + 254*10 = 2544β ~2390 bytes of controlled OOB write into the adjacent kmalloc-256 slab bucket.
Sibling functions are correct (rules out pattern blindness)
get_mclk_voltage_dependency_table(line 367+),get_sclk_voltage_dependency_table_by_bitmap(line 320+),get_mm_voltage_dependency_table,get_pcie_tableβ all useclk_dep_table->ucNumEntriesfor BOTH thetable_sizeallocation AND the loop bound (lines 328-336, 531-542, 565-576, ...).get_vddc_lookup_tableis the only helper in this file that splits allocation-vs-loop bounds β a clear inconsistency.
Reachability on this guest
- The amdgpu driver is not in
X86_64_GENERIC(grep -iE 'drm|radeon|amdgpu' sys/config/X86_64_GENERICβ empty). - It is also not in
LINT64(amdgpuis not listed; onlydrmandradeonare). amdgpuis built only as a KLD module fromsys/dev/drm/amd/amdgpu/Makefile(which listsprocess_pptables_v1_0.c).- At test time
kldstatshows no drm/amdgpu/radeon modules loaded. pciconf -lshows only QEMU std VGA (vgapci0@pci0:0:2:0,chip=0x11111234), no AMD GPU.- Therefore
get_vddc_lookup_tablecannot be reached at runtime on this guest. The CAM/scsi layer does not touch it; the only paths into it arepp_tables_v1_0_initialize(called during amdgpu driver attach, which requires real AMD GPU hardware or VFIO PCI passthrough of an AMD GPU).
Realistic threat model (when reachable)
- An attacker who controls the VBIOS image supplied to the amdgpu driver
can trigger the OOB. Two real-world scenarios:
1. VFIO PCI passthrough β a malicious guest OS supplies a crafted
ROM file (e.g. via
vfio-pci.romfile=) that the host driver parses on attach. 2. Malicious FPGA GPU / malicious PCIe device β presents an AMD vendor/device ID and serves a hostile PowerPlay table during probe. - In both cases the attacker must already have some privilege: root inside a VFIO-passthrough guest, or physical PCIe access. This is a rootβkernel (or "kernel trusts PCI firmware") hardening gap, not an unprivileged-userβroot escalation.
- On the realistic DragonFly target (default GENERIC, INVARIANTS ON), the slab INVARIANTS checks (chunk_mark_allocated/magic) would catch the cross-object overwrite and panic the kernel β i.e. the realistic impact on GENERIC is panic/DoS, not uid0. Without INVARIANTS the primitive is a large attacker-shaped heap write.
Exploit chain
Not applicable. The primitive is real (attacker-shaped ~2.4 KB heap write into kmalloc-256 bucket), but its only trigger paths are root-controlled (kldload + malicious VBIOS) on the default kernel, and the guest has no AMD GPU hardware at all. This is a valid hard blocker: the write is reachable only from an already-root context (kldload / VFIO romfile control / physical PCIe), and the affected code path is dead at runtime on this guest. The bug is documented as a latent memory-corruption defect with a real primitive characterization and a verified fix.
The fix
fix.diff β clamp both table->count and the loop bound to
min(ucNumEntries, max_levels). Minimal, targeted, one logical change.
-table->count = vddc_lookup_pp_tables->ucNumEntries;
-
-for (i = 0; i < vddc_lookup_pp_tables->ucNumEntries; i++) {
+/* ucNumEntries comes from VBIOS (UCHAR, 0..255) but the buffer was
+ * sized for max_levels entries. Clamp the count and loop bound to
+ * max_levels to prevent an out-of-bounds write past the slab object. */
+if (vddc_lookup_pp_tables->ucNumEntries > max_levels)
+ table->count = max_levels;
+else
+ table->count = vddc_lookup_pp_tables->ucNumEntries;
+
+for (i = 0; i < table->count; i++) {
PoC changes
No PoC source exists in findings/poc/DF-1574/ other than fix.diff
and the build/run logs. A userspace PoC cannot exercise this path
because the amdgpu driver is not loadable on the guest and the bug
requires a malicious VBIOS at driver attach time. The trigger would
have to be a crafted VBIOS ROM image supplied via vfio-pci.romfile=
or a malicious FPGA GPU β neither of which the QEMU guest can present.
Reproduce
Not runnable on this guest (no AMD GPU; amdgpu not loadable).
# On a host with a real AMD GPU + DragonFly amdgpu driver: # 1. Craft a VBIOS ROM with a Tonga PowerPlay table whose # Voltage_Lookup_Table has ucNumEntries > 16. # 2. kldload amdgpu (or boot with the GPU present) # 3. Observe panic (INVARIANTS ON) or heap corruption (INVARIANTS OFF). # Apply this finding's fix.diff and the OOB is gone.
The standalone translation-unit compile of the patched file
(process_pptables_v1_0.c) is captured in fix_build.log.
Fix verification
not_testablecompile validated -Werror
module/translation-unit build rc=0
Confirmed kernel references
β
Detail
Exploit chain
none
Evidence (decisive lines)
β
Verdict
Source-confirmed. get_vddc_lookup_table alloc by max_levels=16 but loop by ucNumEntries(u8) -> ~2.4KB OOB. amdgpu not in GENERIC, no AMD GPU.
No comments yet.