DF-5295 / rum_txslot_leak.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 | /* * DF-5295 deterministic model - rum_tx_mgt() permanently loses a tx_data * slot on its early-error paths (TX path exhaustion). * * Models, line-for-line (sys/bus/u4b/wlan/if_rum.c): * * rum_tx_mgt() 1478-1494: * data = STAILQ_FIRST(&sc->tx_free); :1478 * STAILQ_REMOVE_HEAD(&sc->tx_free, next); :1479 <-- slot popped * sc->tx_nfree--; :1480 * ... * if (wh->i_fc[1] & IEEE80211_FC1_PROTECTED) { * k = ieee80211_crypto_get_txkey(ni, m0); * if (k == NULL) * return (ENOENT); :1490 <-- slot ORPHANED * if ((k->wk_flags & KEY_SWCRYPT) && * !k->wk_cipher->ic_encap(k, m0)) * return (ENOBUFS); :1494 <-- slot ORPHANED * } * * The entry is no longer on tx_free, is not put on tx_q, holds no mbuf: * tx_nfree is never incremented again for it. RUM_TX_LIST_COUNT is 8, * RUM_TX_MINFREE is 2 (if_rumvar.h:29-30). * * rum_start() 1734: while (sc->tx_nfree >= RUM_TX_MINFREE && ...) * -> once 7 slots are orphaned, tx_nfree == 1 < 2 and the interface * stops dequeuing frames (permanent TX wedge until down/up, since * only rum_init() -> rum_setup_tx_list() (:2577) restores the * list). * * Compare rum_tx_data() 1631-1645: same key checks happen BEFORE the * slot is popped (pop is at 1670) and the error paths m_freem() - the * ordering deviation is unique to rum_tx_mgt. * * Trigger: raw 802.11 management frame with the Protected flag set and no * matching key, injected through the legacy rum_raw_xmit() path * (params == NULL, :3060-3065), i.e. bpf writes on a monitor-mode wlan. */ #include <stdio.h> #define RUM_TX_LIST_COUNT 8 #define RUM_TX_MINFREE 2 struct state { int tx_nfree; int orphaned; int tx_wedged_resets; }; /* returns 0 on success, errcode otherwise; models rum_tx_mgt exactly */ static int rum_tx_mgt(struct state *sc, int protected_flag, int have_key) { /* :1478-1480 - unconditional pop */ sc->tx_nfree--; if (protected_flag) { if (!have_key) { sc->orphaned++; /* :1490 ENOENT - slot leaked */ return (-1); } /* ic_encap failure path :1494 would leak identically */ } /* success path would push onto tx_q and complete later */ sc->tx_nfree++; return 0; } /* rum_start gate :1734 */ static int rum_start_can_send(struct state *sc) { return sc->tx_nfree >= RUM_TX_MINFREE; } int main(void) { struct state sc = { RUM_TX_LIST_COUNT, 0, 0 }; int injected = 0, sent = 0, wedged_after = -1; printf("start: tx_nfree=%d (RUM_TX_LIST_COUNT=%d, MINFREE=%d)\n", sc.tx_nfree, RUM_TX_LIST_COUNT, RUM_TX_MINFREE); for (int i = 0; i < 16; i++) { /* raw mgmt frame, Protected set, no key -> ENOENT every time */ if (!rum_start_can_send(&sc)) sc.tx_nfree++; /* bypass gate only to drain slots */ (void)rum_tx_mgt(&sc, 1, 0); injected++; if (rum_start_can_send(&sc)) { sent++; } else if (wedged_after < 0) { wedged_after = injected; } } printf("injected %d protected-no-key mgmt frames; orphaned slots=%d; " "tx_nfree=%d\n", injected, sc.orphaned, sc.tx_nfree); printf("TX path wedged after %d injections (rum_start gate " "tx_nfree >= %d fails permanently)\n", wedged_after, RUM_TX_MINFREE); /* recovery only via down/up: rum_init -> rum_setup_tx_list (:2577) */ sc.tx_nfree = RUM_TX_LIST_COUNT; sc.orphaned = 0; sc.tx_wedged_resets++; printf("after ifconfig down/up: tx_nfree=%d, wedged resets=%d\n", sc.tx_nfree, sc.tx_wedged_resets); return 0; } |