⬢ DragonFlyBSD Kernel Audit
DF-3888 / num_levels_underflow.c
← back to finding ↓ download raw
/*
 * DF-3888 harness — kv_dpm_get_sclk / kv_set_valid_clock_range num_levels==0
 * -> levels[num_levels - 1] wild read (+~48 GB).
 *
 * Mirrors sys/dev/drm/amd/amdgpu/kv_dpm.c:2929-2939 (get_sclk) and
 * :1791/:1817 (set_valid_clock_range) with 1:1 struct layouts (kv_dpm.h),
 * for a parsed power state whose ucNumDPMLevels==0 (or all clockInfoIndex
 * entries invalid) -> ps->num_levels stays 0 from kzalloc.
 *
 * Modes:
 *   ./t          accounting: prints the unsigned index and byte offset.
 *   ./t crash    executes the verbatim read against a single-page mapping
 *                -> read at levels + 0xFFFFFFFF*12 = +51539607540 -> SIGSEGV
 *                (kernel equivalent: page fault in kernel mode -> panic).
 *
 * Unprivileged trigger in the kernel: AMDGPU_INFO ioctl / AMDGPU_INFO_DEV_INFO
 * (amdgpu_kms.c:700, DRM_AUTH|DRM_RENDER_ALLOW) -> amdgpu_dpm_get_sclk(false)
 * -> kv_dpm_get_sclk; also dce_v8_0 modeset (dce_v8_0.c:971).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>

#define SUMO_MAX_HARDWARE_POWERLEVELS 5

struct kv_pl {
	uint32_t sclk;
	uint8_t  vddc_index;
	uint8_t  ds_divider_index;
	uint8_t  ss_divider_index;
	uint8_t  allow_gnb_slow;
	uint8_t  force_nbp_state;
	uint8_t  display_wm;
	uint8_t  vce_wm;
};

struct kv_ps {
	struct kv_pl levels[SUMO_MAX_HARDWARE_POWERLEVELS];
	uint32_t num_levels;
	uint8_t  need_dfs_bypass;
	uint8_t  dpm0_pg_nb_ps_lo, dpm0_pg_nb_ps_hi, dpmx_nb_ps_lo, dpmx_nb_ps_hi;
};

/* verbatim kv_dpm.c:2929-2939 shape */
static uint32_t kv_dpm_get_sclk(struct kv_ps *requested_state, int low)
{
	if (low)
		return requested_state->levels[0].sclk;
	else
		return requested_state->levels[requested_state->num_levels - 1].sclk;
}

/* verbatim kv_dpm.c:1790-1794 shape (kv_set_valid_clock_range high loop) */
static uint32_t kv_set_valid_clock_range_sink(struct kv_ps *new_ps)
{
	uint32_t i;

	for (i = 4; i > 0; i--) {           /* graphics_dpm_level_count-1 = 4 */
		if (i <= new_ps->levels[new_ps->num_levels - 1].sclk)
			break;
	}
	return new_ps->levels[new_ps->num_levels - 1].sclk;
}

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

	/* kzalloc'd kv_ps for a VBIOS state with zero valid clock levels:
	 * kv_parse_power_table loop (:2756-2769) never calls
	 * kv_parse_pplib_clock_info -> num_levels stays 0. */
	if (crash) {
		uint8_t *page = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
				     MAP_ANON | MAP_PRIVATE, -1, 0);
		if (page == MAP_FAILED) { perror("mmap"); return 1; }
		struct kv_ps *ps = (void *)page;
		ps->num_levels = 0;	/* kzalloc value, never set */

		printf("crash mode: levels at %p, reading levels[num_levels-1].sclk\n",
		       (void *)ps->levels);
		fflush(stdout);
		(void)kv_dpm_get_sclk(ps, 0);
		printf("NOT REACHED\n");
		return 0;
	}

	struct kv_ps ps;
	memset(&ps, 0, sizeof(ps));	/* num_levels = 0 */

	uint64_t idx = (uint64_t)(uint32_t)(ps.num_levels - 1);
	printf("num_levels = %u\n", ps.num_levels);
	printf("index used : levels[num_levels - 1] = levels[%llu]\n",
	       (unsigned long long)idx);
	printf("byte offset: %llu * sizeof(kv_pl)=%zu -> levels + %llu bytes (~%.1f GB)\n",
	       (unsigned long long)idx, sizeof(struct kv_pl),
	       (unsigned long long)(idx * sizeof(struct kv_pl)),
	       (double)(idx * sizeof(struct kv_pl)) / 1e9);
	printf("kernel sinks: kv_dpm.c:2938 (get_sclk, unprivileged via AMDGPU_INFO ioctl), kv_dpm.c:1791 and :1817 (set_valid_clock_range)\n");
	return 0;
}