DF-5293 / rum_cmdq_uaf.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 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 | /* * DF-5293 simulation harness - deferred rum_cmdq entry holds a raw * struct ieee80211vap * that outlives rum_vap_delete(). * * Models, line-for-line: * * rum_cmd_sleepable() if_rum.c:745-771 * - stores (ptr,len) into cmdq[cmdq_last] under cmdq_lock and calls * ieee80211_runtask() (768) which is taskqueue_enqueue() - ASYNC. * * rum_update_beacon() if_rum.c:2830 * - rum_cmd_sleepable(sc, &vap, sizeof(vap), 0, rum_update_beacon_cb) * -> the queued union sec_param holds the RAW vap POINTER. * * rum_cmdq_cb() if_rum.c:723-743 * - taskqueue thread drains the ring, rc->func(sc, &rc->data, rvp_id) * * rum_update_beacon_cb() if_rum.c:2784-2790 * - data->vap -> rum_set_beacon(sc, vap) (2729) * 2733: m = rvp->bcn_mbuf <- deref of freed rum_vap * 2744: vap->iv_txparms[...] <- deref of freed rum_vap * 2754: rum_write_multi(..., mtod(m,...), m->m_pkthdr.len) * <- wild read of freed mbuf * * rum_vap_delete() if_rum.c:709-721 * - 715: m_freem(rvp->bcn_mbuf) * - 716: usb_callout_drain(ratectl_ch) (ratectl only) * - 717: ieee80211_draintask(ratectl_task) (ratectl only) * - 720: kfree(rvp, M_80211_VAP) * *** sc->cmdq_task is NEVER drained and the cmdq ring is never purged * of entries referencing the vap. (if_run/if_mtw's FreeBSD * counterparts gained exactly this drain after CVE-class fixes; * DF's own if_run has the same hole - see DF-5247/DF-5260.) * * The harness models the taskqueue as a worker thread with realistic * dispatch latency. "freed" dereferences are counted via a poison flag. * Mode 0 = rum as-shipped (no drain): post-free rum_set_beacon executes. * Mode 1 = with a draintask(cmdq_task) before kfree: zero post-free cb. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <stdatomic.h> #include <unistd.h> #define RUM_CMDQ_SIZE 16 struct sec_param_model { void *vap; }; struct cmdq_entry { struct sec_param_model data; int used; }; static struct cmdq_entry cmdq[RUM_CMDQ_SIZE]; static int cmdq_first, cmdq_last; static pthread_mutex_t cmdq_lock = PTHREAD_MUTEX_INITIALIZER; static atomic_int vap_freed; static atomic_long cb_total, cb_uaf; static atomic_long enq_ok, enq_full; static volatile int stop; static int guarded_mode; /* taskqueue with dispatch latency */ static pthread_mutex_t tq_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t tq_cv = PTHREAD_COND_INITIALIZER; static int tq_pending; static pthread_cond_t tq_idle_cv; static int tq_running; static void runtask(void) { pthread_mutex_lock(&tq_lock); tq_pending++; pthread_cond_signal(&tq_cv); pthread_mutex_unlock(&tq_lock); } static void tq_wait_idle(void) { pthread_mutex_lock(&tq_lock); while (tq_pending || tq_running) pthread_cond_wait(&tq_idle_cv, &tq_lock); pthread_mutex_unlock(&tq_lock); } /* rum_cmdq_cb drain loop, executed on the "taskqueue" thread */ static void taskqueue_body(void) { for (;;) { struct cmdq_entry *rc = NULL; pthread_mutex_lock(&cmdq_lock); if (cmdq[cmdq_first].used) rc = &cmdq[cmdq_first]; pthread_mutex_unlock(&cmdq_lock); if (rc == NULL) break; /* rum_update_beacon_cb -> rum_set_beacon(sc, vap): * dereferences the raw vap pointer */ atomic_fetch_add(&cb_total, 1); if (atomic_load(&vap_freed)) atomic_fetch_add(&cb_uaf, 1); pthread_mutex_lock(&cmdq_lock); memset(rc, 0, sizeof(*rc)); cmdq_first = (cmdq_first + 1) % RUM_CMDQ_SIZE; pthread_mutex_unlock(&cmdq_lock); } } static void * thr_taskqueue(void *arg) { (void)arg; for (;;) { pthread_mutex_lock(&tq_lock); while (!tq_pending && !stop) pthread_cond_wait(&tq_cv, &tq_lock); if (stop && !tq_pending) { pthread_mutex_unlock(&tq_lock); break; } tq_pending--; tq_running = 1; pthread_mutex_unlock(&tq_lock); /* task dispatch latency: the window in which vap_delete runs */ usleep(200 + random() % 800); taskqueue_body(); pthread_mutex_lock(&tq_lock); tq_running = 0; pthread_cond_signal(&tq_idle_cv); pthread_mutex_unlock(&tq_lock); } return NULL; } /* rum_cmd_sleepable storing the RAW vap pointer (if_rum.c:2830) */ static int cmd_sleepable_vap(void *vap) { pthread_mutex_lock(&cmdq_lock); if (cmdq[cmdq_last].used) { pthread_mutex_unlock(&cmdq_lock); atomic_fetch_add(&enq_full, 1); return (-1); } cmdq[cmdq_last].data.vap = vap; cmdq[cmdq_last].used = 1; cmdq_last = (cmdq_last + 1) % RUM_CMDQ_SIZE; pthread_mutex_unlock(&cmdq_lock); atomic_fetch_add(&enq_ok, 1); runtask(); return 0; } static void * thr_beacon_updater(void *arg) { (void)arg; while (!stop) { /* iv_update_beacon: queues with raw &vap */ cmd_sleepable_vap((void *)0x1000); usleep(400); /* beacon churn rate */ } return NULL; } static void * thr_destroyer(void *arg) { (void)arg; while (!stop) { usleep(1000 + random() % 3000); /* ifconfig wlanX destroy */ /* rum_vap_delete (709-721): NO cmdq drain in shipped code */ if (guarded_mode) tq_wait_idle(); /* the missing draintask fix */ atomic_store(&vap_freed, 1); /* kfree(rvp) :720 */ usleep(4000); /* vap gone; new vap may appear */ atomic_store(&vap_freed, 0); /* realloc/reuse of the slot */ } return NULL; } int main(int argc, char **argv) { int secs = argc > 1 ? atoi(argv[1]) : 10; guarded_mode = argc > 2 ? atoi(argv[2]) : 0; pthread_t t1, t2, t3; srandom(1234); pthread_create(&t1, NULL, thr_taskqueue, NULL); pthread_create(&t2, NULL, thr_beacon_updater, NULL); pthread_create(&t3, NULL, thr_destroyer, NULL); sleep(secs); stop = 1; pthread_mutex_lock(&tq_lock); pthread_cond_broadcast(&tq_cv); pthread_mutex_unlock(&tq_lock); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); printf("rum cmdq/vap lifetime model (%d s, mode=%s): enq=%ld " "(ring-full %ld) cb_executions=%ld POST-FREE rum_set_beacon=%ld\n", secs, guarded_mode ? "DRAINED(fixed)" : "SHIPPED(no drain)", atomic_load(&enq_ok), atomic_load(&enq_full), atomic_load(&cb_total), atomic_load(&cb_uaf)); return 0; } |