/*
 * DF-3954 — radeon kv_dpm.c num_levels==0 -> levels[0xFFFFFFFF] wild read
 * ========================================================================
 * Target: sys/dev/drm/radeon/kv_dpm.c  (radeon KV/Kaveri DPM, pass 2)
 *
 * Root cause (source-certain, hardware-gated):
 *   kv_parse_power_table() kv_dpm.c:2681-2694: for each power state, k only
 *   advances when a clockInfoIndex is < clock_info_array->ucNumEntries AND
 *   k < SUMO_MAX_HARDWARE_POWERLEVELS. A crafted VBIOS state with
 *   ucNumDPMLevels == 0 (or with every clockInfoIndex out of range) never
 *   calls kv_parse_pplib_clock_info(), so kzalloc'd ps->num_levels stays 0
 *   (struct kv_ps, kv_dpm.h:79-87: u32 num_levels, levels[] is FIRST member).
 *
 * Sinks (u32 arithmetic: 0 - 1 = 0xFFFFFFFF, sizeof(struct kv_pl) = 12):
 *   kv_set_valid_clock_range() kv_dpm.c:1721 and :1745-1747
 *       new_ps->levels[new_ps->num_levels - 1].sclk
 *       -> levels + 0xFFFFFFFF*12 = object + 0x2FFFFFFF4 (~12.9 GiB)
 *   kv_dpm_get_sclk() kv_dpm.c:2886 (non-low branch)
 *       requested_state->levels[requested_state->num_levels - 1].sclk
 *
 * Reachability: such a zero-level state is picked by
 *   radeon_dpm_pick_power_state() (radeon_pm.c:950-1051) whenever its class
 *   matches (e.g. PERFORMANCE) -> radeon_dpm_change_power_state_locked ->
 *   kv_dpm_pre_set_power_state -> kv_dpm_set_power_state ->
 *   kv_set_valid_clock_range (:1721). kv_dpm_get_sclk is called from
 *   dce8_bandwidth_update (cik.c:9287/9327) after that state became
 *   'requested'.
 *
 * This harness replicates the struct layout and the exact index expression in
 * userland: the object is mmap'd directly below a PROT_NONE guard region far
 * smaller than the 12.9 GiB computed displacement, so the dereference faults.
 * Arithmetic demonstration, NOT a kernel reproduction.
 *
 * Build: cc -O2 -Wall -o trigger_df3954 trigger_df3954.c
 * Run:   ./trigger_df3954 / ./trigger_df3954 crash
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <signal.h>

#define SUMO_MAX_HARDWARE_POWERLEVELS 5

struct kv_pl {			/* kv_dpm.h:70-78 — 12 bytes */
	unsigned int	sclk;
	unsigned char	vddc_index;
	unsigned char	ds_divider_index;
	unsigned char	ss_divider_index;
	unsigned char	allow_gnb_slow;
	unsigned char	force_nbp_state;
	unsigned char	display_wm;
	unsigned char	vce_wm;
};

struct kv_ps {			/* kv_dpm.h:79-87 — levels[] FIRST member */
	struct kv_pl	levels[SUMO_MAX_HARDWARE_POWERLEVELS];
	unsigned int	num_levels;	/* u32 */
	unsigned char	need_dfs_bypass;
	unsigned char	dpm0_pg_nb_ps_lo, dpm0_pg_nb_ps_hi;
	unsigned char	dpmx_nb_ps_lo, dpmx_nb_ps_hi;
};

_Static_assert(sizeof(struct kv_pl) == 12, "kv_pl layout drifted");

int main(int argc, char **argv)
{
	int crash = (argc > 1 && !strcmp(argv[1], "crash"));

	unsigned int bad32 = (unsigned int)0 - 1;	/* u32 num_levels(0) - 1 */
	unsigned long disp = (size_t)bad32 * sizeof(struct kv_pl);

	printf("DF-3954 radeon kv_dpm num_levels==0 wild read (simulation)\n");
	printf("sizeof(struct kv_pl) = %zu\n", sizeof(struct kv_pl));
	printf("num_levels(u32) = 0  =>  index = 0x%x  =>  displacement = 0x%lx (%.2f GiB)\n",
	       bad32, disp, disp / 1073741824.0);
	printf("kv_set_valid_clock_range :1721/:1745-1747 -> read at ps_obj + 0x%lx\n", disp);
	printf("kv_dpm_get_sclk          :2886           -> read at ps_obj + 0x%lx\n", disp);
	printf("kernel effect: unmapped/non-canonical address => page fault => panic\n");

	if (!crash) {
		printf("NON-CRASH MODE: analysis only. Re-run with 'crash' to execute the deref.\n");
		return 0;
	}

	/* Place a fake kv_ps just below a guard region; +12.9 GiB is far beyond
	 * the mapping -> SIGSEGV exactly like the kernel page fault. */
	const size_t map_sz = 4096;
	char *region = mmap(NULL, map_sz + 4096, PROT_READ | PROT_WRITE,
			    MAP_PRIVATE | MAP_ANON, -1, 0);
	if (region == MAP_FAILED) { perror("mmap"); return 1; }
	if (mprotect(region + map_sz, 4096, PROT_NONE) != 0) { perror("mprotect"); return 1; }
	struct kv_ps *ps = (struct kv_ps *)(region + map_sz - sizeof(struct kv_ps));
	memset(ps, 0, sizeof(*ps));
	ps->num_levels = 0;		/* crafted state: ucNumDPMLevels == 0 */

	pid_t p = fork();
	if (p == 0) {
		printf("child: executing 'ps->levels[ps->num_levels - 1].sclk' (kv_dpm.c:1721) ...\n");
		fflush(stdout);
		/* verbatim expression from kv_set_valid_clock_range() */
		unsigned int sclk = ps->levels[ps->num_levels - 1].sclk;
		printf("child: NOT REACHED (sclk=0x%x)\n", sclk);
		_exit(0);
	}
	int st;
	waitpid(p, &st, 0);
	printf("child: %s", WIFSIGNALED(st) ? "SIGNALLED" : "exited");
	if (WIFSIGNALED(st)) printf(" sig=%d", WTERMSIG(st));
	printf(" -> predicted wild read at +0x%lx confirmed faulting\n", disp);
	printf("RESULT: %s\n",
	       (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) ?
	       "WILD READ FAULTS AS PREDICTED" : "DEVIATION");
	return (WIFSIGNALED(st) && WTERMSIG(st) == SIGSEGV) ? 0 : 1;
}
