DF-5249 / cmdq_race.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 | /* * DF-5249 - Simulated reproduction of the if_run(4) run_cmdq machinery races * ========================================================================== * * Kernel code modeled (sys/bus/u4b/wlan/if_run.c): * * - sc->cmdq[16] ring, slot picked locklessly by * RUN_CMDQ_GET(c) (atomic_fetchadd_32(c,1) & 15) if_run.c:92 * - Producers store fields with plain stores, no lock, e.g. * run_key_set: cmdq[i].func = run_key_set_cb; if_run.c:2370 * cmdq[i].arg0 = NULL; if_run.c:2371 * cmdq[i].arg1 = vap; if_run.c:2372 * cmdq[i].k = k; if_run.c:2373 * run_update_beacon: cmdq[i].func = run_update_beacon_cb; * cmdq[i].arg0 = vap; if_run.c:4896-4897 * run_key_delete / run_newassoc / run_updateprot / * run_updateslot / run_tx(fifo) / tx-timeout cb (same pattern) * followed by ieee80211_runtask() -> taskqueue_enqueue. * - Single consumer run_cmdq_cb() runs under RUN_LOCK: if_run.c:1041-1068 * for (i = cmdq_exec; cmdq[i].func && pending; * i = cmdq_exec, pending--) { * if (cmdq_run == GO) { * if (cmdq[i].arg0) cmdq[i].func(cmdq[i].arg0); * else cmdq[i].func(&cmdq[i]); <- expects a * } fully written * cmdq[i].arg0 = NULL; cmdq[i].func = NULL; slot * cmdq_exec++ &= 15; * } * * Two failure modes simulated: * * (1) TORN SLOT: producer preempted between storing .func and .arg0 (or a * wrap-overwrite happening between the consumer's func/arg0 loads); * consumer sees func != NULL with arg0 == NULL -> kernel equivalent: * run_update_beacon_cb(NULL) dereferences vap = NULL immediately. * Or func_old called with arg0_new: run_key_set_cb(vap) treats the vap * as struct run_cmdq* and loads cmdq->k from vap memory -> wild * pointer deref (type confusion). * * (2) SLOT WRAP: more than 16 submissions outstanding overwrite * not-yet-executed entries (RUN_CMDQ_GET wraps the 16-slot ring while * the consumer is behind) -> a deferred command is silently replaced * by another one; e.g. a lost run_key_delete leaves a stale hardware * key installed. * * The consumer below is driven exactly like run_cmdq_cb, with `pending` * being the taskqueue coalesced enqueue count. * * build: cc -O2 -pthread -o cmdq_race cmdq_race.c * run: ./cmdq_race [seconds] */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <unistd.h> #define RUN_CMDQ_MAX 16 #define RUN_CMDQ_MASQ (RUN_CMDQ_MAX - 1) #define TAG_BEACON 0x11 #define TAG_KEYSET 0x22 struct run_cmdq { void *arg0; void *arg1; void (*func)(void *); void *k; }; static struct run_cmdq cmdq[RUN_CMDQ_MAX]; static volatile unsigned cmdq_store; static volatile int cmdq_exec; static volatile int pending; static volatile int stop; static long torn_null_hits; static long type_confusion_hits; static long wrap_overwrites; static long executed; static void cb_beacon(void *arg) { (void)arg; } static void cb_keyset(void *arg) { (void)arg; } static void *producer(void *arg) { long id = (long)arg; while (!stop) { unsigned slot = __sync_fetch_and_add(&cmdq_store, 1) & RUN_CMDQ_MASQ; if (cmdq[slot].func != NULL) __sync_fetch_and_add(&wrap_overwrites, 1); cmdq[slot].func = (id & 1) ? cb_keyset : cb_beacon; __sync_synchronize(); cmdq[slot].arg0 = (void *)((id & 1) ? (long)TAG_KEYSET : (long)TAG_BEACON); cmdq[slot].k = cmdq[slot].arg0; __sync_fetch_and_add(&pending, 1); /* ieee80211_runtask() */ } return NULL; } static void *consumer(void *arg) { (void)arg; for (;;) { int p, i; while ((p = pending) == 0) { if (stop) return NULL; usleep(100); } p = __sync_fetch_and_add(&pending, -pending); /* coalesced count */ i = cmdq_exec; while (cmdq[i].func && p) { void (*f)(void *) = cmdq[i].func; void *a = cmdq[i].arg0; if (a == NULL) __sync_fetch_and_add(&torn_null_hits, 1); else if ((f == cb_keyset) != (a == (void *)TAG_KEYSET)) __sync_fetch_and_add(&type_confusion_hits, 1); f(a); cmdq[i].arg0 = NULL; cmdq[i].func = NULL; cmdq_exec = (cmdq_exec + 1) & RUN_CMDQ_MASQ; __sync_fetch_and_add(&executed, 1); p--; i = cmdq_exec; } } } int main(int argc, char **argv) { int secs = (argc > 1) ? atoi(argv[1]) : 5; pthread_t p1, p2, c; pthread_create(&p1, NULL, producer, (void *)0); pthread_create(&p2, NULL, producer, (void *)1); pthread_create(&c, NULL, consumer, NULL); sleep(secs); stop = 1; pthread_join(p1, NULL); pthread_join(p2, NULL); pthread_join(c, NULL); printf("DF-5249 cmdq machinery race simulation over %ds\n", secs); printf(" executed callbacks : %ld\n", executed); printf(" torn NULL-arg calls : %ld (kernel: run_update_beacon_cb(NULL) = NULL deref)\n", torn_null_hits); printf(" identity mismatches : %ld (kernel: func() run with another cmd's args)\n", type_confusion_hits); printf(" slot wrap overwrites : %ld (kernel: deferred cmd lost/replaced)\n", wrap_overwrites); return 0; } |