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