DF-1306 / 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 129 130 131 132 133 134 135 136 137 | /* * DF-1306 harness โ kv_parse_power_table nonClockInfoIndex OOB heap read * * Reproduces the vulnerable access pattern of * sys/dev/drm/radeon/kv_dpm.c:2668-2670 * in userspace. * * The kernel function kv_parse_power_table() parses the powerplay tables from * the GPU VBIOS. At line 2668-2670: * * non_clock_array_index = power_state->v2.nonClockInfoIndex; // u8 0..255 VBIOS * non_clock_info = (struct _ATOM_PPLIB_NONCLOCK_INFO *) * &non_clock_info_array->nonClockInfo[non_clock_array_index]; * * `non_clock_array_index` comes straight from the VBIOS (ATOM_PPLIB_STATE_V2. * nonClockInfoIndex, a u8). It is used to index nonClockInfo[] with NO bounds * check against non_clock_info_array->ucNumEntries. Contrast: the CLOCK path * at line 2683 DOES check `if (clock_array_index >= ucNumEntries) continue;`. * * nonClockInfo is flex[1] of 24-byte ATOM_PPLIB_NONCLOCK_INFO entries * (pptable.h:298-309,456-464). nonClockInfoIndex=255 reads at byte offset * 255*24 = 6120 past nonClockInfo[0] -> heap OOB read. * * Build: cc -O2 -Wall -o harness harness.c * Run: ./harness * * Proof strategy: place nonClockInfo[0] at the very end of a page-backed * region with the next page unmapped; set nonClockInfoIndex=255 so the read * at nonClockInfo[255] faults on the unmapped page (in-kernel: heap OOB read * off the kmalloc'd bios buffer). */ #include <stdio.h> #include <stdint.h> #include <stddef.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <setjmp.h> #include <unistd.h> #include <sys/mman.h> static sig_atomic_t got_fault = 0; static jmp_buf jb; static void fault_handler(int sig) { (void)sig; got_fault = sig; longjmp(jb, 1); } typedef uint8_t UCHAR; typedef uint16_t USHORT; typedef uint32_t ULONG; /* ATOM_PPLIB_NONCLOCK_INFO, pptable.h:298-309 โ 24 bytes */ typedef struct { USHORT usClassification; /* 2 */ UCHAR ucMinTemperature; /* 1 */ UCHAR ucMaxTemperature; /* 1 */ ULONG ulCapsAndSettings; /* 4 */ UCHAR ucRequiredPower; /* 1 */ USHORT usClassification2; /* 2 */ ULONG ulVCLK; /* 4 */ ULONG ulDCLK; /* 4 */ UCHAR ucUnused[5]; /* 5 = 24 total */ } ATOM_PPLIB_NONCLOCK_INFO; /* _NonClockInfoArray, pptable.h:456-464 */ typedef struct { UCHAR ucNumEntries; /* VBIOS: how many real entries */ UCHAR ucEntrySize; /* sizeof(ATOM_PPLIB_NONCLOCK_INFO) = 24 */ ATOM_PPLIB_NONCLOCK_INFO nonClockInfo[1]; /* flex[1] */ } NonClockInfoArray; /* Faithful replica of the vulnerable access (kv_dpm.c:2668-2670). */ static ULONG read_nonclock_classification(NonClockInfoArray *arr, uint8_t non_clock_array_index) { ATOM_PPLIB_NONCLOCK_INFO *info = &arr->nonClockInfo[non_clock_array_index]; /* :2670 NO bounds check */ return info->ulCapsAndSettings; /* the OOB read */ } int main(void) { struct sigaction sa; sa.sa_handler = fault_handler; sigemptyset(&sa.sa_mask); sa.sa_flags = 0; sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); printf("DF-1306 kv_parse_power_table nonClockInfoIndex OOB read harness\n"); printf("sizeof(ATOM_PPLIB_NONCLOCK_INFO)=%zu sizeof(NonClockInfoArray)=%zu\n", sizeof(ATOM_PPLIB_NONCLOCK_INFO), sizeof(NonClockInfoArray)); /* Lay out a page with nonClockInfo[0] at the very end, next page unmapped. */ long pagesize = (long)getpagesize(); size_t maplen = (size_t)pagesize * 2; uint8_t *base = mmap(NULL, maplen, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (base == MAP_FAILED) { perror("mmap"); return 1; } munmap(base + pagesize, (size_t)pagesize); /* unmap page 2 */ /* NonClockInfoArray at the tail of page 1, with only nonClockInfo[0] * fitting. Place the array so that nonClockInfo[0] ends exactly at the * page boundary. */ size_t arr_overhead = offsetof(NonClockInfoArray, nonClockInfo); /* 2 bytes */ size_t need = arr_overhead + sizeof(ATOM_PPLIB_NONCLOCK_INFO); /* 2 + 24 */ NonClockInfoArray *arr = (NonClockInfoArray *)(base + pagesize - need); arr->ucNumEntries = 1; /* only 1 real entry (nonClockInfo[0]) */ arr->ucEntrySize = (UCHAR)sizeof(ATOM_PPLIB_NONCLOCK_INFO); memset(&arr->nonClockInfo[0], 0xAA, sizeof(ATOM_PPLIB_NONCLOCK_INFO)); /* Crafted VBIOS power_state: nonClockInfoIndex = 255 (way past ucNumEntries=1). * Access nonClockInfo[255] = byte offset 255*24 = 6120 -> faults on unmapped page. */ uint8_t non_clock_array_index = 255; printf("NonClockInfoArray @ %p (nonClockInfo[0] @ %p), ucNumEntries=%u\n", (void *)arr, (void *)&arr->nonClockInfo[0], arr->ucNumEntries); printf("VBIOS nonClockInfoIndex = %u (NO check vs ucNumEntries=%u)\n", non_clock_array_index, arr->ucNumEntries); printf("access byte offset = %u * %zu = %u (past the single valid entry)\n", non_clock_array_index, sizeof(ATOM_PPLIB_NONCLOCK_INFO), non_clock_array_index * (unsigned)sizeof(ATOM_PPLIB_NONCLOCK_INFO)); if (setjmp(jb) == 0) { ULONG v = read_nonclock_classification(arr, non_clock_array_index); printf("UNEXPECTED: read succeeded (value=0x%08x) โ layout too loose\n", v); } else { printf("FAULT (signal %d): OOB read at nonClockInfo[%u] off the bios buffer\n", got_fault, non_clock_array_index); printf(" -> in-kernel equivalent: heap OOB read up to %u bytes past nonClockInfo[0]\n", non_clock_array_index * (unsigned)sizeof(ATOM_PPLIB_NONCLOCK_INFO)); printf("RESULT: OOB read CONFIRMED at kv_dpm.c:2670 " "(nonClockInfoIndex used with NO bounds check vs ucNumEntries)\n"); } munmap(base, (size_t)pagesize); return 0; } |