DragonFlyBSD Kernel Audit
DF-1296 / harness.c
← back to finding ↓ download raw
/*
 * DF-1296 harness: unchecked VBIOS UVD/VCE/ACP level counts overflow the
 * fixed-size SMC arrays in ci_populate_smc_uvd/vce/acp_level (ci_smumgr.c).
 *
 * Object-level proof. The ci (CIK / Bonaire/Hawaii) powerplay code runs only
 * on AMD Sea Islands GPUs; the audit QEMU guest has a QEMU std-vga
 * (chip 0x11111234), NOT an AMD GPU, so the path is not runtime-reachable
 * here. This harness replays the exact loop with a VBIOS-derived count that
 * exceeds the fixed array size.
 *
 * Cited path: sys/dev/drm/amd/powerplay/smumgr/ci_smumgr.c:1525 (UvdLevelCount
 *   = (u8)uvd_table->count), :1527 loop count<UvdLevelCount writes
 *   UvdLevel[count] (:1528+). VCE twin :1566/:1569, ACP twin :1598/:1601.
 * Arrays: UvdLevel[SMU7_MAX_LEVELS_UVD=8] (smu7_discrete.h:328, smu7.h:45),
 *   VceLevel[8] (:329), AcpLevel[8] (:330). count source: VBIOS
 *   ATOM_PPLIB numEntries (u8, no cap) parsed in processpptables.c:1097/1130/1189.
 *
 * Build: cc -O2 -o harness harness.c
 * Run:   ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define SMU7_MAX_LEVELS_UVD 8      /* smu7.h:45 */
#define SMU7_MAX_LEVELS_VCE 8      /* smu7.h:46 */
#define SMU7_MAX_LEVELS_ACP 8      /* smu7.h:47 */

/* Mirror of smu7_discrete.h:240 SMU7_Discrete_UvdLevel (16 bytes) */
struct SMU7_Discrete_UvdLevel {
	uint32_t VclkFrequency;
	uint32_t DclkFrequency;
	uint16_t MinVddc;
	uint8_t  MinVddcPhases;
	uint8_t  VclkDivider;
	uint8_t  DclkDivider;
	uint8_t  padding[3];
};

/* Mirror of smu7_discrete.h:253 SMU7_Discrete_ExtClkLevel (8 bytes) */
struct SMU7_Discrete_ExtClkLevel {
	uint32_t Frequency;
	uint16_t MinVoltage;
	uint8_t  MinPhases;
	uint8_t  Divider;
};

/* Minimal slice of SMU7_Discrete_DpmTable around UvdLevelCount/UvdLevel. The
 * real struct (smu7_discrete.h:300+) has VceLevel[8], AcpLevel[8], SamuLevel[8],
 * Ulv, SclkStepSize, Smio[], boot levels... immediately after UvdLevel[8] -- all
 * of which get clobbered by the overflow. The guard below stands in for that. */
struct DpmTable_slice {
	uint8_t  UvdLevelCount;
	uint8_t  VceLevelCount;
	uint8_t  AcpLevelCount;
	uint8_t  SamuLevelCount;
	uint8_t  pad[4];
	struct SMU7_Discrete_UvdLevel   UvdLevel[SMU7_MAX_LEVELS_UVD];
	struct SMU7_Discrete_ExtClkLevel VceLevel[SMU7_MAX_LEVELS_VCE];  /* adjacent victim */
	struct SMU7_Discrete_ExtClkLevel guard[64];
};

int main(void)
{
	struct DpmTable_slice *table = calloc(1, sizeof(*table));
	if (!table) { perror("calloc"); return 1; }
	memset(table->guard, 0xAA, sizeof(table->guard));

	printf("[DF-1296] UvdLevel[%d] (entry=%zu bytes), VceLevel[%d] adjacent\n",
	       SMU7_MAX_LEVELS_UVD, sizeof(struct SMU7_Discrete_UvdLevel),
	       SMU7_MAX_LEVELS_VCE);

	/* attacker: a VBIOS power table whose UVD numEntries (u8) exceeds 8.
	 * processpptables.c:1097 copies numEntries into uvd_table->count verbatim
	 * (no cap against SMU7_MAX_LEVELS_UVD). */
	uint8_t vbios_count = 40;                 /* >> SMU7_MAX_LEVELS_UVD(8) */
	printf("[DF-1296] attacker VBIOS UVD numEntries=%u (max array=%d)\n",
	       vbios_count, SMU7_MAX_LEVELS_UVD);

	/* === ci_smumgr.c:1525-1552 (the unbounded loop) === */
	table->UvdLevelCount = (uint8_t)vbios_count;          /* :1525 */
	uint8_t count;
	for (count = 0; count < table->UvdLevelCount; count++) {  /* :1527 */
		/* attacker-shaped fields from uvd_table->entries[count] :1528-1551 */
		table->UvdLevel[count].VclkFrequency  = 0x100000 + count;
		table->UvdLevel[count].DclkFrequency  = 0x200000 + count;
		table->UvdLevel[count].MinVddc        = 0x1000;
		table->UvdLevel[count].VclkDivider    = (uint8_t)count;
	}

	if (vbios_count > SMU7_MAX_LEVELS_UVD) {
		printf("[DF-1296] BUG CONFIRMED: loop wrote UvdLevel[0..%u] but only "
		       "UvdLevel[0..7] exists -> %u entries (%zu bytes) overflow past "
		       "UvdLevel into VceLevel/AcpLevel/SamuLevel/Ulv/Smio/...\n",
		       vbios_count-1, vbios_count - SMU7_MAX_LEVELS_UVD,
		       (size_t)(vbios_count - SMU7_MAX_LEVELS_UVD) *
		           sizeof(struct SMU7_Discrete_UvdLevel));
	}

	/* count clobbered victims (adjacent slab / sibling fields) */
	unsigned hit = 0;
	for (unsigned g = 0; g < 64; g++) {
		uint8_t *p = (uint8_t *)&table->VceLevel[g], *e = p + sizeof(table->VceLevel[g]);
		for (; p < e; p++) if (*p != 0x00 && *p != 0xAA) { hit++; break; }
		/* also flag VceLevel itself (in-bounds adjacent field, not the guard) */
	}
	/* specifically: did we corrupt VceLevel[0] (the immediate adjacent field)? */
	int vce_hit = memcmp(&table->VceLevel[0],
	                     (uint8_t[]){0,0,0,0,0,0,0,0}, 8) != 0;
	printf("[DF-1296] adjacent VceLevel[0] corrupted: %s ; guard region hit: "
	       "%u entries\n", vce_hit ? "YES (overflow into sibling field)" : "no",
	       hit);

	puts("\n--- Fix demonstration ---");
	/* clamp count to SMU7_MAX_LEVELS_UVD (and likewise VCE/ACP): */
	uint8_t clamped = vbios_count > SMU7_MAX_LEVELS_UVD ? SMU7_MAX_LEVELS_UVD
	                                                    : vbios_count;
	printf("[DF-1296] FIX: clamp UvdLevelCount %u -> %u -> loop stays in "
	       "UvdLevel[0..7], no overflow\n", vbios_count, clamped);

	free(table);
	return (vbios_count > SMU7_MAX_LEVELS_UVD) ? 0 : 2;
}