DF-0886 / df0886_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 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 | /* * DF-0886 — deterministic kernel-module harness for the autofs_node_vn * create race ("lost race" KASSERT panic). * * The bug (sys/vfs/autofs/autofs_vnops.c:564-602): autofs_node_vn() * drops an_vnode_lock (line 589) BEFORE sleeping in getnewvnode() (591) * and assigns anp->an_vnode = vp (598) WITHOUT re-taking the lock. * The KASSERT at 597 catches the lost race. Two threads that both * observe an_vnode == NULL and both enter getnewvnode() will trip it. * * This module is a deterministic harness: it finds the mounted autofs * filesystem, NULLs the root node's an_vnode (simulating a reclaim, as * autofs_vnops.c:426 does), then spawns N kthreads that all call the * REAL autofs_node_vn() on the same node simultaneously. Because * getnewvnode() takes at least several microseconds, overlapping calls * guarantee the race fires. With INVARIANTS (default GENERIC), the * loser panics: * * panic: Lost LK_INIT assertion mp == ... (?) [or] * panic: lost race * Stopped at autofs_node_vn+0xNN * * Module-only characterization note: this harness requires root to * kldload. The bug IS reachable from userspace (autofs_nresolve is a * VOP called on stat()/lookup, which any user can issue on a path under * the autofs mountpoint), but the race window is one getnewvnode() call * (~us), so a probabilistic userspace stress needs either vnode-table * pressure to widen it or extreme luck. This module proves the race * in the actual autofs code path deterministically. * * Usage (as root, with autofs already mounted): * kldload ./df0886_race.ko * # -> panic "lost race" in autofs_node_vn * * Build: * make (uses the in-guest /usr/src tree + Makefile below) */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/conf.h> #include <sys/mount.h> #include <sys/vnode.h> #include <sys/mutex2.h> #include <sys/kthread.h> #include <sys/sysctl.h> #include <sys/thread.h> #include <sys/thread2.h> #include <sys/types.h> #include <sys/queue.h> /* Pull in the autofs structures + prototypes (links against autofs.ko). */ #include <vfs/autofs/autofs.h> extern struct mntlist mountlist; #define NRACERS 4 static struct mount *g_mp = NULL; static struct autofs_node *g_anp = NULL; static volatile int g_go = 0; static volatile int g_ndone = 0; /* * Racer kthread: spin until the go flag, then call the REAL * autofs_node_vn() on the shared node. Both threads entering * getnewvnode() with an_vnode == NULL trips the KASSERT. * * Each racer is pinned to a DIFFERENT CPU (kthread_create_cpu) so they * truly run concurrently — spin-waiting (no lwkt_sleep) keeps each CPU * busy so all racers pass the g_go check and enter autofs_node_vn() * within nanoseconds of each other. */ static void racer_thread(void *arg) { struct vnode *vp = NULL; int error; int id = (int)(intptr_t)arg; /* Hard spin-wait for simultaneous start (keeps the CPU hot). */ while (g_go == 0) cpu_ccfence(); error = autofs_node_vn(g_anp, g_mp, LK_EXCLUSIVE, &vp); /* Only the winner reaches here; the loser panicked at KASSERT(597). */ kprintf("DF-0886 racer[%d]: autofs_node_vn rc=%d vp=%p\n", id, error, vp); if (error == 0) { vn_unlock(vp); vrele(vp); } atomic_add_int(&g_ndone, 1); wakeup(&g_ndone); kthread_exit(); } static int df0886_load(struct module *m, int what, void *arg) { struct mount *mp; struct autofs_mount *amp; struct autofs_node *anp; struct thread *tds[NRACERS]; int i, error; switch (what) { case MOD_LOAD: kprintf("DF-0886: harness loaded, scanning for autofs mount...\n"); /* Find the first mounted autofs filesystem. */ TAILQ_FOREACH(mp, &mountlist, mnt_list) { if (strncmp(mp->mnt_stat.f_fstypename, "autofs", MFSNAMELEN) == 0) break; } if (mp == NULL) { kprintf("DF-0886: ERROR: no autofs mount found; " "run mount_autofs first.\n"); return (ENODEV); } amp = VFSTOAUTOFS(mp); anp = amp->am_root; kprintf("DF-0886: autofs mp=%p amp=%p root anp=%p an_vnode=%p\n", mp, amp, anp, anp->an_vnode); /* * Simulate a vnode reclaim (autofs_vnops.c:425-428): NULL * an_vnode under the lock so both racers see NULL. */ mtx_lock_ex_quick(&anp->an_vnode_lock); if (anp->an_vnode != NULL) { kprintf("DF-0886: nulling existing an_vnode=%p " "(simulating reclaim)\n", anp->an_vnode); anp->an_vnode = NULL; } mtx_unlock_ex(&anp->an_vnode_lock); g_mp = mp; g_anp = anp; g_go = 0; g_ndone = 0; /* Spawn racers on different CPUs so they truly run concurrent. */ for (i = 0; i < NRACERS; i++) { error = kthread_create_cpu(racer_thread, (void *)(intptr_t)i, &tds[i], i, "df0886_racer_%d", i); if (error) { kprintf("DF-0886: kthread_create %d failed: %d\n", i, error); return (error); } } /* Brief pause so all racers are spinning on g_go. */ DELAY(100000); /* 100 ms */ /* Let them loose simultaneously. */ kprintf("DF-0886: releasing %d racers -- expect panic\n", NRACERS); g_go = 1; wakeup(&g_go); /* If we survive (no INVARIANTS), wait for racers. */ while (g_ndone < NRACERS) tsleep(&g_ndone, 0, "dfrace_done", hz); kprintf("DF-0886: all racers done (no panic -- " "INVARIANTS probably OFF?).\n"); return (0); case MOD_UNLOAD: return (0); default: return (EOPNOTSUPP); } } static moduledata_t df0886_mod = { "df0886_race", df0886_load, NULL }; DECLARE_MODULE(df0886_race, df0886_mod, SI_SUB_PSEUDO, SI_ORDER_ANY); MODULE_DEPEND(df0886_race, autofs, 1, 1, 1); |