DF-4606 / cancelrace.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 | /* * DF-4606 mechanism PoC: ata_timeout() mid-callback lock window vs * synchronous callout_cancel() -> permanent channel/kernel deadlock. * * Reproduces the exact kernel-primitive interleaving cited in the finding, * using the same kernel machinery the nata timeout path uses: * * ata-queue.c:93 callout_init_lk(&request->callout, &ch->state_mtx) * ata-queue.c:555-561 ata_timeout: LK_RELEASE -> ATA_LOCKING(UNLOCK) -> * ata_finish() -> LK_EXCLUSIVE (re-acquire) * ata-lowlevel.c:442 ata_end_transaction: callout_cancel() while holding * ch->state_mtx (also ata-all.c:208 ata_reinit, * ata-queue.c:578 ata_fail_requests) * * kern_timeout.c:609-620 executes the callback via * lockmgr(c->lk, LK_EXCLUSIVE|LK_CANCELABLE); qfunc(); lockmgr(c->lk, LK_RELEASE); * kern_timeout.c:888-925 _callout_cancel_or_stop(sync=1): if INPROG, issues * lockmgr(c->lk, LK_CANCEL_BEG) and ssleep()s until the callback returns. * kern_lock.c: LKC_CANCEL only aborts LK_CANCELABLE acquisitions; a plain * LK_EXCLUSIVE re-acquire (ata-queue.c:560) blocks normally. * * => canceller holds lk and sleeps waiting for the callback; the callback is * blocked re-acquiring lk held by the canceller. Circular wait. * * This module deterministically stages that interleaving: * [softclock] cb: holds lk (AUTOLOCK), LK_RELEASE, window opens, * then re-acquires (== ata_timeout shape) * [kthread] canceller: takes lk inside the window, callout_cancel() * * Expected (finding TRUE): "callout_cancel RETURNED" is NEVER printed, the * softclock thread and the canceller sleep forever, and the system wedges * (all timeout-based sleeps stop firing) -- i.e. local DoS. * Expected (finding FALSE): the RETURNED line prints and the module reports * no deadlock. */ #include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/lock.h> #include <sys/callout.h> #include <sys/kthread.h> #include <sys/thread.h> #include <sys/sysctl.h> static struct lock lk; static struct callout co; static volatile int phase; /* 0 armed, 1 window open, 2 canceller holds lk, * 3 finished */ /* progress probe (sysctl df4606.progress) so the blocking point of the * softclock thread is observable even when console drains are wedged: * 1 window opened (after LK_RELEASE), 2 spin passed, 3 pre-lockmgr, * 4 re-acquired (never in the deadlock case) */ static int cb_progress = 0; SYSCTL_INT(_hw, OID_AUTO, df4606_progress, CTLFLAG_RW, &cb_progress, 0, "softclock callback progress"); static void df4606_canceller(void *); /* plays the role of ata_timeout() (ata-queue.c:539-562) */ static void df4606_cb(void *arg) { kprintf("df4606: [softclock] callback: framework holds lk, " "dropping it (== ata-queue.c:557)\n"); lockmgr(&lk, LK_RELEASE); cb_progress = 1; phase = 1; /* * Keep the window open until the canceller has taken lk (ata_timeout * keeps it open for ATA_LOCKING(UNLOCK)+ata_finish(); we only widen * it deterministically). Busy-rendezvous: never sleep in a softclock * callback (its own timeouts are serviced by itself). */ while (phase != 2) cpu_ccfence(); cb_progress = 2; kprintf("df4606: [softclock] callback: re-acquiring lk " "(== ata-queue.c:560) ... last line before wedge if the " "finding holds\n"); cb_progress = 3; lockmgr(&lk, LK_EXCLUSIVE); /* blocks on the canceller */ cb_progress = 4; kprintf("df4606: [softclock] callback: re-acquired (no deadlock)\n"); phase = 3; wakeup(&phase); } /* plays the role of ata_interrupt()/ata_reinit()/ata_fail_requests() calling * callout_cancel() while holding the channel state lock */ static void df4606_canceller(void *arg) { while (phase != 1) tsleep(&phase, 0, "df4606c", 1); /* * Take lk while the callback is inside its window, exactly like a * late ata_interrupt() acquiring ch->state_mtx after ata_timeout() * dropped it at ata-queue.c:557. */ lockmgr(&lk, LK_EXCLUSIVE); kprintf("df4606: [canceller] acquired lk inside the window " "(== late ata_interrupt with state_mtx)\n"); phase = 2; /* release the callback into its re-acquire */ kprintf("df4606: [canceller] calling callout_cancel() " "(== ata-lowlevel.c:442) ...\n"); callout_cancel(&co); kprintf("df4606: [canceller] callout_cancel RETURNED -- " "mechanism refuted, no deadlock\n"); phase = 3; wakeup(&phase); lockmgr(&lk, LK_RELEASE); kthread_exit(); } static int df4606_load(void) { lockinit(&lk, "df4606lk", 0, 0); callout_init_lk(&co, &lk); phase = 0; kthread_create(df4606_canceller, NULL, NULL, "df4606c"); callout_reset(&co, 2, df4606_cb, NULL); kprintf("df4606: armed; watch for the wedge\n"); return (0); } static int df4606_handler(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: return (df4606_load()); case MOD_UNLOAD: /* the callout/thread never finish if the deadlock hit */ return (EBUSY); default: return (0); } } static moduledata_t df4606_mod = { "df4606", df4606_handler, NULL }; DECLARE_MODULE(df4606, df4606_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |