DragonFlyBSD Kernel Audit
DF-1402 / trinity_vce_oob.c
← back to finding ↓ download raw
/*
 * DF-1402 harness: trinity_parse_power_table VCE clk_idx OOB read.
 *
 * Replicates the EXACT vulnerable logic from:
 *   sys/dev/drm/radeon/trinity_dpm.c:1809-1813
 *      for (i = 0; i < RADEON_MAX_VCE_LEVELS; i++) {
 *          clock_array_index = rdev->pm.dpm.vce_states[i].clk_idx;  // 6-bit 0..63, NO CHECK
 *          clock_info = (union pplib_clock_info *)
 *              &clock_info_array->clockInfo[clock_array_index * ucEntrySize]; // OOB read
 *          sclk = le16_to_cpu(clock_info->sumo.usEngineClockLow);
 *          ...
 *      }
 *
 * clk_idx is a 6-bit field (0..63). With ucNumEntries=1 and clk_idx=63, the
 * index reads 63 entries past the single-entry clockInfo array: with
 * ucEntrySize ~16, that is ~1008 bytes OOB. The read value is stored into
 * vce_states[i].sclk -> wrong VCE engine clock (DoS / OOB info read).
 *
 * READ-ONLY primitive (no write-through), so no escalation chain — the ceiling
 * is OOB-info read / wrong clock programming. The QEMU guest has no AMD Trinity
 * GPU, so it is unreachable live here.
 *
 * Build:  cc -O2 -o trinity_vce_oob trinity_vce_oob.c
 * Run:    ./trinity_vce_oob
 */
#include <stdio.h>
#include <string.h>
#include <stdint.h>

#define RADEON_MAX_VCE_LEVELS 4

struct sumo_clock {
	uint16_t usEngineClockLow;
	uint8_t  ucEngineClockHigh;
};
union pplib_clock_info {
	struct sumo_clock sumo;
	uint8_t raw[16];
};

/* VERBATIM from sys/dev/drm/radeon/pptable.h:446-454:
 *   typedef struct _ClockInfoArray {
 *       UCHAR ucNumEntries;
 *       UCHAR ucEntrySize;
 *       UCHAR clockInfo[1];          <-- BYTE array (UCHAR), VLA in reality
 *   } ClockInfoArray;
 * So `clockInfo[idx * ucEntrySize]` is a BYTE offset (the kernel multiplies
 * by ucEntrySize precisely because clockInfo is a byte array). */
struct _ClockInfoArray {
	uint8_t ucNumEntries;
	uint8_t ucEntrySize;
	uint8_t clockInfo[1];
};

struct vce_state {
	uint8_t  clk_idx;     /* 6-bit 0..63 from VBIOS */
	uint32_t sclk;
};

int
main(void)
{
	uint8_t bios[2048];
	memset(bios, 0xDD, sizeof(bios));   /* poison for OOB visibility */

	struct _ClockInfoArray *arr = (struct _ClockInfoArray *)bios;
	arr->ucNumEntries = 1;
	arr->ucEntrySize  = 16;
	((union pplib_clock_info *)&arr->clockInfo[0])->sumo.usEngineClockLow = 0x1000;

	/* Crafted VCE states with clk_idx near the 6-bit max (63). */
	struct vce_state vce[RADEON_MAX_VCE_LEVELS] = {
		{ .clk_idx = 63 }, { .clk_idx = 63 }, { .clk_idx = 63 }, { .clk_idx = 63 },
	};

	printf("DF-1402 trinity VCE clk_idx OOB-read demonstration\n");
	printf("clockInfoArray.ucNumEntries = %u, ucEntrySize = %u\n",
	    arr->ucNumEntries, arr->ucEntrySize);

	int oob_count = 0;
	for (int i = 0; i < RADEON_MAX_VCE_LEVELS; i++) {
		/* ---- exact kernel logic (trinity_dpm.c:1811-1814) ----
		 * clockInfo is a UCHAR[] (byte array), so [idx*ucEntrySize] is a
		 * BYTE offset, then cast to union pplib_clock_info *. */
		uint8_t clock_array_index = vce[i].clk_idx;            /* line 1811: NO CHECK */
		union pplib_clock_info *clock_info =
		    (union pplib_clock_info *)
		    &arr->clockInfo[clock_array_index * arr->ucEntrySize];  /* line 1812-1813: OOB */
		uint32_t sclk = clock_info->sumo.usEngineClockLow;     /* line 1814 */
		sclk |= (uint32_t)clock_info->sumo.ucEngineClockHigh << 16;
		vce[i].sclk = sclk;

		int oob = (clock_array_index >= arr->ucNumEntries);
		if (oob) oob_count++;
		printf("  level %d: clk_idx=%u -> byte offset [%u*%u=%u] %s -> sclk=0x%x\n",
		    i, clock_array_index, clock_array_index, arr->ucEntrySize,
		    clock_array_index * arr->ucEntrySize,
		    oob ? "OOB" : "in-bounds", sclk);
	}

	printf("guard present? : NO (clock path at :1789 checks, this VCE loop does not)\n");
	if (oob_count > 0) {
		printf("\nOOB READ CONFIRMED: clk_idx=63 vs ucNumEntries=1 reads ~%u bytes OOB per level "
		       "(idx*ucEntrySize).\n", 63 * arr->ucEntrySize);
		printf("READ-ONLY primitive -> no escalation; impact = wrong VCE engine clock / OOB info "
		       "read (DoS via bogus clock programming on crafted VBIOS).\n");
		return 0;
	}
	fprintf(stderr, "ERROR: OOB not observed\n");
	return 1;
}