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

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3620 Β· 8 files
FileTypeDescriptionSize
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
VERDICT.md
↓ download 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 address 0x7FFF8 from the real ZERO_LENGTH_PTR definition.

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_testable
baseline no→ patch + rebuild →patched clean

fix.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
↓ fix.diffper-fix-DF-3620

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