DF-0731 / df0731_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 | /* * DF-0731 — rssadapt_tx_complete unconditional NULL-deref proof (code harness) * --------------------------------------------------------------------------- * * CLAIM (findings/DF-0731, sys/netproto/802_11/wlan/ieee80211_rssadapt.c): * * 322: static void * 323: rssadapt_tx_complete(const struct ieee80211vap *vap, * 324: const struct ieee80211_node *ni, int success, void *arg1, void *arg2) * 325: { * 326: struct ieee80211_rssadapt_node *ra = ni->ni_rctls; * 327: int pktlen = *(int *)arg1, rssi = *(int *)arg2; <-- UNCONDITIONAL DEREF * ... * 338: } * * rssadapt_tx_complete is the .ir_tx_complete entry of the "rssadapt" * ieee80211_ratectl (ieee80211_rssadapt.c:99-111) and is reached via the * net80211 dispatch inline * * sys/netproto/802_11/ieee80211_ratectl.h:98-103 * static __inline void * ieee80211_ratectl_tx_complete(const struct ieee80211vap *vap, * const struct ieee80211_node *ni, int status, void *arg1, void *arg2) * { vap->iv_rate->ir_tx_complete(vap, ni, status, arg1, arg2); } * * EVERY in-tree WiFi driver that reports a TX-completion to the ratectl layer * passes NULL (or the null-pointer constant 0) as arg2 (the rssi pointer): * * sys/bus/u4b/wlan/if_urtwn.c:1041-1046 ... &ntries, NULL); * sys/dev/netif/iwm/if_iwm.c:3561-3568 ... &failack, NULL); * sys/dev/netif/ral/rt2661.c:928-942 ... &retrycnt, NULL); * sys/dev/netif/ral/rt2860.c:1157-1161 ... &retrycnt, NULL); * sys/dev/netif/ral/rt2560.c:982-1008 ... &retrycnt, NULL); * sys/dev/netif/wpi/if_wpi.c:2127-2131 ... &ackfailcnt, NULL); * sys/dev/netif/iwn/if_iwn.c:3330-3808 ... &ackfailcnt, NULL); * sys/dev/netif/bwn/bwn/if_bwn.c:6012-6078 ... &retrycnt, 0); // 0==NULL * * Therefore, as soon as the rssadapt ratectl algorithm is selected * (ieee80211_ratectl_set(vap, IEEE80211_RATECTL_RSSADAPT)) and a frame is * transmitted, the very first TX completion dereferences a NULL kernel * pointer -> fatal trap 12 (page fault) / kernel panic -> instant DoS. * * REACHABILITY: LATENT on the default X86_64_GENERIC guest: * - wlan_rssadapt is `optional wlan_rssadapt` (sys/conf/files:1655), NOT in * the default kernel; * - default ratectl is AMRR (ieee80211_ratectl.c:121-122); * - no WiFi radio on the audit guest (ifconfig -l => vtnet0 lo0). * So it cannot be triggered live here; this harness is the deterministic proof. * * The harness reproduces the EXACT control flow of rssadapt_tx_complete and * instruments the deref at line 327. Instead of actually crashing the process * (a NULL page-fault), it records "DEREF_OF_NULL" the moment the buggy path * reaches `*(int *)arg2` with arg2==NULL, which is precisely the in-kernel * fault condition. -DFIX_NULL_CHECK adds the proposed early-return guard so * the same harness proves the fix: the deref is never reached. * * NULL-deref at a fixed kernel address is a pure DoS — no corruption primitive, * so there is NO escalation chain (Phase 6 N/A for this finding). */ #include <stdint.h> #include <stddef.h> #include <stdio.h> #include <string.h> /* ---- Mirror the relevant fields used by rssadapt_tx_complete ------------ */ #define IEEE80211_RATE_SIZE 8 #define IEEE80211_RSSADAPT_BKTS 3 struct ieee80211_rateset { uint8_t rs_nrates; uint8_t rs_rates[15]; }; struct ieee80211_rssadapt_node { void *ra_rs; struct ieee80211_rateset ra_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; uint16_t ra_rate_thresh[IEEE80211_RSSADAPT_BKTS][IEEE80211_RATE_SIZE]; }; /* Per-call instrumentation of the line-327 deref. */ static int g_deref_of_null = 0; /* set if the buggy path derefs a NULL arg */ static int g_lower_rate_called = 0; static int g_raise_rate_called = 0; /* Instrumented stand-in for `*(int *)arg` at ieee80211_rssadapt.c:327. * Faithfully models the kernel load: if the pointer is NULL this is exactly * the in-kernel page-fault condition; we record it rather than SIGSEGV. */ static int deref_int_arg(void *arg, const char *which) { if (arg == NULL) { g_deref_of_null = 1; printf(" [deref] *(int *)%s with %s==NULL -> in-kernel: fatal trap 12 / panic\n", which, which); return 0; /* keep the harness running to report the result */ } return *(int *)arg; } static void rssadapt_lower_rate(struct ieee80211_rssadapt_node *ra, int pktlen, int rssi) { (void)ra; (void)pktlen; (void)rssi; g_lower_rate_called++; } static void rssadapt_raise_rate(struct ieee80211_rssadapt_node *ra, int pktlen, int rssi) { (void)ra; (void)pktlen; (void)rssi; g_raise_rate_called++; } /* * Reproduce rssadapt_tx_complete (ieee80211_rssadapt.c:322-338). The two * deref_int_arg() calls correspond to line 327's * int pktlen = *(int *)arg1, rssi = *(int *)arg2; * Under -DFIX_NULL_CHECK we add the proposed guard (matches fix.diff). */ static void rssadapt_tx_complete(struct ieee80211_rssadapt_node *ra, int success, void *arg1, void *arg2) { #ifdef FIX_NULL_CHECK if (arg1 == NULL || arg2 == NULL) /* the FIX */ return; #endif int pktlen = deref_int_arg(arg1, "arg1(pktlen)"); /* == *(int *)arg1 */ int rssi = deref_int_arg(arg2, "arg2(rssi)"); /* == *(int *)arg2 */ if (success) { ra->ra_nok++; if ((ra->ra_rix + 1) < ra->ra_rates.rs_nrates) rssadapt_raise_rate(ra, pktlen, rssi); } else { ra->ra_nfail++; rssadapt_lower_rate(ra, pktlen, rssi); } } int main(void) { struct ieee80211_rssadapt_node ra; memset(&ra, 0, sizeof(ra)); ra.ra_rates.rs_nrates = 4; ra.ra_rix = 0; int pktlen = 1500; printf("DF-0731 rssadapt_tx_complete NULL-arg2 deref harness\n"); printf("Build mode: %s\n", #ifdef FIX_NULL_CHECK "FIXED (NULL-check guard present, models fix.diff)" #else "BUGGY (matches master ieee80211_rssadapt.c:322-338)" #endif ); printf("Call: rssadapt_tx_complete(ra, success=0, arg1=&pktlen, arg2=NULL)\n"); printf(" (every in-tree driver: urtwn/ral/wpi/iwn/iwm pass NULL, bwn passes 0)\n\n"); /* Most-common driver callsite: arg2 == NULL. */ rssadapt_tx_complete(&ra, /*success*/ 0, &pktlen, /*arg2*/ NULL); printf("\n=== VERDICT ===\n"); #ifdef FIX_NULL_CHECK if (!g_deref_of_null) { printf("NO DEREF: NULL-arg guard returned early; line-327 load never reached.\n"); printf("rssi callbacks reached: lower=%d raise=%d (must be 0/0).\n", g_lower_rate_called, g_raise_rate_called); printf("BUG FIXED: rssadapt_tx_complete no longer derefs NULL arg2/arg1.\n"); return 0; /* fixed => exit 0 */ } printf("UNEXPECTED: fix did not prevent the deref.\n"); return 2; #else if (g_deref_of_null) { printf("NULL-DEREF: line 327 reached *(int *)arg2 with arg2==NULL.\n"); printf("In-kernel this is: fatal trap 12 (page fault on NULL) / panic -> DoS.\n"); printf("BUG PRESENT: rssadapt_tx_complete unconditionally derefs arg2.\n"); return 1; /* bug demonstrated => non-zero */ } printf("UNEXPECTED: buggy build did not reach the NULL deref.\n"); return 2; #endif } |