DF-4913 / v14leak.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 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | /* * DF-4913 PoC — ath_hal_v14EepromAttach leaks HAL_EEPROM_v14 on error paths. * * Calls the REAL, in-kernel ath_hal_v14EepromAttach() (compiled into the * guest kernel) with a synthetic struct ath_hal whose ah_eepromRead callback * serves a crafted EEPROM image. Exercises: * * mode 1 (EEREAD): read fails mid-image -> return at ah_eeprom_v14.c:376 * mode 2 (EEBADSUM): XOR checksum != 0xffff-> return at ah_eeprom_v14.c:414 * mode 3 (EEBADSUM ver): checksum OK, bad ver -> return at ah_eeprom_v14.c:433 * mode 0 (CONTROL): valid v14 image -> HAL_OK, eeprom installed, * explicit detach frees it (balanced). * * The three failure modes each orphan the ath_hal_malloc'd HAL_EEPROM_v14 * (ah_eeprom_v14.c:363): no ath_hal_free, and AH_PRIVATE(ah)->ah_eeprom / * ah_eepromDetach are only installed at :438-440 after every error return, * so nothing can ever reclaim it. * * Measure the leak from userland: * vmstat -m | grep ath_hal (before kldload / after kldload / after kldunload) */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/errno.h> #include <sys/sysctl.h> #include "opt_ah.h" #include "ah.h" #include "ah_internal.h" #include "ah_eeprom_v14.h" /* ath_hal_v14EepromAttach() is declared in ah_eeprom.h (via ah_eeprom_v14.h) */ #define NWORDS (sizeof(struct ar5416eeprom) / sizeof(uint16_t)) #define NFAIL 200 /* attach attempts per failure mode */ static uint16_t fake_eep[NWORDS]; static int fail_word = -1; static HAL_BOOL fake_eepromRead(struct ath_hal *ah, u_int off, uint16_t *data) { u_int idx; if (off == AR5416_EEPROM_MAGIC_OFFSET) { *data = AR5416_EEPROM_MAGIC; return AH_TRUE; } if (off < owl_eep_start_loc) { *data = 0xffff; return AH_TRUE; } idx = off - owl_eep_start_loc; if (fail_word >= 0 && idx == (u_int)fail_word) return AH_FALSE; /* simulated EEPROM read failure */ *data = (idx < NWORDS) ? fake_eep[idx] : 0xffff; return AH_TRUE; } /* * Build an image whose baseEepHeader.length word (word 2) is `length'. * The attach code checksums AH_MIN(length, sizeof(struct ar5416eeprom))/2 * words; we fix up word 1 so that XOR over the checked range == 0xffff when * `fixup' is set. After the checksum the code swaps words 0 and 2, so the * version it validates is the `length' value we put in word 2: * good : 0xE001 -> (0xE001 >> 12) & 0xF == 14 -> attach succeeds * badver: NWORDS -> high nibble 0 -> version check fails */ static void craft_image(uint16_t length, int fixup) { u_int len_check = (AH_MIN(length, sizeof(struct ar5416eeprom))) / 2; uint16_t s = 0; u_int w; memset(fake_eep, 0, sizeof(fake_eep)); fake_eep[2] = length; if (!fixup) fake_eep[5] = 0x1234; /* guaranteed != 0xffff checksum */ else { for (w = 0; w < len_check && w < NWORDS; w++) s ^= fake_eep[w]; fake_eep[1] = (uint16_t)(0xffff ^ s); /* word 1 in range */ } fail_word = -1; } struct ath_hal_private *fake_ahp; static int do_mode(const char *name, uint16_t length, int fixup, int midread_fail) { HAL_STATUS st; int i, ok = 0, leaked = 0; craft_image(length, fixup); if (midread_fail) fail_word = 5; for (i = 0; i < NFAIL; i++) { memset(fake_ahp, 0, sizeof(*fake_ahp)); AH_PRIVATE((struct ath_hal *)fake_ahp)->ah_eepromRead = fake_eepromRead; st = ath_hal_v14EepromAttach((struct ath_hal *)fake_ahp); if (st == HAL_OK) ok++; /* not installed => permanently orphaned */ if (AH_PRIVATE((struct ath_hal *)fake_ahp)->ah_eeprom == NULL) leaked++; } kprintf("DF-4913[%s]: %d attaches, %d HAL_OK, %d with ah_eeprom==NULL " "(orphaned mallocs)\n", name, NFAIL, ok, leaked); return 0; } static int do_control(void) { HAL_STATUS st; int i, ok = 0; craft_image(0xE001, 1); for (i = 0; i < NFAIL; i++) { memset(fake_ahp, 0, sizeof(*fake_ahp)); AH_PRIVATE((struct ath_hal *)fake_ahp)->ah_eepromRead = fake_eepromRead; st = ath_hal_v14EepromAttach((struct ath_hal *)fake_ahp); if (st == HAL_OK) { ok++; /* properly installed: detach balances the malloc */ AH_PRIVATE((struct ath_hal *)fake_ahp)-> ah_eepromDetach((struct ath_hal *)fake_ahp); } } kprintf("DF-4913[control]: %d attaches, %d HAL_OK, each detached " "(balanced; expect no ath_hal growth)\n", NFAIL, ok); return 0; } static int sysctl_run(SYSCTL_HANDLER_ARGS) { int v = 0, error; error = sysctl_handle_int(oidp, &v, 0, req); if (error || req->newptr == NULL) return (error); kprintf("DF-4913: trigger\n"); fake_ahp = kmalloc(sizeof(*fake_ahp), M_TEMP, M_WAITOK | M_ZERO); do_mode("EEREAD@376", 0, 0, 1); do_mode("EEBADSUM@414", 0x1111, 0, 0); do_mode("BADVER@433", NWORDS, 1, 0); do_control(); kfree(fake_ahp, M_TEMP); kprintf("DF-4913: demo complete\n"); return (0); } static int v14leak_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("DF-4913 PoC loaded (inert): sizeof(HAL_EEPROM_v14)=%zu " "NWORDS=%zu owl_eep_start_loc=%d — " "sysctl hw.v14leak.run=1 to run\n", sizeof(HAL_EEPROM_v14), (size_t)NWORDS, owl_eep_start_loc); SYSCTL_ADD_OID(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "v14leak", CTLTYPE_INT|CTLFLAG_RW, NULL, 0, sysctl_run, "I", "DF-4913 trigger"); return 0; case MOD_UNLOAD: kprintf("DF-4913 PoC unloaded (leaked blocks stay leaked)\n"); return 0; } return 0; } static moduledata_t v14leak_mod = { "v14leak", v14leak_modevent, NULL }; DECLARE_MODULE(v14leak, v14leak_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(v14leak, 1); |