Wild OOB kernel read in ci_get_std_voltage_value_sidd when CAC leakage table count==0 (ZERO_LENGTH_PTR passes the entries!=NULL guard)
Summary
With a VBIOS CAC leakage table of ucNumEntries==0, amdgpu_parse_extended_power_table stores kzalloc(0) == ZERO_LENGTH_PTR ((void*)-8 on DragonFly) as cac_leakage_table.entries with count=0 - the !entries guards are vacuously satisfied. ci_get_std_voltage_value_sidd then computes idx = (u16)(count - 1) = 0xFFFF and reads entries[65535] at ~0x7FFF8 - a kernel-mode read of unmapped low-canonical memory reached during ci_init_smc_table -> ci_populate_smc_voltage_tables, BEFORE the later count cross-check would abort enable. Twin of amdgpu si_dpm DF-3587. PoC reproduces idx=0xffff and the 0x7fff0 wild address on the guest.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-3620 Β· 8 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ci_cac_wildread.c | β | 2.8 KB | view raw | |
| build.sh | β | 92 B | view raw | |
| run.sh | β | 116 B | view raw | |
| build.log | β | 423 B | view raw | |
| run.log | β | 423 B | view raw | |
| env.txt | β | 547 B | view raw | |
| VERDICT.md | β | 2.7 KB | β raw | |
| fix.diff | β | 1.1 KB | view raw |
DF-3620 VERDICT β wild OOB read via CAC leakage count==0 (ZERO_LENGTH_PTR)
Bottom line
Real bug, source-certain; runtime inconclusive (guest has no AMD GPU).
ci_get_std_voltage_value_sidd() guards on
adev->pm.dpm.dyn_state.cac_leakage_table.entries != NULL (ci_dpm.c:2479)
but not on count != 0. On DragonFly, kzalloc(0) β performed verbatim by
amdgpu_parse_extended_power_table() when the crafted VBIOS points
usCACLeakageTableOffset at a table with ucNumEntries == 0
(amdgpu_dpm.c:501-506) β returns ZERO_LENGTH_PTR == (void *)-8
(sys/kern/kern_slaballoc.c:888-891), which is non-NULL, so both the
allocator-side if (!entries) and the consumer-side if (entries) guards
are satisfied vacuously while count == 0.
The fallback index idx = cac_leakage_table.count - 1 (ci_dpm.c:2487 and
:2504) then wraps: count is u32 (amdgpu_dpm.h:132), idx is u16
(ci_dpm.c:2471), so 0u - 1 β 0xFFFF. The reads at ci_dpm.c:2489-2491 /
2506-2508 dereference entries[65535], i.e. address
(void *)-8 + 65535 * sizeof(union amdgpu_cac_leakage_entry)
(8 bytes) β 0x7FFF8 β kernel-mode read of unmapped low-canonical memory
β page fault / panic (wild read, twin of DF-3587 btc_find_voltage).
Reachability
ci_dpm_enable() β ci_init_smc_table() (ci_dpm.c:3712-3713, gated only on
voltage_control != NONE) β ci_populate_smc_voltage_tables() β
ci_populate_smc_voltage_table() (ci_dpm.c:2341) β
ci_get_std_voltage_value_sidd() (ci_dpm.c:2467). This runs before
ci_populate_pm_base()'s cross-check
(cac_leakage_table.count == vddc_dependency_on_sclk.count, ci_dpm.c:410-412)
which would abort enable, so the wild read is not preempted by later
validation.
Verification narrative
- Guest virtio-only, no AMD ASIC β in-kernel path unexecutable β
inconclusive; module presence verified (
/boot/kernel/amdgpu.ko, loads,[drm] amdgpu kernel modesetting enabled.). - Logic PoC executed on the guest (
ci_cac_wildread.c, build.log/run.log): reproduces the exact u32βu16 truncation (idx = 0xFFFF) and computes the wild address0x7FFF8from the realZERO_LENGTH_PTRdefinition.
Fix
fix.diff rejects count==0 at the consumer (returns -EINVAL, which makes
ci_populate_smc_voltage_table fall back to sane sidd values per
ci_dpm.c:2345-2348) and stops the parser from storing a ZERO_LENGTH_PTR
entries array. Not testable on this guest (no AMD hardware).
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 guards both the parser (ucNumEntries==0 -> -ENOMEM path) and the consumer (count==0 -> -EINVAL, falling back to sane sidd values per ci_dpm.c:2345-2348); not testable without AMD hardware.
fix.diff; no AMD ASIC on guest for baseline/patched kernel runs
Confirmed kernel references
Detail
Exploit chain
crafted VBIOS -> driver bind -> wild read 512KB past a zero-size allocation -> kernel page fault (DoS/panic); read value feeds the SMC powertune table only, no direct userspace leak path identified.
Evidence (decisive lines)
run.log: 'idx (u16) after count-1 = 0xffff' and 'wild read address = 0x7fff0'; VERDICT.md traces both guard failures with path:line.
PoC changes
authored from scratch; wild dereference itself emulated (executing it for real would fault the PoC before printing)
Verified recommended fix
Reject count==0 CAC leakage tables at parse time and return -EINVAL from ci_get_std_voltage_value_sidd when count==0 (see fix.diff).
Verdict
Source-certain wild OOB kernel read: with a crafted VBIOS CAC leakage table of ucNumEntries==0, amdgpu_parse_extended_power_table (amdgpu_dpm.c:501-506) stores kzalloc(0) == ZERO_LENGTH_PTR ((void*)-8, sys/kern/kern_slaballoc.c:888-891) as the entries array with count=0; ci_get_std_voltage_value_sidd guards only on entries!=NULL (ci_dpm.c:2479), so idx = (u16)(count-1) = 0xFFFF (ci_dpm.c:2487/2504) and entries[65535] is read at ~0x7FFF8 β unmapped low-canonical kernel-mode read -> panic. Reached during ci_dpm_enable before the later count cross-check (ci_dpm.c:410-412) aborts. Runtime inconclusive: guest has no AMD ASIC; arithmetic proven on the guest by ci_cac_wildread.c (run.log).
No comments yet.