โฌข DragonFlyBSD Kernel Audit
DF-0955 / vm_contig_stress.c
โ† back to finding โ†“ download raw
/*
 * DF-0955 โ€” vm_contig_pg_alloc size=0 panic stress test.
 *
 * Bug: in sys/vm/vm_contig.c:398-426, the alloc loop calls
 * vm_contig_pg_free(start, (i - start) * PAGE_SIZE) on failure. On the
 * first iteration (i==start), the size argument is 0. vm_contig_pg_free
 * (vm_contig.c:489-496) does:
 *
 *    size = round_page(size);
 *    if (size == 0)
 *        panic("vm_contig_pg_free: size must not be 0");
 *
 * Triggering the panic requires:
 *   1. The verify loop (:377) to succeed on a candidate page range
 *   2. Between verify (:377) and alloc (:398) โ€” NO lock is held โ€”
 *      a concurrent allocator/fault/pageout makes the FIRST page of
 *      the range busy, OR moves it out of PQ_FREE, OR bumps hold_count.
 *   3. The alloc loop's first iteration then takes the failure branch,
 *      calling vm_contig_pg_free(start, 0) โ†’ panic.
 *
 * User-reachable paths into vm_contig_pg_alloc:
 *   - /dev/cpuctl CPUCTL_UPDATE (SYSCAP_NOCPUCTL_UPDATE = root)
 *   - DRM/ioctl paths (root or video group)
 *   - netmap (root or netmap access)
 *   - Indirectly via any contigmalloc/contigmalloc_map/kmem_alloc_contig
 *
 * This guest has no /dev/cpuctl, no /dev/netmap, no active DRM. So the
 * direct trigger paths are absent. This stress test instead drives
 * heavy memory pressure (background swap churn via mmap+munmap of
 * large regions) to force the page daemon into heavy activity, hoping
 * to widen the verify->alloc race window for any kernel-internal
 * contigmalloc. The race is AC:High; success is not guaranteed.
 */

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

#define NTHREADS 4
#define REG_MB    64
#define RUN_SEC   60

static volatile int stop = 0;

static void *churn(void *arg) {
    size_t sz = REG_MB * 1024 * 1024;
    while (!stop) {
        void *p = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0);
        if (p == MAP_FAILED) continue;
        /* touch every page to force allocation + pressure */
        memset(p, 0x41, sz);
        madvise(p, sz, MADV_DONTNEED);
        munmap(p, sz);
    }
    return NULL;
}

int main(void) {
    printf("[*] DF-0955 vm_contig_pg_alloc size=0 panic stress test\n");
    printf("[*] %d threads x %dMB churn, run %ds\n", NTHREADS, REG_MB, RUN_SEC);
    printf("[*] running as: uid=%d\n", getuid());

    pthread_t t[NTHREADS];
    for (int i = 0; i < NTHREADS; i++)
        pthread_create(&t[i], NULL, churn, NULL);

    printf("[*] stressing for %d seconds (panic may occur on vulnerable kernel)...\n", RUN_SEC);
    sleep(RUN_SEC);
    stop = 1;
    for (int i = 0; i < NTHREADS; i++)
        pthread_join(t[i], NULL);

    printf("[+] no panic observed in this run\n");
    return 0;
}