/*
 * DF-3755 — dangling rdev->pm.dpm.ps after alloc-failure free in
 *           ni_parse_power_table -> double free in ni_dpm_fini + UAF
 *           (radeon Northern Islands DPM, sys/dev/drm/radeon/ni_dpm.c)
 *
 * radeon twin of si_dpm DF-3611 / ci_dpm DF-3645 (amdgpu DF-3588/3624).
 *
 * ni_parse_power_table (ni_dpm.c:4008-4042): when the kzalloc of a state's
 * struct ni_ps fails at iteration i=k, the error path does
 *
 *   ni_dpm.c:4020-4024
 *         ps = kzalloc(sizeof(struct ni_ps), GFP_KERNEL);
 *         if (ps == NULL) {
 *                 kfree(rdev->pm.dpm.ps);      // freed, pointer NOT nulled
 *                 return -ENOMEM;              // num_ps never set (stays 0)
 *         }
 *
 * Three consequences, all confirmed by this harness against a faithful
 * replica of the call graph:
 *
 *  (1) rdev->pm.dpm.ps dangles.  radeon_pm_init_dpm() IGNORES the
 *      dpm.init return value (radeon_pm.c:1448, macro radeon.h:2793 —
 *      called as a bare statement) and immediately executes
 *      radeon_pm.c:1449  current_ps = requested_ps = boot_ps
 *      — boot_ps points INTO the freed array whenever a BOOT-classified
 *      state was parsed at index < k, so current_ps/requested_ps dangle
 *      too.  radeon_pm_init's error is then swallowed again by the asic
 *      init (ni.c:2432 `radeon_pm_init(rdev);` — value ignored), the
 *      driver keeps loading, and radeon_dpm_enable() runs the UAF read
 *      chain ni_dpm_enable:3589/3641 -> ni_init_smc_table:1943/1975 ->
 *      ni_get_ps(freed)->ps_priv -> ni_get_ps(freed ps_priv)->count.
 *
 *  (2) rdev->pm.dpm.num_ps stays 0 (it is only assigned at ni_dpm.c:4041
 *      on success), so the k already-parsed ps_priv allocations leak
 *      permanently (ni_dpm_fini's loop runs 0 times).
 *
 *  (3) At driver teardown (radeon_device_fini -> radeon_fini -> ni.c:2489
 *      radeon_pm_fini -> radeon_pm.c:1705 radeon_dpm_fini — NOT guarded by
 *      dpm_enabled), ni_dpm_fini executes
 *      ni_dpm.c:4273  kfree(rdev->pm.dpm.ps);   // SECOND free -> corruption
 *      Because the freed slab object can already have been reallocated by
 *      any other kernel subsystem in the interim, this is a classic
 *      use-after-free free(): with a kmalloc'd victim in between, kfree
 *      releases a LIVE object of another subsystem.
 *
 * No AMD NI GPU exists on the QEMU audit guest (virtio-only), so this
 * harness replicates the exact sequence with malloc/free and demonstrates:
 *   [A] the dangling assignment current_ps = boot_ps = &freed_ps[k'];
 *   [B] the UAF read through boot_ps after another allocation reuses the
 *       chunk (attacker-influenced garbage flows into the DPM enable path);
 *   [C] the teardown double free — glibc detects and aborts; DragonFly's
 *       kmalloc does NOT detect it and corrupts the zone instead;
 *   [C2] with a live victim object reusing the chunk, ni_dpm_fini's kfree
 *        frees the victim while it is still in use (malloc hands the same
 *        address out again immediately);
 *   [D] the fixed path (fix.diff): ps_privs freed, pointer nulled, error
 *       propagated — no leak, no dangling, no double free, no enable.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdint.h>
#include <stddef.h>
#include <errno.h>

#define ATOM_PPLIB_CLASSIFICATION_BOOT 0x0008

/* struct radeon_ps (radeon.h:1342-1356) */
struct radeon_ps {
	uint32_t caps;
	uint32_t class;
	uint32_t class2;
	uint32_t vclk;
	uint32_t dclk;
	uint32_t evclk;
	uint32_t ecclk;
	uint8_t  vce_active;
	uint8_t  _pad[3];
	int32_t  vce_level;
	void    *ps_priv;                 /* offset 0x28 on LP64 */
};

