DF-0730 / df0730_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 | /* * DF-0730 — ra_rate_thresh[3][8] heap OOB read/write proof (code-level harness) * --------------------------------------------------------------------------- * * The DragonFlyBSD master `wlan_rssadapt` rate-adaptation module * (sys/netproto/802_11/wlan/ieee80211_rssadapt.c) has a fixed-size array * dimension that is smaller than the maximum index used to access it: * * sys/netproto/802_11/ieee80211_rssadapt.h:65-66 * uint16_t ra_rate_thresh[IEEE80211_RSSADAPT_BKTS=3] * [IEEE80211_RATE_SIZE=8]; * * sys/netproto/802_11/_ieee80211.h:374-375 * #define IEEE80211_RATE_SIZE 8 // 802.11 standard * #define IEEE80211_RATE_MAXSIZE 15 // max rates we'll handle * struct ieee80211_rateset { uint8_t rs_nrates; uint8_t rs_rates[15]; }; * * `rs_nrates` can legitimately reach up to 15 (ieee80211_setup_rates, * ieee80211_input.c:413-439, merges Supported(<=8) + XRATES(<=7), capped at * MAXSIZE=15). rssadapt_node_init (:198) copies that rateset verbatim, so * ra->ra_rates.rs_nrates can be up to 15. * * Then: * rssadapt_rate :253 for (rix = rs->rs_nrates-1; rix >= 0; rix--) * :254 if ((*thrs)[rix] < (rssi << 8)) break; * rssadapt_lower_rate:285 (*thrs)[rix] = interpolate(...) // WRITE * rssadapt_raise_rate:310 (*thrs)[rix+1] = interpolate(...) // WRITE * * where `thrs = &ra->ra_rate_thresh[bucket(pktlen)]` and bucket()=2 for * pktlen > 1024 (IEEE80211_RSSADAPT_BKT0 * 2^BKTPOWER = 128*8 = 1024). * * For bucket=2 and rix=14, (*thrs)[14] = ra->ra_rate_thresh[2][14], which is * 6 uint16_t (12 bytes) PAST the end of ra_rate_thresh[2][7] = past the end * of the struct -> heap OOB WRITE. * * This harness replicates the EXACT struct layout and the rssadapt_lower_rate * write logic, allocates the struct inside a canaried region, and demonstrates * the OOB write corrupts memory past the struct. It is parameterized by * THRESH_COLS so the same harness proves the bug when built with the buggy * dimension (8) and proves the fix when built with IEEE80211_RATE_MAXSIZE (15). * * This is a code-level proof because the bug is LATENT on this guest: * - no WiFi radio (ifconfig -l => vtnet0 lo0) * - wlan_rssadapt is `optional wlan_rssadapt` (sys/conf/files:1655), NOT in * the default X86_64_GENERIC kernel * - NO in-tree driver calls ieee80211_ratectl_set(vap, RSSADAPT); the * default ratectl is AMRR (ieee80211_ratectl.c:122) * So it cannot be triggered live here; the harness is the deterministic proof. */ #include <stdint.h> #include <stddef.h> #include <stdio.h> #include <string.h> /* --- Mirror sys/netproto/802_11/_ieee80211.h:374-379 --------------------- */ #define IEEE80211_RATE_SIZE 8 /* 802.11 standard (array dim) */ #define IEEE80211_RATE_MAXSIZE 15 /* max rates we'll handle */ struct ieee80211_rateset { uint8_t rs_nrates; uint8_t rs_rates[IEEE80211_RATE_MAXSIZE]; }; /* --- Mirror sys/netproto/802_11/ieee80211_rssadapt.h:41-67 ---------------- */ #define IEEE80211_RSSADAPT_BKTS 3 #define IEEE80211_RSSADAPT_BKT0 128 #define IEEE80211_RSSADAPT_BKTPOWER 3 /* * THRESH_COLS is the second dimension of ra_rate_thresh. * Default = IEEE80211_RATE_SIZE (8) = the BUGGY value in master. * Build with -DTHRESH_COLS=IEEE80211_RATE_MAXSIZE to model the fix. */ #ifndef THRESH_COLS #define THRESH_COLS IEEE80211_RATE_SIZE #endif struct ieee80211_rssadapt_node { void *ra_rs; /* backpointer (pointer-sized) */ struct ieee80211_rateset ra_rates; /* negotiated rates */ int ra_rix; int ra_ticks; int ra_last_raise; int ra_raise_interval; uint32_t ra_nfail; uint32_t ra_nok; uint32_t ra_pktrate; /* THE BUG: dimension is IEEE80211_RATE_SIZE(8) but indexed up to 14. */ uint16_t ra_rate_thresh[IEEE80211_RSSADAPT_BKTS][THRESH_COLS]; }; /* --- Mirror ieee80211_rssadapt.c:220-233 (bucket()) ---------------------- */ static int bucket(int pktlen) { int i, top, thridx; for (i = 0, top = IEEE80211_RSSADAPT_BKT0; i < IEEE80211_RSSADAPT_BKTS; i++, top <<= IEEE80211_RSSADAPT_BKTPOWER) { thridx = i; if (pktlen <= top) break; } return thridx; } /* --- Mirror ieee80211_rssadapt.c:274-292 (rssadapt_lower_rate) ----------- * * Only the WRITE matters for this proof; we use a fixed rix and a fake * interpolate result so the corruption is unambiguous. */ static uint16_t fake_new_thr; static void rssadapt_lower_rate_write(struct ieee80211_rssadapt_node *ra, int pktlen, int rix) { uint16_t (*thrs)[THRESH_COLS] = &ra->ra_rate_thresh[bucket(pktlen)]; /* (*thrs)[rix] = interpolate(...); -- the OOB WRITE */ (*thrs)[rix] = fake_new_thr; } int main(void) { /* Layout sanity prints (tie harness to kernel source). */ printf("THRESH_COLS (ra_rate_thresh 2nd dim) = %d\n", (int)THRESH_COLS); printf("IEEE80211_RATE_SIZE = %d\n", (int)IEEE80211_RATE_SIZE); printf("IEEE80211_RATE_MAXSIZE = %d\n", (int)IEEE80211_RATE_MAXSIZE); printf("IEEE80211_RSSADAPT_BKTS= %d\n", (int)IEEE80211_RSSADAPT_BKTS); printf("sizeof(struct ieee80211_rssadapt_node) = %zu\n", sizeof(struct ieee80211_rssadapt_node)); printf("offsetof(ra_rate_thresh) = %zu\n", offsetof(struct ieee80211_rssadapt_node, ra_rate_thresh)); printf("sizeof(ra_rate_thresh[0]) = %zu\n", sizeof(((struct ieee80211_rssadapt_node *)0)->ra_rate_thresh[0])); printf("\n"); /* The realistic trigger: a standard 11g AP advertises 12 rates. */ const uint8_t nrates = 12; /* rs_nrates, attacker/peer-driven */ int max_rix = nrates - 1; /* = 11 (could be 14 with 15 rates) */ /* bucket=2 means pktlen > 1024 (the worst bucket: spills past struct). */ int pktlen = 1500; /* typical MTU frame */ int b = bucket(pktlen); printf("Trigger: rs_nrates=%u, max rix=%d, pktlen=%d -> bucket=%d\n", nrates, max_rix, pktlen, b); /* Worst-case index across all valid nrates (<=15). */ int worst_rix = IEEE80211_RATE_MAXSIZE - 1; /* 14 */ printf("Worst-case rix (rs_nrates=15) = %d\n", worst_rix); /* Place the struct inside a canaried allocation so an OOB write past its * end is detected deterministically. */ enum { CANARY = 64 }; unsigned char region[sizeof(struct ieee80211_rssadapt_node) + CANARY]; memset(region, 0xAA, sizeof(region)); struct ieee80211_rssadapt_node *ra = (struct ieee80211_rssadapt_node *)region; fake_new_thr = 0xDEAD; /* Simulate rssadapt_lower_rate writing at the worst-case index for the * worst bucket. In the kernel this is (*thrs)[ra->ra_rix]; ra_rix comes * from rssadapt_rate which left it at the loop's last value. */ rssadapt_lower_rate_write(ra, pktlen, worst_rix); size_t struct_end = sizeof(struct ieee80211_rssadapt_node); /* where did the write land, relative to region start? */ size_t thresh_base = offsetof(struct ieee80211_rssadapt_node, ra_rate_thresh); size_t row_size = sizeof(uint16_t) * THRESH_COLS; size_t write_off = thresh_base + (size_t)b * row_size + (size_t)worst_rix * sizeof(uint16_t); printf("\nWrite landed at offset %zu within the allocation.\n", write_off); printf("struct end is at offset %zu.\n", struct_end); int oob = (write_off + sizeof(uint16_t)) > struct_end; int canary_clobbered = 0; for (size_t i = struct_end; i < struct_end + CANARY; i++) { if (region[i] != 0xAA) { canary_clobbered = 1; break; } } printf("\n=== VERDICT ===\n"); if (oob) { printf("OUT-OF-BOUNDS: write at off %zu is %zu byte(s) PAST the struct " "(struct ends at %zu).\n", write_off, (write_off + sizeof(uint16_t)) - struct_end, struct_end); printf("Canary clobbered past struct end: %s\n", canary_clobbered ? "YES (heap OOB WRITE confirmed)" : "no"); printf("BUG PRESENT: ra_rate_thresh[3][%d] indexed by rix=%d -> OOB.\n", (int)THRESH_COLS, worst_rix); return 1; /* non-zero => bug demonstrated */ } else { printf("IN-BOUNDS: write at off %zu is within the struct (ends %zu). " "No OOB.\n", write_off, struct_end); printf("Canary intact: %s\n", canary_clobbered ? "NO (unexpected)" : "YES"); printf("BUG FIXED: ra_rate_thresh[3][%d] safely holds rix=%d.\n", (int)THRESH_COLS, worst_rix); return 0; /* zero => no bug */ } } |