โฌข DragonFlyBSD Kernel Audit
DF-4666 / find_closest_vddci_zero.c
โ† back to finding โ†“ download raw
/*
 * DF-4666 harness โ€” verbatim kernel function + 1:1 struct layout.
 *
 * Bug: sys/dev/drm/amd/powerplay/hwmgr/smu_helper.c:426-437
 *      phm_find_closest_vddci() is the ONLY member of its family with no
 *      empty-table guard:
 *        phm_get_voltage_index  (:393)  asserts count != 0
 *        phm_get_voltage_id     (:413)  asserts count != 0
 *        phm_find_closest_vddci (:426)  NOTHING
 *      With count == 0 the loop never runs and the fallback
 *        return vddci_table->entries[i-1].value;   (i == 0, uint32_t)
 *      reads entries[0xFFFFFFFF] => base + 0xFFFFFFFF*8 = +34,359,738,360
 *      (~32 GiB) โ€” wild kernel read, guaranteed fault on any real mapping.
 *
 * count==0 provenance (crafted VBIOS, all in-tree):
 *   ppatomctrl.c:546-563 atomctrl_get_voltage_table_v3 only asserts
 *   ucGpioEntryNum <= PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES(32) โ€” 0 passes;
 *   it then sets voltage_table->count = ucGpioEntryNum = 0 and returns 0.
 *   smu7_hwmgr.c:276-282 accepts this (result == 0) when
 *   vddci_control == SMU7_VOLTAGE_CONTROL_BY_GPIO.
 *   smu7_hwmgr.c:337-342 only TRIMS when count > SMU_MAX_LEVELS_VDDCI.
 *   Later, polaris10_smumgr.c:377/402 (also vegam_smumgr.c:626/646/1227/1341,
 *   fiji_smumgr.c:380/405) call phm_find_closest_vddci whenever a dep-table
 *   entry has vddci == 0 and vddci_control != NONE  =>  wild read.
 *
 * Modes:
 *   safe   โ€” compute the exact wild address without dereferencing.
 *   deref  โ€” actually run the verbatim function (dies SIGSEGV).
 *   normal โ€” control: populated table, normal path returns sane value.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/wait.h>
#include <unistd.h>

/* ---- verbatim: ppatomctrl.h:45,200-213 ---- */
#define PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES 32

struct pp_atomctrl_voltage_table_entry {
	uint16_t value;
	uint32_t smio_low;
};

struct pp_atomctrl_voltage_table {
	uint32_t count;
	uint32_t mask_low;
	uint32_t phase_delay;
	struct pp_atomctrl_voltage_table_entry entries[PP_ATOMCTRL_MAX_VOLTAGE_ENTRIES];
};

/* ---- VERBATIM: smu_helper.c:426-437 ---- */
uint16_t phm_find_closest_vddci(struct pp_atomctrl_voltage_table *vddci_table, uint16_t vddci)
{
	uint32_t  i;

	for (i = 0; i < vddci_table->count; i++) {
		if (vddci_table->entries[i].value >= vddci)
			return vddci_table->entries[i].value;
	}

	printf("vddci is larger than max value in vddci_table\n");
	return vddci_table->entries[i-1].value;
}
/* ---- end verbatim ---- */

int main(int argc, char **argv)
{
	const char *mode = (argc > 1) ? argv[1] : "safe";
	struct pp_atomctrl_voltage_table *t = calloc(1, sizeof(*t));

	if (!t) { perror("calloc"); exit(1); }

	if (!strcmp(mode, "normal")) {
		/* control: GPIO LUT with 4 entries; nothing exceeds request */
		t->count = 4;
		for (uint32_t k = 0; k < 4; k++)
			t->entries[k].value = (uint16_t)(800 + 10 * k);
		uint16_t r = phm_find_closest_vddci(t, 9999);
		printf("[normal] count=4 -> returns entries[3].value = %u (in bounds)\n", r);
		return 0;
	}

	/* the crafted-VBIOS state: ucGpioEntryNum == 0 -> count == 0 */
	t->count = 0;

	if (!strcmp(mode, "deref")) {
		uint16_t r;
		printf("[deref] calling verbatim phm_find_closest_vddci(count=0)...\n");
		fflush(stdout);
		r = phm_find_closest_vddci(t, 1000);
		/* not reached on failure */
		printf("[deref] SURVIVED, returned %u (unexpected)\n", r);
		return 1;
	}

	/* safe: exact address math for the fallback read, no deref */
	{
		uint32_t i = t->count;			/* == 0 after the loop */
		char *base = (char *)&t->entries[0];
		char *wild = (char *)&t->entries[(uint32_t)(i - 1)];
		printf("[safe] count=%u -> fallback reads entries[i-1] = entries[0x%08x]\n",
		       t->count, (uint32_t)(i - 1));
		printf("[safe] entries base        = %p\n", base);
		printf("[safe] wild read address   = %p\n", wild);
		printf("[safe] effective byte offset = %llu (0x%llx)\n",
		       (unsigned long long)(wild - base),
		       (unsigned long long)(wild - base));
		printf("[safe] entry size = %zu -> offset = 0xFFFFFFFF * %zu\n",
		       sizeof(struct pp_atomctrl_voltage_table_entry),
		       sizeof(struct pp_atomctrl_voltage_table_entry));
		printf("WILD-READ CONFIRMED (address math): kernel would read ~32 GiB "
		       "past the heap object -> non-canonical/unmapped -> page fault panic\n");
	}
	return 0;
}