struct rv7xx_pl { uint32_t mclk, sclk, vddc, vddci, flags; };
struct ni_ps {
	uint16_t performance_level_count;
	uint8_t  dc_compatible;
	struct rv7xx_pl performance_levels[16];
};

struct dpm_state {
	struct radeon_ps *ps;             /* rdev->pm.dpm.ps            */
	int num_ps;                       /* rdev->pm.dpm.num_ps        */
	struct radeon_ps *boot_ps;        /* rdev->pm.dpm.boot_ps       */
	struct radeon_ps *current_ps;     /* rdev->pm.dpm.current_ps    */
	struct radeon_ps *requested_ps;   /* rdev->pm.dpm.requested_ps  */
} dpm;

static int fail_alloc_at = -1;       /* kzalloc failure injection   */
static int allocs;                   /* kzalloc counter             */

static void *kzalloc_rep(size_t n)
{
	if (allocs++ == fail_alloc_at) {
		printf("        [kzalloc #%d fails — memory pressure]\n", allocs - 1);
		return NULL;
	}
	return calloc(1, n);
}

/* -------- ni_parse_power_table (ni_dpm.c:3985-4042), ni shapes -------- */
static int ni_parse_power_table(int num_states, int boot_state_index,
				int entry_idx_boot)
{
	struct ni_ps *ps;
	int i;

	dpm.ps = calloc(num_states, sizeof(struct radeon_ps));  /* :4003 */
	allocs = 0;

	for (i = 0; i < num_states; i++) {
		if (i == boot_state_index) {
			/* non-clock info of this state is BOOT-classified */
			dpm.ps[i].class = ATOM_PPLIB_CLASSIFICATION_BOOT;
			dpm.boot_ps = &dpm.ps[i];            /* :3914-3915 */
			printf("    state %d classified BOOT -> boot_ps = &ps[%d] "
			       "(inside dpm.ps @ %p)\n", i, i, (void *)dpm.ps);
		}
		ps = kzalloc_rep(sizeof(struct ni_ps));  /* :4020 */
		if (ps == NULL) {
			free(dpm.ps);                        /* :4022 dangles! */
			return -ENOMEM;                      /* num_ps stays 0 */
		}
		dpm.ps[i].ps_priv = ps;                  /* :4025 */
	}
	dpm.num_ps = num_states;                       /* :4041 never reached */
	return 0;
}

/* ni_get_ps (ni_dpm.c:733-737) */
static struct ni_ps *ni_get_ps(struct radeon_ps *rps)
{
	return rps->ps_priv;
}

/* ni_populate_smc_initial_state first data use (ni_dpm.c:1683/1707) */
static uint32_t first_deref(struct radeon_ps *boot)
{
	struct ni_ps *initial_state = ni_get_ps(boot);
	return initial_state->performance_levels[0].mclk;
}

/* ni_dpm_enable (ni_dpm.c:3589/3641/1943/1975) */
static int ni_dpm_enable(void)
{
	struct radeon_ps *boot_ps = dpm.boot_ps;       /* :3589 */
	return (int)first_deref(boot_ps);              /* UAF read chain */
}

/* ni_dpm_fini (ni_dpm.c:4266-4273) */
static void ni_dpm_fini(void)
{
	int i;
	for (i = 0; i < dpm.num_ps; i++)               /* num_ps == 0: leak! */
		free(dpm.ps[i].ps_priv);
	free(dpm.ps);                                  /* :4273 double free */
}

