DF-4956 / v4kleak.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 188 189 190 191 192 193 194 195 196 197 | /* * DF-4956 PoC — ath_hal_v4kEepromAttach leaks HAL_EEPROM_v4k on error paths. * * Third and last member of the eeprom-attach leak family: * DF-4873 (ah_eeprom_9287.c, ar9287) / DF-4913 (ah_eeprom_v14.c, ar5416) * / DF-1995 (ar5210 v1). This file: ah_eeprom_v4k.c (ar9285 "Kite"). * * Calls the REAL, in-kernel ath_hal_v4kEepromAttach() (compiled into the * guest kernel) with a synthetic struct ath_hal whose ah_eepromRead callback * serves a crafted 4k-variant EEPROM image (owl_eep_start_loc == 64). * * mode 1 (EEREAD): read fails mid-image -> return at ah_eeprom_v4k.c:324 * mode 2 (EEBADSUM): XOR checksum != 0xffff-> return at ah_eeprom_v4k.c:366 * mode 3 (EEBADSUM ver): checksum OK, bad ver -> return at ah_eeprom_v4k.c:385 * mode 0 (CONTROL): valid v4k image -> HAL_OK, eeprom installed, * explicit detach frees it (balanced). * * The three failure modes each orphan the ath_hal_malloc'd HAL_EEPROM_v4k * (ah_eeprom_v4k.c:311): no ath_hal_free, and AH_PRIVATE(ah)->ah_eeprom / * ah_eepromDetach are only installed at :390-396 after every error return, * so nothing can ever reclaim it. The caller (ar9285_attach.c:246-248 * "goto bad" -> ah_detach -> ar5416Detach:507 -> ath_hal_eepromDetach macro, * ah_internal.h:474-477) is a no-op for it. * * Measure the leak from userland: * vmstat -m | grep ath_hal (before kldload / after trigger / 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" #include "ah_eeprom_v4k.h" /* ath_hal_v4kEepromAttach() is declared in ah_eeprom.h (pulled in above). * NB: ah_eeprom_v4k.h #undef's owl_eep_start_loc and sets it to 64. */ #define NWORDS (sizeof(struct ar5416eeprom_4k) / 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 word 2 is `length'. The attach code checksums * AH_MIN(length, sizeof(struct ar5416eeprom_4k))/2 words; we fix up word 1 * (the checksum field) so that XOR over the checked range == 0xffff when * `fixup' is set. After the checksum the code swaps words 0 and 2 * (ah_eeprom_v4k.c:373-375), so the version it validates is the value we * put in word 2: * good : 0xE001 -> (0xE001 >> 12) & 0xF == 14 == AR5416_EEP_VER -> OK * 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_4k))) / 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_v4kEepromAttach((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-4956[%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_v4kEepromAttach((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-4956[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-4956: trigger\n"); fake_ahp = kmalloc(sizeof(*fake_ahp), M_TEMP, M_WAITOK | M_ZERO); do_mode("EEREAD@324", 0, 0, 1); do_mode("EEBADSUM@366", 0x1111, 0, 0); do_mode("BADVER@385", NWORDS, 1, 0); do_control(); kfree(fake_ahp, M_TEMP); kprintf("DF-4956: demo complete\n"); return (0); } static int v4kleak_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("DF-4956 PoC loaded (inert): sizeof(HAL_EEPROM_v4k)=%zu " "sizeof(ar5416eeprom_4k)=%zu NWORDS=%zu owl_eep_start_loc=%d " "— sysctl hw.v4kleak=1 to run\n", sizeof(HAL_EEPROM_v4k), sizeof(struct ar5416eeprom_4k), (size_t)NWORDS, owl_eep_start_loc); SYSCTL_ADD_OID(NULL, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, "v4kleak", CTLTYPE_INT|CTLFLAG_RW, NULL, 0, sysctl_run, "I", "DF-4956 trigger"); return 0; case MOD_UNLOAD: kprintf("DF-4956 PoC unloaded (leaked blocks stay leaked)\n"); return 0; } return 0; } static moduledata_t v4kleak_mod = { "v4kleak", v4kleak_modevent, NULL }; DECLARE_MODULE(v4kleak, v4kleak_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(v4kleak, 1); |