/*
 * DF-4545 — vega20 pptable init succeeds with NULL OD arrays
 * ==========================================================
 * Lifted-copy harness (DF-1179/DF-4395 convention; the in-kernel path needs
 * a Vega20 ASIC, absent on the audit guest).
 *
 * Faithful replica of:
 *   - check_powerplay_tables (vega20_processpptables.c:636-657)
 *   - init_powerplay_table_information (:802-892): the
 *     `ucODTableRevision == 1` gate (:823) around ALL od_* copies, the five
 *     copy calls whose return values are IGNORED (:835-846, :870-877), the
 *     smc_pptable kmalloc (:880) and memcpy (:884) that unconditionally
 *     succeed, and the `return result` of append_vbios_pptable (:889-891)
 *     which is 0 -> init reports SUCCESS
 *   - the consumer's first deref in vega20_od8_set_feature_capabilities
 *     (vega20_hwmgr.c:906): pptable_information->od_feature_capabilities[0]
 *
 * Variant A: crafted VBIOS with OverDrive8Table.ucODTableRevision = 2
 *            (a perfectly legal value the driver does not recognise).
 * Variant B: revision 1 but a kzalloc failure inside
 *            copy_overdrive_feature_capabilities_array whose -ENOMEM is
 *            dropped on the floor at :835.
 *
 * Both leave hwmgr->pptable->od_feature_capabilities == NULL while
 * vega20_pp_tables_initialize returns 0; backend_init then runs
 * vega20_od8_initialize_default_settings (vega20_hwmgr.c:1598) which
 * dereferences the NULL pointer -> kernel panic. The harness catches the
 * resulting SIGSEGV and reports the fault address.
 *
 * Build: cc -O2 -Wall -o od_null_deref od_null_deref.c
 * Run:   ./od_null_deref
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <signal.h>
#include <ucontext.h>
#include <unistd.h>
#include <errno.h>
#include <sys/wait.h>

typedef uint8_t  UCHAR;
typedef uint16_t USHORT;
typedef uint32_t ULONG;

#define ATOM_VEGA20_TABLE_REVISION_VEGA20	11
#define ATOM_VEGA20_ODFEATURE_COUNT		10
#define PPTABLE_V20_SMU_VERSION			3

#pragma pack(push, 1)
struct atom_common_table_header {
	USHORT structuresize;
	UCHAR  format_revision;
	UCHAR  content_revision;
};

typedef struct _ATOM_VEGA20_OVERDRIVE8_RECORD {
	UCHAR ucODTableRevision;
	ULONG ODFeatureCount;
	UCHAR ODFeatureCapabilities[32];
	ULONG ODSettingCount;
	ULONG ODSettingsMax[32];
	ULONG ODSettingsMin[32];
} ATOM_VEGA20_OVERDRIVE8_RECORD;

typedef struct _ATOM_VEGA20_POWER_SAVING_CLOCK_RECORD {
	UCHAR ucTableRevision;
	ULONG PowerSavingClockCount;
	ULONG PowerSavingClockMax[16];
	ULONG PowerSavingClockMin[16];
} ATOM_VEGA20_POWER_SAVING_CLOCK_RECORD;

typedef struct _PPTable_t_stub {
	ULONG Version;
	UCHAR pad[2044];
} PPTable_t_stub;

typedef struct _ATOM_Vega20_POWERPLAYTABLE {
	struct atom_common_table_header sHeader;
	UCHAR  ucTableRevision;
	USHORT usTableSize;
	ULONG  ulGoldenPPID;
	ULONG  ulGoldenRevision;
	USHORT usFormatID;
	ULONG  ulPlatformCaps;
	UCHAR  ucThermalControllerType;
	USHORT usSmallPowerLimit1;
	USHORT usSmallPowerLimit2;
	USHORT usBoostPowerLimit;
	USHORT usODTurboPowerLimit;
	USHORT usODPowerSavePowerLimit;
	USHORT usSoftwareShutdownTemp;
	ATOM_VEGA20_POWER_SAVING_CLOCK_RECORD PowerSavingClockTable;
	ATOM_VEGA20_OVERDRIVE8_RECORD OverDrive8Table;
	USHORT usReserve[5];
	PPTable_t_stub smcPPTable;
} ATOM_Vega20_POWERPLAYTABLE;
#pragma pack(pop)

struct phm_ppt_v3_information {
	uint8_t  *od_feature_capabilities;
	uint32_t *od_settings_max;
	uint32_t *od_settings_min;
};

static int inject_alloc_failure;	/* variant B */
static int patched;			/* PATCHED control (fix.diff) */

