DF-2942 / sysref_probe.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 | /* * sysref_probe.c -- kern_sysref.c layer probes for DF-2941 / DF-2942. * * DF-2942 (resurrection): sysref_activate()'s KASSERT at * kern_sysref.c:280 accepts count == -0x40000000 -- the exact state of an * object that just entered termination-in-progress (the 1 -> -0x40000000 * transition at kern_sysref.c:323). Trigger A drives a cdev-style object * into termination, then calls sysref_activate() *inside* the terminate * callback (simulating a racing misuser). The layer KASSERT passes, the * object is resurrected to refcnt +1, and the protocol then runs the * terminate callback a SECOND time (double termination). For any real * class (e.g. devfs_cdev_terminate) the second terminate re-executes the * class teardown (double devfs_release_ops, double unlock) -- here we only * count and log it, deterministically, without guest corruption. * * DF-2941 (put-side floor): _sysref_put()'s branch * `else if (count > -0x40000000)` (kern_sysref.c:329) also accepts * count == 0, so a double-release walks a putaway object 0 -> -1 with no * structural guard; the ONLY barrier is the debug-only KKASSERT at * kern_sysref.c:303 reading SRF_PUTAWAY non-atomically w.r.t. the * refcnt cmpset (:346) -> flags RMW (:348) pair. Trigger B: * B1: normal alloc/activate/get/put/put cycle (protocol baseline); * B2: fault injection emulating a put that enters _sysref_put between * :346 and :348 (SRF_PUTAWAY not yet visible): the double-release * is ACCEPTED, refcnt walks 0 -> -1, logged live; * B3: a real post-putaway double-put: the KKASSERT fires -> guest * panic (proving the guard is debug-kernel-only; production builds * compile it out and take the B2 path silently). * * Build: see build.sh. Run: kldload ./sysref_probe.ko (root). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/module.h> #include <sys/lock.h> #include <sys/sysctl.h> #include <sys/sysref.h> #include <sys/sysref2.h> struct probe_obj { struct sysref sr; uint64_t magic; char pad[64]; }; MALLOC_DEFINE(M_SYSPROBE, "sysprobe", "sysref layer probe objects"); static struct lock probe_lk; static int term_count; static int resurrect_armed; static struct probe_obj *pa; static struct probe_obj *pb; static void probe_terminate(void *data); static void probe_lock(void *data); static void probe_unlock(void *data); static struct sysref_class probe_class = { .name = "sysprobe", .mtype = M_SYSPROBE, .proto = 0, .offset = offsetof(struct probe_obj, sr), /* 0 */ .objsize = sizeof(struct probe_obj), .nom_cache = 4, .flags = 0, .oc = NULL, .ctor = NULL, .dtor = NULL, .ops = { .terminate = probe_terminate, .lock = probe_lock, .unlock = probe_unlock, }, }; static void probe_lock(void *data) { lockmgr(&probe_lk, LK_EXCLUSIVE); } static void probe_unlock(void *data) { lockmgr(&probe_lk, LK_RELEASE); } /* * Mirrors devfs_cdev_terminate() protocol shape: the callback is invoked * with ops.lock held (acquired in _sysref_put's count==1 branch) and is * responsible for unlocking, then dropping the terminal reference. */ static void probe_terminate(void *data) { struct probe_obj *o = data; int n; n = ++term_count; kprintf("sysprobe: terminate#%d obj=%p entry refcnt=%d\n", n, o, o->sr.refcnt); if (o == pa && resurrect_armed) { /* DF-2942: resurrect the terminating object (ONE-SHOT: * run 1 showed that re-activating on every terminate loops * unbounded -- 292+ re-terminations until kernel stack * exhaustion / double fault. See run.1.unbounded.log). */ resurrect_armed = 0; kprintf("sysprobe: DF-2942: sysref_activate() on " "TERMINATING obj, before=%d\n", o->sr.refcnt); sysref_activate(&o->sr); kprintf("sysprobe: DF-2942: after activate refcnt=%d " "(RESURRECTED; kern_sysref.c:280 KASSERT passed, " "no diagnostic)\n", o->sr.refcnt); } probe_unlock(data); sysref_put(&o->sr); /* terminal ref */ kprintf("sysprobe: terminate#%d exit refcnt=%d\n", n, o->sr.refcnt); } static void trigger_A(void) { kprintf("sysprobe: === DF-2942 trigger A: activate/termination " "negative-space ambiguity ===\n"); resurrect_armed = 1; pa = sysref_alloc(&probe_class); kprintf("sysprobe: A: alloc refcnt=%d magic=%llx\n", pa->sr.refcnt, (unsigned long long)pa->magic); sysref_activate(&pa->sr); kprintf("sysprobe: A: activate refcnt=%d\n", pa->sr.refcnt); sysref_get(&pa->sr); sysref_put(&pa->sr); kprintf("sysprobe: A: get/put refcnt=%d\n", pa->sr.refcnt); kprintf("sysprobe: A: final put -> termination-in-progress...\n"); sysref_put(&pa->sr); kprintf("sysprobe: A: result term_count=%d refcnt=%d\n", term_count, pa->sr.refcnt); if (term_count != 2) kprintf("sysprobe: A: UNEXPECTED term_count\n"); else kprintf("sysprobe: A: CONFIRMED DOUBLE TERMINATION " "(terminate callback ran twice for one object)\n"); pa = NULL; } static void trigger_B(void) { kprintf("sysprobe: === DF-2941 trigger B: put-side count==0 " "acceptance ===\n"); term_count = 0; pb = sysref_alloc(&probe_class); sysref_activate(&pb->sr); sysref_get(&pb->sr); sysref_put(&pb->sr); sysref_put(&pb->sr); kprintf("sysprobe: B1: normal cycle refcnt=%d flags=%04x " "term_count=%d (putaway)\n", pb->sr.refcnt, pb->sr.flags, term_count); /* * B2: fault injection. A put racing the final release between the * refcnt cmpset (kern_sysref.c:346) and the SRF_PUTAWAY flags RMW * (:348) sees exactly this state: count==0, PUTAWAY not visible. * Emulate it, then issue the double-release. */ pb->sr.flags &= ~SRF_PUTAWAY; kprintf("sysprobe: B2: injected pre-:348 visibility; issuing " "double-put\n"); sysref_put(&pb->sr); kprintf("sysprobe: B2: layer ACCEPTED double-release: refcnt=%d " "(walked 0 -> -1; `count > -0x40000000` branch, no floor guard)\n", pb->sr.refcnt); /* repair so the objcache state stays consistent for B3 */ pb->sr.refcnt = 0; pb->sr.flags |= SRF_PUTAWAY; kprintf("sysprobe: B2: repaired refcnt=%d flags=%04x\n", pb->sr.refcnt, pb->sr.flags); } static int sysref_probe_modevent(struct module *m, int what, void *arg) { switch (what) { case MOD_LOAD: lockinit(&probe_lk, "sysprobe", 0, 0); trigger_A(); trigger_B(); kprintf("sysprobe: triggers A+B complete; now DF-2941 B3: " "real post-putaway double-put (expect KKASSERT panic " "at kern_sysref.c:303 on this INVARIANTS kernel)\n"); sysref_put(&pb->sr); kprintf("sysprobe: B3: UNREACHED (accepted silently)\n"); return (0); default: return (EOPNOTSUPP); } } static moduledata_t sysref_probe_mod = { "sysref_probe", sysref_probe_modevent, NULL }; DECLARE_MODULE(sysref_probe, sysref_probe_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(sysref_probe, 1); |