DF-3852 / harness.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | /* * DF-3852 harness โ BIOS-controlled voltage-GPIO register index injected * into RREG32/WREG32 without range validation. * * Kernel source replicated 1:1 (logic, not hardware): * sys/dev/drm/radeon/radeon_combios.c:2739-2763 (VOLTAGE_GPIO record parse: * misc & 0x4 -> gpio.reg = RBIOS16(...) * 4 [16-bit BIOS word] * gpio.mask = (1 << tmp) [BIOS byte]) * sys/dev/drm/radeon/r100.c:352-364 r100_pm_misc() sink: * tmp = RREG32(voltage->gpio.reg); tmp |=/= mask; WREG32(reg, tmp); * sys/dev/drm/radeon/r100.c:4125-4138 r100_mm_{rreg,wreg}_slow(): * reg >= rmmio_size -> writel(reg, RADEON_MM_INDEX); MM_DATA (indexed) * * Demonstration: a COMBIOS powerplay table with misc bit 0x4 set and * gpio register word 0xFFFF yields gpio.reg = 0xFFFF*4 = 0x3FFFC, i.e. a * read-modify-write at MMIO offset 262140 โ 3x past the 64 KB radeon MMIO * BAR โ routed through the MM_INDEX indirection with the raw attacker * offset. In-range values (< 0x10000) still let the VBIOS pick ANY register * in the device space for a single-bit RMW. */ #include <stdio.h> #include <stdint.h> #include <stdbool.h> #include <string.h> /* ---- minimal rdev simulation ---------------------------------------- */ static uint8_t bios[512]; /* crafted COMBIOS image */ static uint32_t rmmio_sim[0x10000 / 4]; /* the real BAR is 64 KB */ #define RBIOS8(i) (bios[i]) #define RBIOS16(i) (RBIOS8(i) | (RBIOS8((i) + 1) << 8)) #define RMMIO_SIZE 0x10000 /* BAR length, radeon pre-AVIVO */ static unsigned long oob_indexed_writes, inbar_writes; /* r100.c:4125 r100_mm_rreg_slow / :4132 r100_mm_wreg_slow (MM_INDEX path) */ static uint32_t mm_rreg(uint32_t reg) { if (reg < RMMIO_SIZE) return rmmio_sim[reg / 4]; printf(" [slow] RREG32: MM_INDEX <- 0x%08x (>= rmmio_size 0x%x) " "-> indexed read OUTSIDE the MMIO BAR\n", reg, RMMIO_SIZE); return 0; } static void mm_wreg(uint32_t reg, uint32_t v) { if (reg < RMMIO_SIZE) { rmmio_sim[reg / 4] = v; inbar_writes++; } else { oob_indexed_writes++; printf(" [slow] WREG32: MM_INDEX <- 0x%08x (>= rmmio_size 0x%x) " "-> indexed write OUTSIDE the MMIO BAR, value 0x%08x\n", reg, RMMIO_SIZE, v); } } /* ---- radeon_combios.c:2720-2781, the powerplay VOLTAGE_GPIO parse ---- */ struct radeon_gpio { int valid; uint32_t reg, mask; }; static struct radeon_gpio voltage_gpio; static void combios_powerplay_voltage_parse(uint16_t offset) { uint8_t rev = RBIOS8(offset); uint16_t misc = RBIOS16(offset + 0x5); uint8_t tmp; if (!(misc & 0x4)) /* radeon_combios.c:2739 */ return; voltage_gpio.valid = true; /* :2747 */ if (rev < 6) { voltage_gpio.reg = RBIOS16(offset + 0x5 + 0xb) * 4; /* :2749-2750 */ tmp = RBIOS8(offset + 0x5 + 0xd); /* :2751 */ voltage_gpio.mask = (1 << tmp); /* :2752 */ } else { uint16_t vtoff = RBIOS16(offset + 0x5 + 0xc); /* :2755 */ voltage_gpio.reg = RBIOS16(vtoff) * 4; /* :2757-2758 */ tmp = RBIOS8(vtoff + 0x2); /* :2759 */ voltage_gpio.mask = (1 << tmp); /* :2760 */ } printf("[combios] VOLTAGE_GPIO parsed: gpio.reg=0x%08x gpio.mask=0x%08x " "(reg raw word 0x%04x * 4)\n", voltage_gpio.reg, voltage_gpio.mask, voltage_gpio.reg / 4); } /* ---- r100.c:345 r100_pm_misc() sink --------------------------------- */ static void r100_pm_misc(void) { uint32_t tmp; if (!voltage_gpio.valid) return; /* ATOM_PM_MISCINFO_VOLTAGE_DROP_SUPPORT == 0x4 == the same bit the * combios parser tested: one crafted word enables the whole path. */ tmp = mm_rreg(voltage_gpio.reg); tmp |= voltage_gpio.mask; /* active_high variant */ mm_wreg(voltage_gpio.reg, tmp); } int main(void) { uint16_t pp = 0x40; /* powerplay table offset in image */ /* crafted powerplay table, rev 5 (rev<6 branch) */ memset(bios, 0, sizeof(bios)); bios[pp + 0x0] = 5; /* rev */ bios[pp + 0x5] = 0x04; /* misc lo: bit2 -> VOLTAGE_GPIO */ bios[pp + 0x6] = 0x00; /* misc hi (also DROPSUPPORT=0x4) */ bios[pp + 0x5 + 0xb] = 0xff; /* gpio register word lo */ bios[pp + 0x5 + 0xc] = 0xff; /* gpio register word hi -> 0xFFFF*/ bios[pp + 0x5 + 0xd] = 0x09; /* mask bit 9 */ printf("== DF-3852: crafted COMBIOS powerplay voltage-GPIO record ==\n"); combios_powerplay_voltage_parse(pp); printf("[pm] r100_pm_misc() executing RREG32/WREG32 RMW at that index:\n"); r100_pm_misc(); printf("\nresult: %lu in-BAR write(s), %lu out-of-BAR indexed write(s)\n", inbar_writes, oob_indexed_writes); if (voltage_gpio.reg >= RMMIO_SIZE && oob_indexed_writes == 1) { printf("PRIMITIVE PROVEN: BIOS word 0x%x -> MMIO offset 0x%x " ">= rmmio_size 0x%x -> raw offset driven into MM_INDEX.\n", voltage_gpio.reg / 4, voltage_gpio.reg, RMMIO_SIZE); return 0; } fprintf(stderr, "unexpected\n"); return 1; } |