static volatile unsigned long fault_addr;

static void sigsegv(int sig, siginfo_t *si, void *uc)
{
	char msg[96];
	int len;
	(void)sig; (void)uc;
	fault_addr = (unsigned long)si->si_addr;
	/* async-signal-safe report of the fault address */
	len = snprintf(msg, sizeof(msg),
	    "  >>> SIGSEGV: fault address 0x%lx (od_feature_capabilities + 0)"
	    "\n", fault_addr);
	write(1, msg, len);
	_exit(1);				/* 1 = NULL-deref faulted */
}

/* copy_overdrive_feature_capabilities_array (:684-712) */
static int copy_overdrive_feature_capabilities_array(
		struct phm_ppt_v3_information *pi,
		const UCHAR *pptable_array, uint32_t od_feature_count)
{
	uint8_t *table;

	if (inject_alloc_failure)
		return -ENOMEM;			/* kzalloc failed */
	table = calloc(1, od_feature_count);
	if (!table)
		return -ENOMEM;
	for (uint32_t i = 0; i < od_feature_count; i++)
		table[i] = pptable_array[i];
	pi->od_feature_capabilities = table;
	return 0;
}

static uint32_t le32(uint32_t v) { return v; }	/* LE host stand-in */

/* check_powerplay_tables (:636-657) */
static int check_powerplay_tables(const ATOM_Vega20_POWERPLAYTABLE *t)
{
	if (t->sHeader.format_revision < ATOM_VEGA20_TABLE_REVISION_VEGA20) {
		printf("  check: unsupported format_revision\n");
		return -1;
	}
	if (t->sHeader.structuresize <= 0) {
		printf("  check: invalid structuresize\n");
		return -1;
	}
	if (t->smcPPTable.Version != PPTABLE_V20_SMU_VERSION) {
		printf("  check: PPTable version mismatch\n");
		return -1;
	}
	printf("  check_powerplay_tables: PASS (format_revision=%u, "
	    "structuresize=%u, smcPPTable.Version=%u)\n",
	    t->sHeader.format_revision, t->sHeader.structuresize,
	    t->smcPPTable.Version);
	return 0;
}

/* init_powerplay_table_information (:802-892) */
static int init_powerplay_table_information(
		struct phm_ppt_v3_information *pi,
		const ATOM_Vega20_POWERPLAYTABLE *powerplay_table)
{
	uint32_t od_feature_count, od_setting_count;
	int result;

	/* :823 */
	if (powerplay_table->OverDrive8Table.ucODTableRevision == 1) {
		od_feature_count =
			(le32(powerplay_table->OverDrive8Table.ODFeatureCount) >
			 ATOM_VEGA20_ODFEATURE_COUNT) ?
			ATOM_VEGA20_ODFEATURE_COUNT :
			le32(powerplay_table->OverDrive8Table.ODFeatureCount);
		od_setting_count = 14;
		/* :835 -- return value IGNORED (CWE-252) */
		copy_overdrive_feature_capabilities_array(pi,
		    powerplay_table->OverDrive8Table.ODFeatureCapabilities,
		    od_feature_count);
		/* :839-846 phm_copy_overdrive_settings_limits_array x2,
		 * returns equally ignored */
		(void)od_setting_count;
	}

	if (patched) {
		/* fix.diff: PP_ASSERT(od_feature_capabilities != NULL && ...)
		 * -> init fails instead of reporting success */
		if (pi->od_feature_capabilities == NULL ||
		    pi->od_settings_max == NULL) {
			printf("  init_powerplay_table_information: PATCHED "
			    "init refuses to succeed (returns -EINVAL)\n");
			return -22;		/* -EINVAL */
		}
	}
	/* :880-886 -- smc_pptable alloc+copy always succeed here */
	/* :889 */
	result = 0;				/* append_vbios_pptable OK */
	printf("  init_powerplay_table_information: return %d "
	    "(od_feature_capabilities=%p od_settings_max=%p od_settings_min=%p)"
	    "\n", result, (void *)pi->od_feature_capabilities,
	    (void *)pi->od_settings_max, (void *)pi->od_settings_min);
	return result;				/* SUCCESS with NULLs */
}

