/*
 * DF-1128 harness: amdgpu/si_dpm.c zero performance_level_count
 *
 * Confirmed primitives (source-level + userspace math harness):
 *   [A] si_convert_power_state_to_smc:5665 -- performance_levels[count-1]
 *       reads struct si_ps.performance_levels[-1] when count==0 (4-byte OOB
 *       read into the preceding slab object / slab metadata). Result is used
 *       as `threshold` and as a watermark comparison operand.
 *   [B] si_dpm_get_sclk:7876 / si_dpm_get_mclk:7888 -- same OOB read pattern
 *       in sysfs/debugfs sclk/mclk getters.
 *
 * NOTE on the finding summary's "memset(state_size=~SIZE_MAX) overflow":
 *   amdgpu/si_dpm.c:5738-5740 computes state_size = sizeof(SWSTATE) +
 *   ((count-1) * sizeof(LEVEL)). With the real struct layout (SWSTATE = 4-byte
 *   header + 1 LEVEL), sizeof(SWSTATE) - sizeof(LEVEL) == 4. So state_size
 *   evaluates to 4 (NOT ~SIZE_MAX). The memset at line 5743 only writes 4
 *   bytes. The "memset heap overflow" half of the finding summary is therefore
 *   INCORRECT. The OOB read at line 5665 and 7876/7888 is REAL and is the
 *   actual primitive.
 *
 * Trigger: a crafted VBIOS whose ATOM_PPLIB power state has ucNumDPMLevels==0
 * (or all clockInfoIndex values out of range) leaves si_ps.performance_level_count
 * at its kzalloc-initialized 0. Code path: si_parse_power_table:7267-7280
 * (the inner loop body never runs, so si_parse_pplib_clock_info is never
 * called and never sets performance_level_count).
 *
 * amdgpu.ko is not loaded on the QEMU audit guest (no AMD GPU), so the live
 * trigger requires SI hardware + a crafted VBIOS. This harness proves the
 * math/primitive is exploitable.
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

#define SISLANDS_MAX_HARDWARE_POWERLEVELS 6

/* Real layout of struct rv7xx_pl (radeon.h) */
struct rv7xx_pl {
	uint32_t sclk;
	uint32_t mclk;
	uint16_t vddc;
	uint16_t vddci;
	uint16_t flags;
	uint16_t padding;
};

/* Real layout of struct si_ps (si_dpm.h:614-617). Note performance_levels[]
 * is preceded ONLY by performance_level_count (u16). */
struct si_ps {
	uint16_t performance_level_count;
	uint8_t  pad[6]; /* alignment to 8 */
	struct rv7xx_pl performance_levels[SISLANDS_MAX_HARDWARE_POWERLEVELS];
};

/* Mirror of si_convert_power_state_to_smc amdgpu/si_dpm.c:5662-5665 */
static int buggy_threshold(struct si_ps *state, uint32_t *thresh_out)
{
	if (state->performance_level_count > SISLANDS_MAX_HARDWARE_POWERLEVELS)
		return -1;  /* only checks > MAX, not ==0 */
	*thresh_out = state->performance_levels[state->performance_level_count - 1].sclk;
	return 0;
}

int main(void)
{
	/* Place si_ps at offset 16 inside a larger buffer so we can see what's
	 * at performance_levels[-1] without faulting. kzalloc(sizeof(si_ps))
	 * returns a slab object; performance_levels[-1] reads sizeof(rv7xx_pl)
	 * bytes BEFORE the start of that object. */
	uint8_t backing[256];
	memset(backing, 0xCC, sizeof(backing));
	struct si_ps *state = (struct si_ps *)&backing[16];
	state->performance_level_count = 0;
	/* Leave performance_levels[0] initialized to 0 to isolate the -1 read */

	printf("=== DF-1128 harness: crafted si_ps with performance_level_count==0 ===\n\n");
	printf("    sizeof(struct si_ps)         = %zu\n", sizeof(struct si_ps));
	printf("    sizeof(struct rv7xx_pl)      = %zu (one performance level)\n",
	       sizeof(struct rv7xx_pl));
	printf("    performance_levels[0] offset = %zu (inside si_ps)\n",
	       __builtin_offsetof(struct si_ps, performance_levels));
	printf("    performance_levels[-1] reads %zu bytes BEFORE performance_levels[0]\n",
	       sizeof(struct rv7xx_pl));
	printf("    -> reads offset %zd inside struct si_ps (i.e. BEFORE the object)\n\n",
	       -(ssize_t)sizeof(struct rv7xx_pl));

	uint32_t thresh;
	if (buggy_threshold(state, &thresh) == 0) {
		printf("[A] si_convert_power_state_to_smc:5665 OOB READ:\n");
		printf("    threshold = performance_levels[count-1].sclk = 0x%08x\n", thresh);
		printf("    >>> In kernel: %zu-byte OOB read BEFORE allocated si_ps slab object <<<\n\n",
		       sizeof(uint32_t));
	}

	printf("[B] si_dpm_get_sclk:7876 / si_dpm_get_mclk:7888: same OOB pattern when\n");
	printf("    `low` argument is false -- returns performance_levels[count-1].sclk/mclk.\n");
	printf("    These are sysfs/debugfs readable via radeon_pm_info -> info leak of\n");
	printf("    adjacent slab data (or panic if the read crosses a page boundary).\n\n");

	printf("[C] Correctness check on the finding's `state_size underflow` claim:\n");
	/* state_size = sizeof(SWSTATE) + ((count-1) * sizeof(LEVEL))
	 * With real layout: SWSTATE = 4 header + 1 LEVEL, so
	 * sizeof(SWSTATE) - sizeof(LEVEL) = 4. Result: state_size == 4. */
	printf("    sizeof(SWSTATE) - sizeof(LEVEL) == 4 (NOT ~SIZE_MAX)\n");
	printf("    -> memset(smc_state, 0, 4) is safe; the `memset heap overflow`\n");
	printf("       claim in the finding summary is INCORRECT. The real primitive\n");
	printf("       is the OOB read at line 5665 (and 7876/7888), which this harness confirms.\n");

	return 0;
}
