DF-0901 / race_harness.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 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | /* * race_harness.c โ DragonFlyBSD kernel module that deterministically * reproduces the unlocked-traversal-vs-free race pattern in smbfs_node_alloc * (DF-0901). * * smbfs_node_alloc (sys/vfs/smbfs/smbfs_node.c) does: * smbfs_hash_unlock(smp, td); // line 203: DROP lock * if (vget(vp, LK_EXCLUSIVE) != 0) // line 204 * goto retry; * LIST_FOREACH(np2, nhpp, n_hash) { ... } // line 209: UNLOCKED relookup * * while smbfs_reclaim concurrently does: * smbfs_hash_lock(smp, td); // line 304 * LIST_REMOVE(np, n_hash); // line 310 * smbfs_hash_unlock(smp, td); // line 316 * kfree(np, M_SMBNODE); // line 319: FREES the node * * This harness replicates the EXACT pattern: a LIST of M_SMBNODE-allocated * entries (same slab bucket as real smbnodes), one thread doing an unlocked * LIST_FOREACH (simulating line 209), another doing LIST_REMOVE + kfree * (simulating lines 310+319). With INVARIANTS, kfree poisons the freed * memory with WEIRD_ADDR (0xdeadc0de), so the unlocked traversal * dereferences poisoned le_next โ fatal trap / page fault. * * On the FIXED kernel/module (lock held around the traversal), this harness * does NOT panic because Thread B blocks on the lock while Thread A * traverses. * * Build: see Makefile below. Load: kldload ./race_harness.ko */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/queue.h> #include <sys/proc.h> #include <sys/kthread.h> #include <sys/lock.h> #include <sys/module.h> #include <sys/sysctl.h> /* M_SMBNODE is defined in the smbfs module. Define our own compatible type. */ MALLOC_DEFINE(M_RACE_SMBNODE, "race_smbnode", "DF-0901 race harness smbnode sim"); /* * Simulated smbnode โ must be the same size as real struct smbnode so it * lands in the same slab bucket. struct smbnode is ~200+ bytes. * We use a 256-byte structure to be safe. */ struct sim_smbnode { LIST_ENTRY(sim_smbnode) n_hash; /* must be first, matching real smbnode */ char padding[256 - sizeof(LIST_ENTRY(sim_smbnode))]; }; LIST_HEAD(sim_hashhead, sim_smbnode); static struct sim_hashhead sim_hash; static struct lock sim_lock; /* simulates sm_hashlock */ static int sim_running = 0; static int sim_hold_lock = 1; /* set to 1 to hold lock during traversal (the FIX) */ static int num_entries = 32; SYSCTL_INT(_debug, OID_AUTO, race_entries, CTLFLAG_RW, &num_entries, 0, ""); SYSCTL_INT(_debug, OID_AUTO, race_holdlock, CTLFLAG_RW, &sim_hold_lock, 0, "1 = hold lock during traversal (simulates fix), 0 = unlocked (bug)"); static struct thread *trav_td = NULL; static struct thread *free_td = NULL; /* * Thread A: simulates smbfs_node_alloc line 209 โ unlocked LIST_FOREACH. * Repeatedly traverses the hash list reading each entry's fields. */ static void traverse_thread(void *arg) { struct sim_smbnode *np; int iter = 0; while (sim_running) { if (sim_hold_lock) lockmgr(&sim_lock, LK_EXCLUSIVE); /* Unlocked traversal (the bug) โ or locked (the fix) */ LIST_FOREACH(np, &sim_hash, n_hash) { /* Read fields โ on freed/poisoned memory this panics */ volatile char c = ((char *)np)[0]; (void)c; } if (sim_hold_lock) lockmgr(&sim_lock, LK_RELEASE); iter++; if (iter % 10000 == 0) kprintf("DF-0901 traverse: %d iterations\n", iter); } kprintf("DF-0901 traverse_thread exiting after %d iterations\n", iter); wakeup(&trav_td); } /* * Thread B: simulates smbfs_reclaim โ removes + frees entries from the list. */ static void free_thread(void *arg) { struct sim_smbnode *np, *np2; int iter = 0; while (sim_running) { lockmgr(&sim_lock, LK_EXCLUSIVE); /* Remove one entry from the list (simulates LIST_REMOVE at line 310) */ np = LIST_FIRST(&sim_hash); if (np != NULL) { LIST_REMOVE(np, n_hash); } lockmgr(&sim_lock, LK_RELEASE); /* Free it OUTSIDE the lock (simulates kfree at line 319, which is * also outside the hash lock in smbfs_reclaim) */ if (np != NULL) { kfree(np, M_RACE_SMBNODE); /* Allocate a replacement to keep the list populated */ np2 = kmalloc(sizeof(struct sim_smbnode), M_RACE_SMBNODE, M_WAITOK | M_ZERO); lockmgr(&sim_lock, LK_EXCLUSIVE); LIST_INSERT_HEAD(&sim_hash, np2, n_hash); lockmgr(&sim_lock, LK_RELEASE); } iter++; if (iter % 10000 == 0) kprintf("DF-0901 free: %d iterations\n", iter); } kprintf("DF-0901 free_thread exiting after %d iterations\n", iter); wakeup(&free_td); } static int race_load(struct module *m, int what, void *arg) { int error = 0; struct sim_smbnode *np; int i; switch (what) { case MOD_LOAD: LIST_INIT(&sim_hash); lockinit(&sim_lock, "raceh", 0, 0); /* Populate the hash list */ for (i = 0; i < num_entries; i++) { np = kmalloc(sizeof(struct sim_smbnode), M_RACE_SMBNODE, M_WAITOK | M_ZERO); LIST_INSERT_HEAD(&sim_hash, np, n_hash); } sim_running = 1; kprintf("DF-0901: race harness loaded (%d entries, holdlock=%d)\n", num_entries, sim_hold_lock); kprintf("DF-0901: if holdlock=0 (bug), expect UAF panic shortly\n"); error = kthread_create(&traverse_thread, NULL, &trav_td, "race_trav"); if (error) return error; error = kthread_create(&free_thread, NULL, &free_td, "race_free"); if (error) { sim_running = 0; tsleep(&trav_td, 0, "racex", hz); return error; } break; case MOD_UNLOAD: sim_running = 0; /* Wait for threads to exit */ tsleep(&trav_td, 0, "racex1", 5*hz); tsleep(&free_td, 0, "racex2", 5*hz); /* Free remaining entries */ while ((np = LIST_FIRST(&sim_hash)) != NULL) { LIST_REMOVE(np, n_hash); kfree(np, M_RACE_SMBNODE); } lockuninit(&sim_lock); kprintf("DF-0901: race harness unloaded\n"); break; default: break; } return error; } static moduledata_t race_mod = { "race_harness", race_load, NULL }; DECLARE_MODULE(race_harness, race_mod, SI_SUB_EXEC, SI_ORDER_ANY); MODULE_VERSION(race_harness, 1); |