/* vega20_od8_set_feature_capabilities first deref (vega20_hwmgr.c:906) */
static void consumer(struct phm_ppt_v3_information *pi)
{
	/* data->smu_features[GNLD_DPM_GFXCLK].enabled is true on real HW;
	 * the very first array access is od_feature_capabilities[0] */
	printf("  consumer: vega20_od8_set_feature_capabilities: reading "
	    "od_feature_capabilities[0] at %p ...\n",
	    (void *)pi->od_feature_capabilities);
	if (pi->od_feature_capabilities[0])	/* <- faults when NULL */
		printf("  cap[0] set\n");
}

static struct sigaction sa;

static int run_case(const char *name, UCHAR od_rev, int alloc_fail,
		int expect_fault)
{
	ATOM_Vega20_POWERPLAYTABLE *t;
	struct phm_ppt_v3_information pi = { NULL, NULL, NULL };
	int pid, status, ret;

	printf("=== %s (ucODTableRevision=%u, inject kzalloc failure=%d) ===\n",
	    name, od_rev, alloc_fail);

	t = calloc(1, sizeof(*t));
	t->sHeader.structuresize = sizeof(*t);
	t->sHeader.format_revision = ATOM_VEGA20_TABLE_REVISION_VEGA20;
	t->OverDrive8Table.ucODTableRevision = od_rev;
	t->OverDrive8Table.ODFeatureCount = ATOM_VEGA20_ODFEATURE_COUNT;
	t->OverDrive8Table.ODSettingCount = 14;
	memset(t->OverDrive8Table.ODFeatureCapabilities, 1, 32);
	t->smcPPTable.Version = PPTABLE_V20_SMU_VERSION;

	inject_alloc_failure = alloc_fail;

	patched = (strstr(name, "PATCHED") != NULL);
	if ((pid = fork()) < 0) { perror("fork"); exit(2); }
	if (pid == 0) {				/* child runs the flow */
		sigaction(SIGSEGV, &sa, NULL);
		if (check_powerplay_tables(t) != 0)
			_exit(10);
		if (init_powerplay_table_information(&pi, t) != 0)
			_exit(11);
		printf("  pptable_init returned SUCCESS, running "
		    "backend_init consumer ...\n");
		consumer(&pi);
		_exit(0);			/* clean completion */
	}
	waitpid(pid, &status, 0);
	ret = WEXITSTATUS(status);
	if (ret == 1) {
		printf("  >>> consumer NULL-DEREF confirmed (see SIGSEGV line "
		    "above)\n");
	} else if (ret == 0) {
		printf("  >>> consumer completed without fault\n");
	} else {
		printf("  >>> flow bailed early (exit %d)\n", ret);
	}
	printf("  %s\n\n", ret == 1 ? "PANIC-CLASS FAULT REPRODUCED" :
	    (expect_fault ? "NOT REPRODUCED" : "OK (no fault, as expected)"));
	free(t);
	return ret == 1;
}

int main(void)
{
	memset(&sa, 0, sizeof(sa));
	sa.sa_sigaction = sigsegv;
	sa.sa_flags = SA_SIGINFO;
	sigfillset(&sa.sa_mask);

	printf("DF-4545 lifted-copy harness: vega20 pptable init succeeds with "
	    "NULL OD arrays -> vega20_hwmgr.c:906 NULL deref\n\n");

	/* control: revision 1, allocations succeed -> clean */
	run_case("CONTROL rev=1 healthy alloc", 1, 0, 0);
	/* variant A: crafted table, revision != 1 -> arrays never copied */
	int a = run_case("VARIANT A crafted ucODTableRevision=2", 2, 0, 1);
	/* variant B: rev=1 but the :835 kzalloc fails, -ENOMEM ignored */
	int b = run_case("VARIANT B rev=1 ignored -ENOMEM", 1, 1, 1);
	/* patched: same crafted input, init now refuses -> consumer never runs */
	int p = run_case("PATCHED crafted rev=2", 2, 0, 0);
	patched = 0; (void)p;

	printf("%s: init returns success with NULL od arrays%s\n",
	    (a || b) ? "REPRODUCED" : "NOT REPRODUCED",
	    (a && b) ? " (both the revision gate and the dropped -ENOMEM)" :
	    "");
	return (a || b) ? 0 : 1;
}
