DragonFlyBSD Kernel Audit
DF-1401 / trinity_nonclock_oob.c
← back to finding ↓ download raw
/*
 * DF-1401 harness: trinity_parse_power_table nonClockInfoIndex OOB read.
 *
 * Replicates the EXACT vulnerable logic from:
 *   sys/dev/drm/radeon/trinity_dpm.c:1774-1776
 *      non_clock_array_index = power_state->v2.nonClockInfoIndex;  // u8 from VBIOS, NO CHECK
 *      non_clock_info = (struct _ATOM_PPLIB_NONCLOCK_INFO *)
 *          &non_clock_info_array->nonClockInfo[non_clock_array_index];  // OOB read
 *
 * Contrast the CLOCK path at :1786-1790 which DOES guard:
 *      idx = (u8 *)&power_state->v2.clockInfoIndex[0];
 *      for (j ...) {
 *          clock_array_index = idx[j];
 *          if (clock_array_index >= clock_info_array->ucNumEntries) continue;  // guarded
 *          ...
 *      }
 * The non-clock index has no such check.
 *
 * With a crafted VBIOS where power_state->v2.nonClockInfoIndex (u8, 0..255) >=
 * ucNumEntries, the array index reads out of bounds. ucNumEntries=1 + index=255
 * reads 255 entries past the single-entry array. non_clock_info is then passed
 * to trinity_parse_pplib_non_clock_info() which reads its fields (clock flags,
 * etc.) -> OOB read of VBIOS/adjacent memory; stored into power state.
 *
 * This is a READ-ONLY primitive (the OOB data is read and stored, not written
 * through), so there is no escalation chain — the impact ceiling is OOB-info
 * read / wrong DPM settings (potential DoS via bogus clock programming). The
 * QEMU guest has no AMD Trinity GPU, so it is unreachable live here.
 *
 * Build:  cc -O2 -o trinity_nonclock_oob trinity_nonclock_oob.c
 * Run:    ./trinity_nonclock_oob
 */
#include <stdio.h>
#include <string.h>
#include <stdint.h>

#define MAX_STATES 16

struct _ATOM_PPLIB_NONCLOCK_INFO {
	uint32_t flags;
	uint32_t entry_size_dummy[3];
};

struct _NonClockInfoArray {
	uint8_t ucNumEntries;
	uint8_t ucEntrySize;
	struct _ATOM_PPLIB_NONCLOCK_INFO nonClockInfo[1];   /* VLA in reality */
};

struct pplib_power_state_v2 {
	uint8_t nonClockInfoIndex;   /* from VBIOS, unchecked */
	uint8_t ucNumDPMLevels;
};

int
main(void)
{
	/* A small VBIOS image region with a 1-entry nonClockInfoArray, followed by
	 * adjacent (attacker-uncontrolled-in-real-life) VBIOS bytes. */
	uint8_t bios[256];
	memset(bios, 0xEE, sizeof(bios));   /* poison so OOB reads are visible */

	struct _NonClockInfoArray *arr = (struct _NonClockInfoArray *)bios;
	arr->ucNumEntries = 1;
	arr->ucEntrySize  = sizeof(struct _ATOM_PPLIB_NONCLOCK_INFO);
	arr->nonClockInfo[0].flags = 0x12345678;

	/* Crafted power state: nonClockInfoIndex far past ucNumEntries. */
	struct pplib_power_state_v2 ps;
	ps.nonClockInfoIndex = 200;          /* u8, unchecked -> 200 vs 1 entry */
	ps.ucNumDPMLevels = 0;

	printf("DF-1401 trinity nonClockInfoIndex OOB-read demonstration\n");
	printf("nonClockInfoArray.ucNumEntries = %u\n", arr->ucNumEntries);

	/* ---- exact kernel logic (trinity_dpm.c:1774-1776) ---- */
	uint8_t non_clock_array_index = ps.nonClockInfoIndex;             /* line 1774: NO CHECK */
	struct _ATOM_PPLIB_NONCLOCK_INFO *non_clock_info =
	    &arr->nonClockInfo[non_clock_array_index];                    /* line 1775-1776: OOB */

	int oob = (non_clock_array_index >= arr->ucNumEntries);
	printf("nonClockInfoIndex (from VBIOS) = %u\n", non_clock_array_index);
	printf("guard present? : %s\n",
	    "NO (contrast clock path at :1789 which checks >= ucNumEntries)");
	printf("reads entry [%u] of a %u-entry array -> %s\n",
	    non_clock_array_index, arr->ucNumEntries, oob ? "OUT OF BOUNDS" : "in bounds");
	printf("OOB bytes past array start read: ~%lu\n",
	    (unsigned long)(non_clock_array_index * arr->ucEntrySize));
	printf("OOB-read flags value stored into power state: 0x%08x\n", non_clock_info->flags);

	if (oob) {
		printf("\nOOB READ CONFIRMED: nonClockInfoIndex=%u used against ucNumEntries=%u with no "
		       "bounds check; reads ~%lu bytes OOB from VBIOS into the power-state struct.\n",
		    non_clock_array_index, arr->ucNumEntries,
		    (unsigned long)(non_clock_array_index * arr->ucEntrySize));
		printf("READ-ONLY primitive -> no escalation; impact = wrong DPM settings / OOB info "
		       "read (DoS via bogus clock programming on crafted VBIOS).\n");
		return 0;
	}
	fprintf(stderr, "ERROR: OOB not observed\n");
	return 1;
}