DF-2849 / gtq_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 | /* * DF-2849 PoC — taskqgroup_detach() leaves the grouptask in an * enqueue-able-but-inconsistent state and clears TASK_NOENQUEUE with an * unsynchronized, non-atomic read-modify-write of ta_flags OUTSIDE the * taskqueue lock (subr_gtaskqueue.c:746-747), racing * grouptaskqueue_enqueue()'s locked "ta_flags |= TASK_ENQUEUED" * (subr_gtaskqueue.c:218-219). * * Observable failure modes of the same root cause: * (1) racer enqueues in the window where gt_taskqueue == NULL * -> INVARIANTS panic("queue == NULL") / NULL deref otherwise; * (2) racer's locked RMW interleaves with detach's unlocked RMW * -> TASK_ENQUEUED bit lost while the gtask IS in the STAILQ * -> next enqueue double-inserts -> STAILQ self-loop * -> worker thread re-runs the task forever (fn_count keeps * growing after all racers stopped) and starves every other * task on that queue. * * Thread A ("detacher"): attach/detach churn, serialized. * Thread B ("racer"): enqueue churn, skips the NULL-queue window to * chase mode (2); mode (1) still reachable. */ #include <sys/param.h> #include <sys/conf.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/lock.h> #include <sys/gtaskqueue.h> #include <sys/thread.h> #include <sys/thread2.h> static struct taskqgroup *rg; static struct grouptask gt; static volatile int stop; static volatile long enq_ok, enq_eagain, enq_null, fn_count; static struct thread *racer_td, *det_td; static void gt_fn(void *ctx) { atomic_add_long(&fn_count, 1); } static void racer(void *arg) { while (!stop) { /* racy read: skip the obvious NULL window (mode 1) to * hunt the flag lost-update (mode 2) */ struct gtaskqueue *q = gt.gt_taskqueue; if (q == NULL) { atomic_add_long(&enq_null, 1); continue; } if (grouptaskqueue_enqueue(q, >.gt_task) == 0) atomic_add_long(&enq_ok, 1); else atomic_add_long(&enq_eagain, 1); } lwkt_exit(); } static void detacher(void *arg) { int i, loops = 200000; for (i = 0; i < loops && !stop; i++) { taskqgroup_attach(rg, >, NULL, NULL, NULL, "gt"); taskqgroup_detach(rg, >); } kprintf("gtq_race: detacher finished %d attach/detach cycles\n", i); stop = 1; lwkt_exit(); } static int gtq_race_ev(module_t mod, int type, void *data) { long c1, c2; switch (type) { case MOD_LOAD: rg = taskqgroup_create("gtq_race", 2, 1); if (rg == NULL) return (ENOMEM); GROUPTASK_INIT(>, 0, gt_fn, NULL); gt.gt_taskqueue = NULL; taskqgroup_attach(rg, >, NULL, NULL, NULL, "gt"); lwkt_create(racer, NULL, &racer_td, NULL, TDF_NOSTART, 1, "gtracer"); lwkt_setpri_initial(racer_td, TDPRI_KERN_DAEMON); lwkt_schedule(racer_td); lwkt_create(detacher, NULL, &det_td, NULL, TDF_NOSTART, 2, "gtdetach"); lwkt_setpri_initial(det_td, TDPRI_KERN_DAEMON); lwkt_schedule(det_td); kprintf("gtq_race: racer(cpu1)/detacher(cpu2) running\n"); return (0); case MOD_UNLOAD: /* threads must be done: wait for stop */ while (!stop) tsleep(gtq_race_ev, 0, "gtrwait", hz / 4); /* * Corruption check (mode 2): nobody is enqueueing anymore, * so fn_count must be frozen. A self-looped STAILQ makes * the queue's worker re-run gt forever. */ c1 = fn_count; tsleep(gtq_race_ev, 0, "gtrchk", 2 * hz); c2 = fn_count; kprintf("gtq_race: enq_ok=%ld enq_eagain=%ld enq_null=%ld " "fn_count=%ld -> %ld\n", enq_ok, enq_eagain, enq_null, c1, c2); if (c2 > c1) { kprintf("gtq_race: CORRUPTION PROVEN - task fn still " "running with no enqueuer (STAILQ self-loop)\n"); } else { kprintf("gtq_race: no post-stop growth observed this " "run\n"); } return (0); default: break; } return (EOPNOTSUPP); } DEV_MODULE(gtq_race, gtq_race_ev, NULL); |