DF-4276 / df4276_rep.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 | /* * DF-4276 protocol replica โ the cbb_detach()/cbb_event_thread() teardown * races in sys/dev/pccard/pccbb/pccbb.c. * * Replicated protocol (verbatim lock/sleep discipline of the driver): * * cbb_event_thread (pccbb.c:423-489) cbb_detach (pccbb.c:279-306) * ------------------------------------- -------------------------------- * sc->flags |= CBB_KTHREAD_RUNNING; device_delete_children(brdev); * while ((sc->flags & DONE) == 0) { bus_teardown_intr(...); * ...cbb_insert/cbb_removal... sc->flags |= CBB_KTHREAD_DONE; * tsleep(&generic_cv, ...); if (flags & RUNNING) { * } crit_enter(); * sc->flags &= ~RUNNING; wakeup(&generic_cv); * wakeup(sc->event_thread); tsleep(sc->event_thread, * [thread exits] 0, "cbbun", 0); * crit_exit(); * } * * The window under test: between cbb_detach()'s wakeup(&sc->generic_cv) * (pccbb.c:297) and its tsleep(sc->event_thread,...) (pccbb.c:298) there is * NO interlock. If the event thread runs its tail โ wakes from generic_cv, * observes DONE, clears RUNNING, executes wakeup(sc->event_thread) โ while * the detacher is inside that window (preempted / running concurrently on * another CPU), the final wakeup() finds nobody on the sleep queue and is * lost. DFly wakeups are not queued; the detacher then sleeps on the ident * with timeout 0 == forever -> kldunload/detach hangs permanently. * * (A crit section does not close the window: it is per-CPU and does not * stop the event thread running on another CPU, nor preemption between the * two calls in the detacher itself.) * * This replica forces exactly that interleaving deterministically: the * "gap" flag stands in for the preemption/scheduling that opens the window * in the wild. Everything else is the driver's own discipline. * * Companion races in the same teardown (source-traced, see VERDICT.md): * - non-atomic RMW on sc->flags can lose CBB_KTHREAD_DONE; * - device_delete_children() runs BEFORE the thread is stopped, so the * thread can call cbb_insert() -> CARD_ATTACH_CARD(sc->cbdev) / * exca_insert(&sc->exca[0]) on already-deleted children. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/kthread.h> #include <sys/proc.h> #include <sys/serialize.h> #include <sys/thread.h> #include <sys/thread2.h> #include <sys/mplock2.h> #include <machine/clock.h> #define RUNNING_FLAG 0x40000000 /* CBB_KTHREAD_RUNNING */ #define DONE_FLAG 0x80000000 /* CBB_KTHREAD_DONE */ static uint32_t flags; /* sc->flags analog */ static int generic_cv; /* sc->generic_cv analog */ static struct thread *event_td; /* sc->event_thread analog */ static struct thread *detach_td; static volatile int gap; /* forces the unguarded window */ static volatile int done; /* ---- cbb_event_thread tail analog ---- */ static void event_thread(void *arg) { get_mplock(); flags |= RUNNING_FLAG; while ((flags & DONE_FLAG) == 0) { crit_enter(); tsleep(&generic_cv, 0, "cbbgcv", 0); /* pccbb.c:479 */ crit_exit(); } /* Park until the detacher is inside its wakeup->tsleep window. */ while (!gap) tsleep(&gap, 0, "df4276w", 1); flags &= ~RUNNING_FLAG; /* pccbb.c:486 (non-atomic RMW) */ wakeup(event_td); /* pccbb.c:487 โ the final wakeup */ kprintf("df4276: event thread tail complete; final wakeup() " "delivered into the void (detacher not yet asleep)\n"); rel_mplock(); kthread_exit(); } /* ---- cbb_detach analog ---- */ static void detach_thread(void *arg) { get_mplock(); detach_td = curthread; flags |= DONE_FLAG; /* pccbb.c:294 */ if (flags & RUNNING_FLAG) { crit_enter(); wakeup(&generic_cv); /* pccbb.c:297 */ gap = 1; /* window open (preemption point) */ DELAY(200000); /* detacher preempted inside window */ tsleep(event_td, 0, "cbbun", 0); /* pccbb.c:298 */ crit_exit(); } kprintf("df4276: detach COMPLETED -- no lost wakeup\n"); done = 1; wakeup(&done); rel_mplock(); kthread_exit(); } static void reporter(void *arg) { int i; get_mplock(); for (i = 0; i < 5 && !done; i++) { tsleep(&done, 0, "df4276r", hz); kprintf("df4276: detach_td=%p wchan=%p wmesg=\"%s\"\n", detach_td, detach_td->td_wchan, detach_td->td_wmesg ? detach_td->td_wmesg : "(null)"); } if (done) kprintf("df4276: RESULT: no lost wakeup\n"); else kprintf("df4276: RESULT: LOST WAKEUP REPRODUCED -- detacher " "asleep forever in \"%s\"; in the driver this is " "cbb_detach()/kldunload hung permanently\n", detach_td->td_wmesg ? detach_td->td_wmesg : "?"); rel_mplock(); kthread_exit(); } static int df4276_load(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: kthread_create(event_thread, NULL, &event_td, "df4276ev"); kthread_create(detach_thread, NULL, NULL, "df4276det"); kthread_create(reporter, NULL, NULL, "df4276rep"); kprintf("df4276: replica armed\n"); break; default: break; } return (0); } static moduledata_t df4276rep_mod = { "df4276rep", df4276_load, NULL }; DECLARE_MODULE(df4276rep, df4276rep_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(df4276rep, 1); |