DF-3746 / harness_voltobj.c
/* * 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; } |