DF-1470 / 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 138 139 140 141 142 143 144 145 | /* * DF-1470 harness — processpptables VBIOS USHORT offsets never validated * * Reproduces the vulnerable pattern across ~50 sites in * sys/dev/drm/amd/powerplay/hwmgr/processpptables.c * representative site: * :55-66 get_vce_table_offset() derefs powerplay_table + usExtendendedHeaderOffset * :874-876 pp_tables_get_num_of_entries() derefs powerplay_table + usStateArrayOffset * :917-918 (clock info array), :920-921 (non-clock info array), :60-65 (extended header) * * The pattern: a USHORT offset from the VBIOS is added to the powerplay_table * base pointer and immediately dereferenced as a typed struct pointer, with * NO check that the offset lies within [0, soft_pp_table_size). A single * malicious USHORT (e.g. 0xFFFF) makes the pointer land ~64 KB past the table * -> chained derefs cascade into arbitrary-read-then-corrupt. * * Representative kernel code (processpptables.c:55-66): * * if (powerplay_table3->usExtendendedHeaderOffset > 0) { * const ATOM_PPLIB_EXTENDEDHEADER *extended_header = * (const ATOM_PPLIB_EXTENDEDHEADER *) * (((unsigned long)powerplay_table3) + * le16_to_cpu(powerplay_table3->usExtendendedHeaderOffset)); // OOB ptr * if (le16_to_cpu(extended_header->usSize) >= ...) // OOB read #1 * vce_table_offset = le16_to_cpu(extended_header->usVCETableOffset); // OOB read #2 * } * * The extended_header pointer is computed from the untrusted offset and then * TWO fields are read from it (usSize, usVCETableOffset) -- both OOB. * * soft_pp_table_size is stored (processpptables.c:844) but never used to * validate any offset. * * Build: cc -O2 -Wall -o harness harness.c * Run: ./harness * * Proof strategy: marker redzone. Allocate a buffer larger than the legit * powerplay table, fill the bytes past the table with a marker, and set * usExtendendedHeaderOffset to land the computed extended_header pointer in * that redzone. Show the deref returns the marker bytes -> the kernel would * have read memory beyond the powerplay table image. */ #include <stdio.h> #include <stdint.h> #include <stddef.h> #include <stdlib.h> #include <string.h> typedef uint8_t UCHAR; typedef uint16_t USHORT; typedef uint32_t ULONG; #define TABLE_REAL_SIZE 16 /* legit powerplay table is 16 bytes */ #define REDZONE_SIZE 32 /* marker-filled bytes past the table */ #define MARKER_SIZE 0xBEEF #define MARKER_VCE_OFF 0xCAFE /* Minimal ATOM_PPLIB_EXTENDEDHEADER (the OOB-deref'd target). */ typedef struct __attribute__((packed)) { USHORT usSize; USHORT usVCETableOffset; } ext_header_replica; /* Minimal powerplay_table3 (header + the malicious offset). */ typedef struct __attribute__((packed)) { UCHAR ucTableFormatRevision; UCHAR ucTableContentRevision; ULONG usStructureSize; USHORT filler[3]; USHORT usExtendendedHeaderOffset; } pp_table_replica; #define SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V2 8 /* Faithful replica of processpptables.c:55-66. Returns the VCE table offset * extracted (forces both OOB derefs to execute). */ static USHORT get_vce_table_offset(const UCHAR *base, USHORT ext_hdr_off) { USHORT vce_table_offset = 0; if (ext_hdr_off > 0) { const ext_header_replica *extended_header = (const ext_header_replica *)(base + ext_hdr_off); /* :60-62 OOB ptr */ if (extended_header->usSize >= /* :63 OOB read #1 */ SIZE_OF_ATOM_PPLIB_EXTENDEDHEADER_V2) vce_table_offset = extended_header->usVCETableOffset; /* :65 OOB read #2 */ } return vce_table_offset; } int main(void) { /* Backing buffer: real table + redzone (what adjacent slab would contain). */ UCHAR *buf = calloc(1, TABLE_REAL_SIZE + REDZONE_SIZE); if (!buf) { perror("calloc"); return 1; } pp_table_replica *ppt = (pp_table_replica *)buf; ppt->ucTableFormatRevision = 0; ppt->usExtendendedHeaderOffset = 0; /* in-bounds: no ext header */ /* Place a fake "extended header" in the REDZONE to expose the OOB read: * at offset TABLE_REAL_SIZE (just past the legit table). */ ext_header_replica *fake = (ext_header_replica *)(buf + TABLE_REAL_SIZE); fake->usSize = MARKER_SIZE; fake->usVCETableOffset = MARKER_VCE_OFF; printf("DF-1470 processpptables VBIOS USHORT offset OOB deref harness\n"); printf("table real size = %d bytes\n", TABLE_REAL_SIZE); printf("redzone size = %d bytes (marker-filled)\n", REDZONE_SIZE); printf("marker ext_header at = offset %d (just past the table)\n", TABLE_REAL_SIZE); printf("marker usSize = 0x%04x\n", MARKER_SIZE); printf("marker usVCETableOffset = 0x%04x\n", MARKER_VCE_OFF); /* In-bounds: offset = 0 -> no deref. */ USHORT ok = get_vce_table_offset(buf, 0); printf("\nIn-bounds usExtendendedHeaderOffset=0 -> vce_offset=%u (OK)\n", ok); /* OOB: attacker sets offset = TABLE_REAL_SIZE (past the table). The kernel * computes base+offset into adjacent memory and derefs. */ USHORT bad_off = TABLE_REAL_SIZE; USHORT bad = get_vce_table_offset(buf, bad_off); printf("OOB usExtendendedHeaderOffset=%u (past table) -> vce_offset=0x%04x\n", bad_off, bad); printf(" (matches marker usVCETableOffset=0x%04x)\n", MARKER_VCE_OFF); /* Worst case: offset = 0xFFFF -> ~64KB past base. */ printf("Worst case usExtendendedHeaderOffset=0xFFFF -> deref at base+65535,\n"); printf(" ~64KB past the powerplay table -> cascading arbitrary read.\n"); int leaked = (bad == MARKER_VCE_OFF); if (leaked) { printf("\nRESULT: heap OOB read CONFIRMED (processpptables.c:60-65 pattern)\n"); printf("USHORT offsets (usExtendendedHeaderOffset/usStateArrayOffset/\n"); printf("usClockInfoArrayOffset/usVCETableOffset/...) added to powerplay_table\n"); printf("base and deref'd with NO check vs soft_pp_table_size. A single\n"); printf("malicious offset cascades into arbitrary-read-then-corrupt chains.\n"); free(buf); return 0; } printf("\nUNEXPECTED: marker not observed\n"); free(buf); return 1; } |