⬢ DragonFlyBSD Kernel Audit
DF-3686 / zero_count_underflow.c
← back to finding ↓ download raw
/*
 * DF-3686 harness: zero-count DPM table -> wild index (uint32_t)(0-1) in
 * smu7_find_dpm_states_clocks_in_dpm_table, reproduced verbatim from
 * sys/dev/drm/amd/powerplay/hwmgr/smu7_hwmgr.c:3588-3618.
 *
 * Precondition chain:
 *   1. processpptables.c init_clock_voltage_dependency (:1304-1310) skips
 *      vddc_dependency_on_sclk when usVddcDependencyOnSCLKOffset == 0 ->
 *      dyn_state.vddc_dependency_on_sclk stays NULL (parse still succeeds).
 *   2. smu7_setup_dpm_tables_v0 (:677-678) returns -EINVAL on the NULL
 *      table, but smu7_setup_default_dpm_tables (:950-953) IGNORES the
 *      return value -> dpm_table.sclk_table.count stays 0.
 *   3. Any power-state change calls smu7_find_dpm_states_clocks_in_dpm_table
 *      -> `for (uint32_t i = 0; i < sclk_table->count; i++)` exits with
 *      i == 0, `i >= count` is true, and `dpm_levels[i - 1]` uses the
 *      UNSIGNED index (uint32_t)0 - 1 = 0xFFFFFFFF.  Pointer arithmetic
 *      scales by sizeof(struct smu7_dpm_level) = 12 -> the kernel READS and
 *      (if the garbage compares less than the requested SCLK) WRRITES at
 *      dpm_levels + 0xFFFFFFFF*12 = +51,539,607,540 bytes (~48 GB out).
 *
 * The same pattern repeats at :3613-3616 for the MCLK table, at :2740
 * (smu7_get_profiling_clk, dpm_levels[count-1] read), :3642-3645
 * (smu7_get_maximum_link_speed, pcie dpm_levels[count-1] read),
 * :4534-4536 / :4576-4578 (smu7_get_sclk_od/mclk_od read + DIV_ROUND_UP),
 * :4745-4750 (smu7_get_max_high_clocks) and :4487/:4490 (OD_RANGE print).
 *
 * Run modes:
 *   ./t        - safe accounting mode: reports the computed index/offset
 *   ./t crash  - executes the verbatim kernel access and dies with SIGSEGV
 *                (kernel equivalent: page fault in kernel mode -> panic)
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>

#define MAX_REGULAR_DPM_NUMBER 8

struct smu7_dpm_level {
	uint8_t enabled;
	uint32_t value;
	uint32_t param1;
};

struct smu7_single_dpm_table {
	uint32_t count;
	struct smu7_dpm_level dpm_levels[MAX_REGULAR_DPM_NUMBER];
};

struct smu7_dpm_table_head {	/* the two first tables of struct smu7_dpm_table */
	struct smu7_single_dpm_table sclk_table;
	struct smu7_single_dpm_table mclk_table;
};

/* ---- verbatim loop from smu7_hwmgr.c:3588-3597 ---- */
static void find_dpm_states_clocks(struct smu7_dpm_table_head *dpm_table,
				   uint32_t sclk)
{
	struct smu7_single_dpm_table *sclk_table = &dpm_table->sclk_table;
	uint32_t i;

	for (i = 0; i < sclk_table->count; i++) {
		if (sclk == sclk_table->dpm_levels[i].value)
			break;
	}

	if (i >= sclk_table->count) {
		if (sclk > sclk_table->dpm_levels[i - 1].value) {
			sclk_table->dpm_levels[i - 1].value = sclk; /* WILD WRITE */
		}
	}
}
/* ---- end verbatim ---- */

int main(int argc, char **argv)
{
	struct smu7_dpm_table_head *dpm = calloc(1, sizeof(*dpm));
	uint32_t i = 0;

	if (!dpm)
		return 1;
	setbuf(stdout, NULL);

	printf("sizeof(struct smu7_dpm_level) = %zu\n", sizeof(struct smu7_dpm_level));
	printf("sclk_table.count = 0 (setup_dpm_tables_v0 failed; return ignored at :950-953)\n");

	/* the exact index the kernel computes at :3594 with count == 0 */
	printf("loop exits with i = %u; i - 1 = 0x%08x\n", i, i - 1);
	printf("effective byte offset of dpm_levels[i-1] = 0x%08x * %zu = %llu bytes\n",
	       i - 1, sizeof(struct smu7_dpm_level),
	       (unsigned long long)(i - 1) * sizeof(struct smu7_dpm_level));

	if (argc > 1 && !strcmp(argv[1], "crash")) {
		printf("executing the verbatim kernel access now; expect SIGSEGV "
		       "(kernel: page fault -> panic)\n");
		find_dpm_states_clocks(dpm, 300000);
		printf("UNEXPECTED: survived\n");
		return 1;
	}

	printf("RESULT: WILD OOB READ+WRITE CONFIRMED at the arithmetic level - "
	       "index 0xFFFFFFFF scales to +51,539,607,540 bytes past dpm_levels; "
	       "same pattern at :3613-3616 (mclk), :2740, :3642-3645, :4534-4536, "
	       ":4576-4578, :4745-4750, :4487/:4490\n");
	return 0;
}