โฌข DragonFlyBSD Kernel Audit
DF-3746 / harness_voltobj.c
โ† back to finding โ†“ download raw
/*
 * DF-3746 harness: VoltageObjectInfo walkers trust self-declared sizes โ€” OOB heap reads
 * and a stride bug in v1
 *
 * Replicas from sys/dev/drm/radeon/radeon_atombios.c:
 *
 * (1) atom_lookup_voltage_object_v1() :3382-3397
 *       u32 size = usStructureSize (BIOS u16, unvalidated);
 *       while (offset < size) { vo = start + offset; ... ;
 *             offset += offsetof(...ucVIDAdjustEntries) + vo->...ucNumOfVoltageEntries; }
 *     a) the walk extends `size` bytes past the table โ€” size=0xFFFF walks ~64 KiB past a
 *        64-byte table (far past the rdev->bios image heap buffer);
 *     b) STRIDE BUG: v1 advances ucNumOfVoltageEntries BYTES while entries are
 *        VOLTAGE_LUT_ENTRY (4 bytes) โ€” compare v2 at :3410-3411 which correctly multiplies
 *        by sizeof(VOLTAGE_LUT_ENTRY); v1 mis-parses every following object.
 *
 * (2) radeon_atom_get_leakage_vddc_based_on_leakage_params() :3255-3280
 *       vddc_buf[j * ucElbVDDC_Num + i]  with j < ucLeakageBinNum, i < ucElbVDDC_Num
 *     both BIOS u8: index up to 254*255+254 = 65024 u16 elements = 130048 bytes past the
 *     array base โ€” despite the `size < sizeof(header)` check at :3237 (covers only the
 *     fixed part, not the arrays).
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32;

struct bios_image { u8 data[96]; u8 guard[256]; };	/* small image: table 64 B inside */
static unsigned long oob;
static u16 rd16(const struct bios_image *b, size_t off)
{
	if (off + 1 >= sizeof(b->data) + sizeof(b->guard)) { oob++; return 0; }
	if (off >= sizeof(b->data)) { oob++; return (u16)b->guard[off - sizeof(b->data)] |
		((u16)b->guard[off - sizeof(b->data) + 1] << 8); }
	return (u16)b->data[off] | ((u16)b->data[off + 1] << 8);
}

typedef struct { u16 usVoltageCode, usVoltageValue; } VOLTAGE_LUT_ENTRY;	/* 4 bytes */

int main(void)
{
	static struct bios_image img;
	memset(&img, 0x77, sizeof(img));

	/* ---------- (1a) self-declared size walk ---------- */
	u32 size = 0xFFF8;			/* usStructureSize from BIOS */
	u32 offset = (u32)8;			/* offsetof(...asVoltageObj[0]) model */
	u32 table_real = 64;			/* actual table bytes in image */
	u32 walked = 0, objs = 0;
	while (offset < size) {
		if (offset >= table_real) walked++;	/* object header read past table */
		u16 type = rd16(&img, offset);
		u8  entries = (u8)(type & 0xff);
		offset += 4 + entries;		/* offsetof(model)+ucNumOfVoltageEntries */
		objs++;
		if (objs > 200000) break;
	}
	printf("[v1 walk] self-declared size=0x%x vs real table=%u bytes (image=%zu): "
	       "walked %u objects, %u of them entirely past the table; %lu reads landed "
	       "past the image end\n",
	       size, table_real, sizeof(img.data), objs, walked, oob);

	/* ---------- (1b) stride bug ---------- */
	u8 n = 3;
	size_t v1_advance = 4 + n;			/* :3393-3394  (bytes) */
	size_t v2_advance = 4 + n * sizeof(VOLTAGE_LUT_ENTRY);	/* :3410-3411 (correct) */
	printf("[stride] v1 advances %zu bytes for %u entries; v2 advances %zu โ€” v1 lands "
	       "%zu bytes early on EVERY subsequent object (mis-parse / wrong object returned)\n",
	       v1_advance, n, v2_advance, v2_advance - v1_advance);

	/* ---------- (2) leakage array index math ---------- */
	u8 ucElbVDDC_Num = 255, ucLeakageBinNum = 254;
	size_t worst = (size_t)(ucLeakageBinNum - 1) * ucElbVDDC_Num + (ucElbVDDC_Num - 1);
	printf("[leakage] vddc_buf[%u*%u+%u] = element %zu of %zu-byte arrays -> %zu bytes "
	       "past array base (image is %zu bytes)\n",
	       ucLeakageBinNum - 1, ucElbVDDC_Num, ucElbVDDC_Num - 1,
	       worst, 2 * (size_t)ucElbVDDC_Num, worst * 2, sizeof(img.data));

	printf("modeled OOB u16 reads: %lu\n", oob);
	printf("REPRODUCED (harness): voltage-object walks read past table/image on "
	       "self-declared sizes and indices\n");
	return 0;
}