โฌข DragonFlyBSD Kernel Audit
DF-3620 / ci_cac_wildread.c
โ† back to finding โ†“ download raw
/* DF-3620 โ€” logic proof for the ci_get_std_voltage_value_sidd wild OOB read.
 *
 * Path (amdgpu ci_dpm.c): ci_dpm_enable -> ci_init_smc_table (ci_dpm.c:3712)
 * -> ci_populate_smc_voltage_tables -> ci_populate_smc_voltage_table (2341)
 * -> ci_get_std_voltage_value_sidd (2467):
 *
 *   if (adev->pm.dpm.dyn_state.cac_leakage_table.entries) {   // 2479 โ€” true even when count==0!
 *           ...
 *           if ((u32)v_index < adev->pm.dpm.dyn_state.cac_leakage_table.count)
 *                   idx = v_index;
 *           else
 *                   idx = adev->pm.dpm.dyn_state.cac_leakage_table.count - 1;  // 2487: 0-1
 *           *std_voltage_lo_sidd = ...entries[idx].vddc ...;   // 2489 โ€” idx == 0xFFFF
 *
 * count==0 precondition: amdgpu_parse_extended_power_table (amdgpu_dpm.c:495-526)
 * only allocates the entries array when usCACLeakageTableOffset != 0, and does
 *     size = ucNumEntries * sizeof(...); entries = kzalloc(size, ...)
 * With ucNumEntries == 0 the allocation is kzalloc(0). On DragonFly,
 * _kmalloc() returns ZERO_LENGTH_PTR == (void *)-8 for size 0
 * (sys/kern/kern_slaballoc.c:888-891, ZERO_LENGTH_PTR at :193) โ€” NON-NULL โ€”
 * so the `if (!entries)` guard does not fire and count stays 0.
 *
 * idx is u16; count is u32; `count - 1` == 0xFFFFFFFF truncates to 0xFFFF.
 * entries[0xFFFF] reads at ZERO_LENGTH_PTR + 65535*8 = ~0x7FFF8 โ€” unmapped
 * low canonical address -> kernel page fault / panic (wild OOB read).
 *
 * Build: cc -O2 -Wall -o ci_cac_wildread ci_cac_wildread.c
 * Run:   ./ci_cac_wildread   (simulates the index arithmetic; the actual
 *        dereference is emulated, since executing it for real would fault)
 */
#include <stdio.h>
#include <stdint.h>

#define ZERO_LENGTH_PTR ((void *)-8)
#define WILD_READ_UADDR ((uintptr_t)ZERO_LENGTH_PTR + 65535UL * 8UL)

int main(void)
{
	uint32_t cac_count = 0;                 /* ucNumEntries == 0 from crafted VBIOS */
	uint16_t idx;
	uint32_t v_index = 0;                   /* first matching vddc_dependency_on_sclk entry */

	/* ci_dpm.c:2484-2487 */
	if (v_index < cac_count)
		idx = v_index;
	else
		idx = (uint16_t)(cac_count - 1);  /* exact expression: u32 0-1 -> u16 */

	printf("cac_leakage_table.entries = %p (ZERO_LENGTH_PTR, non-NULL)\n", ZERO_LENGTH_PTR);
	printf("cac_leakage_table.count   = %u\n", cac_count);
	printf("idx (u16) after count-1   = 0x%04x (%u)\n", idx, idx);
	printf("wild read address = entries[0xFFFF] = %#llx -> %s\n",
	       (unsigned long long)WILD_READ_UADDR,
	       WILD_READ_UADDR < 0x0000800000000000UL
		     ? "low canonical (unmapped in kernel ctx) -> kernel page fault / panic"
		     : "high canonical");
	printf("sizeof(union amdgpu_cac_leakage_entry)-based stride: 8 bytes\n");
	printf("offset = 65535 * 8 = %lu bytes past a ZERO_SIZE allocation\n", 65535UL * 8UL);
	return 0;
}