⬢ DragonFlyBSD Kernel Audit
DF-3623 / ci_bootps_null.c
← back to finding ↓ download raw
/* DF-3623 — logic proof for the unvalidated boot_ps NULL dereference.
 *
 * Path: VBIOS power table without any ATOM_PPLIB_CLASSIFICATION_BOOT state
 *  -> ci_parse_pplib_non_clock_info (ci_dpm.c:5556-5577) never assigns
 *     adev->pm.dpm.boot_ps (only at :5573-5574 when the class bit is present)
 *  -> ci_dpm_enable (ci_dpm.c:5294-5297) fetches boot_ps = adev->pm.dpm.boot_ps
 *  -> ci_init_smc_table (ci_dpm.c:3704) amdgpu_boot_state = adev->pm.dpm.boot_ps
 *  -> ci_populate_smc_initial_state(adev, NULL) at ci_dpm.c:3786
 *  -> ci_get_ps(NULL) at ci_dpm.c:326-331: return rps->ps_priv;  // NULL deref
 *  -> boot_state->performance_levels[0].sclk at ci_dpm.c:2731    // 2nd NULL deref
 *  (also ci_update_current_ps(adev, NULL) at ci_dpm.c:5412 does *rps copies)
 *
 * This program emulates the exact deref chain with a NULL struct and traps
 * SIGSEGV, demonstrating fault address 0x8 (offsetof(ps_priv)) / 0x0-level.
 *
 * Build: cc -O2 -Wall -o ci_bootps_null ci_bootps_null.c && ./ci_bootps_null
 */
#include <stdio.h>
#include <stdint.h>
#include <signal.h>
#include <stddef.h>
#include <stdlib.h>
#include <stdbool.h>

struct amdgpu_ps { uint32_t caps, class, class2, vclk, dclk, evclk, ecclk;
		   bool vce_active; int vce_level; void *ps_priv; };
struct ci_pl { uint32_t mclk, sclk; int pcie_gen; uint16_t pcie_lane; };
struct ci_ps { uint16_t performance_level_count; bool dc; uint32_t sclk_t;
	       struct ci_pl performance_levels[2]; };

/* ci_dpm.c:326-331 */
static struct ci_ps *ci_get_ps(struct amdgpu_ps *rps)
{
	return rps->ps_priv;                 /* <- NULL->ps_priv deref */
}
/* ci_dpm.c:2722-2743 (abridged to the deref) */
static void ci_populate_smc_initial_state(struct amdgpu_ps *boot)
{
	struct ci_ps *boot_state = ci_get_ps(boot);
	printf("    boot_state=%p; reading boot_state->performance_levels[0].sclk ...\n",
	       (void *)boot_state);
	fflush(stdout);
	*(volatile uint32_t *)&boot_state->performance_levels[0].sclk;  /* ci_dpm.c:2731 */
}
/* ci_dpm.c:5556-5577 (abridged): only BOOT class sets boot_ps */
static struct amdgpu_ps *ci_parse_pplib_non_clock_info(uint16_t usClassification)
{
	static struct amdgpu_ps ps;         /* zeroed: ps_priv == NULL */
	ps.class = usClassification;
	if (ps.class & 0x0001 /* ATOM_PPLIB_CLASSIFICATION_BOOT */)
		return &ps;             /* would-be assignment target */
	return NULL;                        /* boot_ps remains NULL */
}

static void segv(int sig) {
	(void)sig;
	printf("    -> SIGSEGV at NULL+%d : in-kernel equivalent is a NULL-page\n"
	       "       dereference in ci_dpm_enable (kernel thread) -> PANIC\n",
	       (int)offsetof(struct amdgpu_ps, ps_priv));
	exit(0);
}

int main(void)
{
	signal(SIGSEGV, segv);
	struct amdgpu_ps *boot_ps = ci_parse_pplib_non_clock_info(0x0000 /* no BOOT bit */);
	printf("VBIOS state table without ATOM_PPLIB_CLASSIFICATION_BOOT:\n"
	       "  adev->pm.dpm.boot_ps = %p\n"
	       "  ci_dpm_enable:5297 boot_ps=%p -> ci_init_smc_table:3704 ->\n"
	       "  ci_populate_smc_initial_state:3786\n", (void *)boot_ps, (void *)boot_ps);
	ci_populate_smc_initial_state(boot_ps);
	return 0;
}