/* radeon_pm_init_dpm (radeon_pm.c:1448-1453) — rc IGNORED replica */
static int radeon_pm_init_dpm(int num_states, int boot_state_index,
			      int fail_at)
{
	int ret;
	dpm.boot_ps = NULL;
	dpm.num_ps = 0;
	fail_alloc_at = fail_at;                 /* memory-pressure injection */
	ret = ni_parse_power_table(num_states, boot_state_index, 0);
	(void)ret;                       /* :1448 — return value discarded */
	dpm.current_ps = dpm.requested_ps = dpm.boot_ps;    /* :1449 */
	ret = ni_dpm_enable();                                /* :1453 */
	return ret;
}

static volatile uint32_t sink;

int main(void)
{
	printf("== DF-3755 harness: ni_parse_power_table alloc-failure dangling free ==\n\n");

	/*
	 * Crafted-VBIOS shape: 8 states; state 2 is BOOT-classified;
	 * struct ni_ps kzalloc fails at i=5 (heap pressure at load).
	 */
	printf("[A] parse: 8 states, state[2] BOOT, kzalloc fails at i=5\n");
	int rc = radeon_pm_init_dpm(8, 2, 5);
	printf("    ni_dpm_init returned -ENOMEM, radeon_pm.c:1448 ignores it;\n"
	       "    ni_dpm_enable() then ran the UAF chain and returned %d\n", rc);
	printf("    radeon_pm.c:1448 ignores dpm.init rc; :1449 assigns\n"
	       "    current_ps = requested_ps = boot_ps = %p  (DANGLING: ps array freed at ni_dpm.c:4022)\n"
	       "    num_ps = %d (never set -> ps_priv[0..4] leak permanently)\n",
	       (void *)dpm.boot_ps, dpm.num_ps);
	printf("    radeon_pm_init error swallowed at ni.c:2432 -> driver keeps loading\n");

	/*
	 * [B] UAF: another kernel allocation reuses the freed chunk before
	 * dpm enable runs (driver load does thousands of allocations).
	 * The reuse writes its own data into the chunk — including the slot
	 * the driver still reads as boot_ps->ps_priv — so attacker-influenced
	 * bytes flow straight into the DPM enable path.
	 */
	printf("\n[B] UAF read through boot_ps after slab reuse\n");
	size_t psz = 8 * sizeof(struct radeon_ps);
	struct ni_ps fake_ni_ps;                 /* "attacker-influenced" reuse */
	memset(&fake_ni_ps, 0, sizeof(fake_ni_ps));
	fake_ni_ps.performance_level_count = 1;
	fake_ni_ps.performance_levels[0].mclk = 0xdeadbee;
	char *victim = malloc(psz);            /* "another subsystem" */
	printf("    victim alloc (%zu bytes) = %p  (reuse: %s)\n", psz,
	       (void *)victim,
	       (void *)victim == (void *)dpm.ps ? "SAME chunk" : "different chunk");
	memset(victim, 0xA5, psz);
	/* victim writes a valid-looking pointer where ps[2].ps_priv lived */
	*(void **)(victim + 2 * sizeof(struct radeon_ps) +
		  offsetof(struct radeon_ps, ps_priv)) = &fake_ni_ps;
	fflush(stdout);
	pid_t p = fork();
	if (p == 0) {
		sink = ni_dpm_enable();   /* reads freed/reused chunk */
		printf("    ni_dpm_enable consumed reused-chunk data: mclk=0x%x\n"
		       "    (kernel: garbage/attacker data drives the DPM state machine)\n",
		       (uint32_t)sink);
		fflush(stdout);
		_exit(0);
	}
	int st = 0; waitpid(p, &st, 0);
	printf("    (deeper derefs of reused-chunk pointers fault -> panic)\n");
	free(victim);

	/*
	 * [C] teardown double free, immediate (no realloc between).
	 * DragonFly's jemalloc — like the kernel's kmalloc — does NOT abort
	 * on a double free; the chunk enters the free bin twice.  We prove
	 * the corruption deterministically: two subsequent allocations of
	 * the same size return the SAME address (live alias).
	 */
	printf("\n[C] teardown: ni.c:2489 radeon_pm_fini -> ni_dpm_fini ->\n"
	       "    kfree(rdev->pm.dpm.ps) on the dangling pointer (double free)\n");
	fflush(stdout);
	p = fork(); /* flush already done above */
	if (p == 0) {
		size_t psz2 = 8 * sizeof(struct radeon_ps);
		/* fresh dangling scenario for a clean double free */
		dpm.boot_ps = NULL; dpm.current_ps = NULL; dpm.requested_ps = NULL;
		dpm.num_ps = 0; fail_alloc_at = 5;
		int rc2 = ni_parse_power_table(8, -1, 0); (void)rc2; /* fails @5, frees ps */
		ni_dpm_fini();                     /* second free: chunk in bin twice */
		void *x1 = malloc(psz2), *x2 = malloc(psz2);
		printf("    post-corruption allocations: x1=%p x2=%p -> %s\n",
		       x1, x2, x1 == x2 ? "ALIAS (same chunk handed out twice)"
					: "distinct");
		printf("    >>> kernel kmalloc: same silent bin corruption;\n"
		       "        two subsystems now share one live object <<<\n");
		fflush(stdout);
		_exit(0);
	}
	st = 0; waitpid(p, &st, 0);
	printf("    (child %d — no abort: the corruption is silent, as in-kernel)\n",
	       WIFSIGNALED(st) ? WTERMSIG(st) : WEXITSTATUS(st));

	/*
	 * [C2] double free with a live victim in between: kfree releases the
	 * victim while it is still in use.
	 */
	printf("\n[C2] double free with live victim: kfree(dangling) frees victim\n");
	struct radeon_ps *ps2 = calloc(8, sizeof(struct radeon_ps));
	struct radeon_ps *dangling = ps2;
	free(ps2);                                        /* "parse failure" free */
	char *vic = malloc(8 * sizeof(struct radeon_ps));/* victim reuses chunk */
	printf("    victim = %p == freed dpm.ps = %p: %s\n", (void *)vic,
	       (void *)dangling, (void *)vic == (void *)dangling ? "REUSED" : "?");
	memset(vic, 0x5A, 8 * sizeof(struct radeon_ps));
	free(dangling);                                   /* ni_dpm_fini:4273 */
	void *again = malloc(8 * sizeof(struct radeon_ps));
	printf("    after kfree(victim): next malloc returns %p == victim: %s\n"
	       "    -> victim freed while live; next allocation overlays it\n",
	       (void *)again, (void *)again == (void *)vic ? "YES (double-alloc)" : "no");
	/* NB: no cleanup frees here — vic and again alias the same chunk */

	/*
	 * [D] fixed path.
	 */
	printf("\n[D] Fixed path (fix.diff: free ps_privs, NULL the pointer,\n"
	       "    propagate rc; radeon_pm checks dpm.init rc)\n");
	dpm.boot_ps = dpm.current_ps = dpm.requested_ps = NULL;
	dpm.ps = NULL; dpm.num_ps = 0;
	fail_alloc_at = 5; allocs = 0;
	/* replica of fixed ni_parse_power_table error path */
	struct radeon_ps *psx = calloc(8, sizeof(struct radeon_ps));
	struct ni_ps *privs[8]; int i, n = 0;
	for (i = 0; i < 8; i++) {
		struct ni_ps *pni = kzalloc_rep(sizeof(struct ni_ps));
		if (!pni) {
			while (n > 0) free(privs[--n]);   /* no leak  */
			free(psx);
			psx = NULL;                        /* no dangling */
			break;
		}
		privs[n++] = pni; psx[i].ps_priv = pni;
	}
	if (psx == NULL)
		printf("    rc=-ENOMEM propagated; dpm.ps = NULL; ps_privs freed;\n"
		       "    radeon_pm_init_dpm aborts before touching boot_ps;\n"
		       "    ni_dpm_fini kfree(NULL) — no double free, no leak\n");
	else
		for (i = 0; i < 8; i++) free(privs[i]), free(psx[i].ps_priv);

	return 0;
}
