Heap OOB write in ci_get_svi2_voltage_table: VBIOS dependency-table count (u8, up to 255) copied into fixed entries[32] before any trim
Summary
For SVID2-controlled VDDC/VDDCI/MVDD (the common Sea Islands regulator style), ci_construct_voltage_tables copies dependency counts of up to 255 (raw VBIOS u8) into struct atom_voltage_table.entries[MAX_VOLTAGE_ENTRIES=32] embedded in the heap struct ci_power_info; the trim calls run only AFTER the copy. Up to (255-32)*8 = 1784 bytes of overwrite with attacker-chosen u16 voltage values at an 8-byte stride, running ~1KB past the end of the kzalloc'd ci_power_info. On-guest harness with exact kernel types proved geometry: 200-entry table -> 1344-byte OOB write, adjacent canary clobbered. Crafted VBIOS on a CI-class card -> kernel heap corruption at DPM init (radeon/ci twin of DF-3585/DF-3614/DF-3619; distinct array from pass-1 DF-1150). Upstream-inherited (Linux master line-identical).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3644 Β· 12 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | doc | 850 B | β raw | |
| VERDICT.md | doc | 4.1 KB | β raw | |
| fix.diff | doc | 582 B | view raw | |
| env.txt | doc | 799 B | view raw | |
| verdict.json | doc | 4.4 KB | view raw | |
| manifest.json | doc | 1.6 KB | view raw | |
| harness.c | doc | 5.7 KB | view raw | |
| build.sh | doc | 65 B | view raw | |
| run.sh | doc | 89 B | view raw | |
| build.log | log | 0 B | β download | |
| run.log | log | 556 B | view raw | |
| run.2.log | log | 556 B | view raw |
DF-3644 β Heap OOB write in ci_get_svi2_voltage_table: VBIOS dependency-table count (u8, up to 255) copied into fixed entries[32] before any trim
File: sys/dev/drm/radeon/ci_dpm.c (pass 2, GLM 5.3 re-audit)
Severity: High | CWE-787 Out-of-bounds Write | confidence: certain
Reproduce
This pack carries a userland logic harness (harness.c) built and run ON THE
AUDIT GUEST with the exact kernel types; decisive section: [A]
./build.sh ./run.sh
Expected decisive line: OOB write of 1344 bytes past entries[32]; canary clobbered: YES
The kernel code path itself requires a Sea Islands radeon GPU + crafted VBIOS; the guest is virtio-only (no AMD PCI device, no /dev/dri, no drm kld) β see env.txt.
Full narrative: VERDICT.md. Proposed fix: fix.diff (authored against the read-only sys/ tree; never applied there).
DF-3644 β verdict narrative
Finding
Heap OOB write in ci_get_svi2_voltage_table: VBIOS dependency-table count (u8, up to 255) copied into fixed entries[32] before any trim
Kernel references
sys/dev/drm/radeon/ci_dpm.c:2147sys/dev/drm/radeon/ci_dpm.c:2159sys/dev/drm/radeon/ci_dpm.c:2160sys/dev/drm/radeon/ci_dpm.c:2187sys/dev/drm/radeon/ci_dpm.c:2205sys/dev/drm/radeon/ci_dpm.c:2223sys/dev/drm/radeon/r600_dpm.c:821sys/dev/drm/radeon/r600_dpm.c:841sys/dev/drm/radeon/radeon_mode.h:671sys/dev/drm/radeon/radeon_mode.h:684
Was it reproduced?
No β not on the kernel path. The audit guest is virtio-only (0 AMD PCI devices, /dev/dri absent, no radeon/drm kld; env.txt), so sys/dev/drm/radeon/ci_dpm.c can never execute there. Per the pass-2 instruction for this hardware-gated file, the verdict is inconclusive (source-level certainty certain) rather than reproduced.
Logic-level proof (guest harness)
harness.c models the exact loop/index/division with the exact kernel types and was compiled and executed on the audit guest (cc -O2 -Wall, DragonFly 6.5-DEVELOPMENT). Decisive output, stable across 2 runs (run.log, run.2.log):
OOB write of 1344 bytes past entries[32]; canary clobbered: YES
Root cause and reasoning
Source-certain heap buffer overflow, radeon/ci twin of DF-3585/DF-3614 (si) and DF-3619 (amdgpu ci). ci_construct_voltage_tables() (ci_dpm.c:2179-2182, 2197-2200, 2215-2218) calls ci_get_svi2_voltage_table() when the VBIOS programs CISLANDS_VOLTAGE_CONTROL_BY_SVID2 (the common regulator style on Sea Islands). That helper sets voltage_table->count = voltage_dependency_table->count (ci_dpm.c:2159) and copies that many struct atom_voltage_table_entry (8 bytes each) into entries[] (ci_dpm.c:2160-2163). The count comes raw from the VBIOS u8 ucNumEntries via r600_parse_clk_voltage_dep_table (r600_dpm.c:821-844, no cap), so up to 255; the destination array is entries[MAX_VOLTAGE_ENTRIES=32] (radeon_mode.h:671-685) embedded in struct ci_power_info (ci_dpm.h:228-230). The si_trim_voltage_table_to_fit_state_table() calls at ci_dpm.c:2187-2189/2205-2207/2223-2225 cap the count to 8/4/4 only AFTER the copy - far too late. Worst case is (255-32)*8 = 1784 bytes of overwrite past entries[32], running over vddci_voltage_table, mvdd_voltage_table and ~1KB past the end of the kzalloc'd struct ci_power_info. Data is semi-attacker-chosen: a u16 voltage value at every 8-byte stride plus zeroed smio_low. On-guest userland harness with the exact kernel types proved the geometry (200-entry table -> 1344-byte OOB write, adjacent canary clobbered). Runtime reproduction on the kernel path is impossible on this virtio-only guest (no AMD GPU), hence inconclusive/not reproduced at kernel level. Same root cause family as pass-1 DF-1150 but a DIFFERENT array (atom_voltage_table.entries vs ci dpm_levels) and different function - not a re-report.
Exploitability
Provision a crafted VBIOS on a Sea Islands card (BONAIRE/HAWAII; device IDs routed to this file via radeon_family) whose VoltageObjectInfo marks VDDC as SVID2 and whose vddc/vddci/mvdd dependency tables carry >32 entries; boot/login triggers ci_dpm_init -> ci_construct_voltage_tables -> the overflow. Characterized primitive: attacker-chosen u16 at each 8-byte slot over ~1.8KB past a large kmalloc - enough to corrupt the adjacent slab object's fields (e.g. function-pointer low halves / refcounts) in the classic slab-groom -> victim-object -> pivot chain; on this audit target the escalation chain cannot be exercised because the guest has no CI GPU (hardware-gated). Panic-level impact is certain from source; uid=0 route plausible but untestable here.
Recommended fix
Reject SVI2 dependency tables with count > MAX_VOLTAGE_ENTRIES in ci_get_svi2_voltage_table before the copy loop. See fix.diff.
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 validation would require a CI-class GPU with a crafted VBIOS (baseline panic/corruption) and a single-fix kernel build; the audit guest is virtio-only, so the fix is source-reviewed only (fix.diff).
fix.diff in this pack; not applied to sys/ (read-only audit target).
Confirmed kernel references
- sys/dev/drm/radeon/ci_dpm.c:2147
- sys/dev/drm/radeon/ci_dpm.c:2159
- sys/dev/drm/radeon/ci_dpm.c:2160
- sys/dev/drm/radeon/ci_dpm.c:2187
- sys/dev/drm/radeon/ci_dpm.c:2205
- sys/dev/drm/radeon/ci_dpm.c:2223
- sys/dev/drm/radeon/r600_dpm.c:821
- sys/dev/drm/radeon/r600_dpm.c:841
- sys/dev/drm/radeon/radeon_mode.h:671
- sys/dev/drm/radeon/radeon_mode.h:684
Detail
Exploit chain
Provision a crafted VBIOS on a Sea Islands card (BONAIRE/HAWAII; device IDs routed to this file via radeon_family) whose VoltageObjectInfo marks VDDC as SVID2 and whose vddc/vddci/mvdd dependency tables carry >32 entries; boot/login triggers ci_dpm_init -> ci_construct_voltage_tables -> the overflow. Characterized primitive: attacker-chosen u16 at each 8-byte slot over ~1.8KB past a large kmalloc - enough to corrupt the adjacent slab object's fields (e.g. function-pointer low halves / refcounts) in the classic slab-groom -> victim-object -> pivot chain; on this audit target the escalation chain cannot be exercised because the guest has no CI GPU (hardware-gated). Panic-level impact is certain from source; uid=0 route plausible but untestable here.
Evidence (decisive lines)
VERDICT.md (full source trace); harness.c + run.log/run.2.log section [A] (OOB write of 1344 bytes past entries[32]; canary clobbered: YES); env.txt (guest has no AMD GPU: pciconf 0x1002 count=0, /dev/dri absent, no drm kld); fix.diff
PoC changes
n/a β no seeded PoC for pass-2; harness written fresh (models the exact kernel types; divisors made volatile so -O2 cannot fold the #DE)
Verified recommended fix
Reject SVI2 dependency tables with count > MAX_VOLTAGE_ENTRIES in ci_get_svi2_voltage_table before the copy loop.
Verdict
Source-certain heap buffer overflow, radeon/ci twin of DF-3585/DF-3614 (si) and DF-3619 (amdgpu ci). ci_construct_voltage_tables() (ci_dpm.c:2179-2182, 2197-2200, 2215-2218) calls ci_get_svi2_voltage_table() when the VBIOS programs CISLANDS_VOLTAGE_CONTROL_BY_SVID2 (the common regulator style on Sea Islands). That helper sets voltage_table->count = voltage_dependency_table->count (ci_dpm.c:2159) and copies that many struct atom_voltage_table_entry (8 bytes each) into entries[] (ci_dpm.c:2160-2163). The count comes raw from the VBIOS u8 ucNumEntries via r600_parse_clk_voltage_dep_table (r600_dpm.c:821-844, no cap), so up to 255; the destination array is entries[MAX_VOLTAGE_ENTRIES=32] (radeon_mode.h:671-685) embedded in struct ci_power_info (ci_dpm.h:228-230). The si_trim_voltage_table_to_fit_state_table() calls at ci_dpm.c:2187-2189/2205-2207/2223-2225 cap the count to 8/4/4 only AFTER the copy - far too late. Worst case is (255-32)*8 = 1784 bytes of overwrite past entries[32], running over vddci_voltage_table, mvdd_voltage_table and ~1KB past the end of the kzalloc'd struct ci_power_info. Data is semi-attacker-chosen: a u16 voltage value at every 8-byte stride plus zeroed smio_low. On-guest userland harness with the exact kernel types proved the geometry (200-entry table -> 1344-byte OOB write, adjacent canary clobbered). Runtime reproduction on the kernel path is impossible on this virtio-only guest (no AMD GPU), hence inconclusive/not reproduced at kernel level. Same root cause family as pass-1 DF-1150 but a DIFFERENT array (atom_voltage_table.entries vs ci dpm_levels) and different function - not a re-report.
No comments yet.