/*
 * harness.c - Userspace replication of the unbounded attribute walk in
 *             ntfs_loadntnode() (sys/vfs/ntfs/ntfs_subr.c:305-320).
 *
 * WHY A HARNESS
 * -------------
 * The live NTFS trigger (mount_ntfs on a crafted image) reaches the buggy
 * loop at mount time before the DF-0786 lockmgr panic (which only fires on
 * directory LOOKUP, not on the initial MFT load). But on a default GENERIC
 * kernel the OOB-read variant may or may not fault depending on what byte
 * pattern lives in the adjacent slab chunk; only the reclen==0 variant is
 * guaranteed to manifest (as a kernel hang). To make the bug deterministic
 * AND to demonstrate the fix in isolation, this harness reproduces the exact
 * walk logic with a guard page immediately after the (4096-byte) record
 * buffer, so any OOB read faults and any reclen==0 spins (caught by an
 * iteration cap that the kernel does NOT have).
 *
 * The harness mirrors the exact struct layout from sys/vfs/ntfs/ntfs.h:
 *   struct attrhdr { u32 a_type; u32 reclen; u8 a_flag; u8 a_namelen;
 *                    u8 a_nameoff; u8 reserved1; u8 a_compression;
 *                    u8 reserved2; u16 a_index; };     // 16 bytes
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness {clean|loop|oob_attroff|oob_reclen} [apply_fix]
 * Exit:   0 = walk completed cleanly (end marker found, or fix rejected bad input)
 *         1 = iteration cap hit (would be infinite loop in kernel)
 *         2 = SIGSEGV caught (would be OOB read in kernel)
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/mman.h>

#define REC_BYTES  4096       /* mirror ntm_bpmftrec=8 sectors for our image */
#define ATTR_END   0xFFFFFFFFu
#define ITER_CAP   100000     /* kernel has no cap; this catches infinite loops */

struct attrhdr {
    uint32_t a_type;
    uint32_t reclen;
    uint8_t  a_flag;
    uint8_t  a_namelen;
    uint8_t  a_nameoff;
    uint8_t  reserved1;
    uint8_t  a_compression;
    uint8_t  reserved2;
    uint16_t a_index;
} __attribute__((packed));

/* Mirror of the buggy walk in ntfs_loadntnode() lines 305-320. */
static int buggy_walk(uint8_t *rec, uint16_t fr_attroff)
{
    int off = fr_attroff;
    struct attrhdr *ap;
    unsigned iters = 0;

    while (1) {
        ap = (struct attrhdr *)(rec + off);
        if (ap->a_type == ATTR_END)
            return 0;                        /* clean end-of-attributes */
        if (++iters > ITER_CAP)
            return 1;                        /* infinite loop (reclen==0 etc) */
        /* ntfs_attrtontvattr(ap) would be called here in the kernel */
        off += ap->reclen;                   /* UNCHECKED: no bound, no !=0 */
    }
}

/* The proposed fix: bound the walk by the record size and reject zero/overflow. */
static int fixed_walk(uint8_t *rec, uint16_t fr_attroff, int recsz)
{
    int off = fr_attroff;
    struct attrhdr *ap;
    unsigned iters = 0;

    if (off <= 0 || off >= recsz)            /* NEW: bound initial offset */
        return -1;
    while (1) {
        if (off + (int)sizeof(struct attrhdr) > recsz)   /* NEW */
            return -1;
        ap = (struct attrhdr *)(rec + off);
        if (ap->a_type == ATTR_END)
            return 0;
        if (ap->reclen < sizeof(struct attrhdr))          /* NEW: reject 0 / tiny */
            return -1;
        if (ap->reclen > recsz - off)                     /* NEW: no overflow past end */
            return -1;
        if (++iters > REC_BYTES)                          /* paranoia cap */
            return -1;
        off += ap->reclen;
    }
}

/* ---- signal handling so we can catch the OOB SIGSEGV cleanly ---- */
static sigjmp_buf jb;
static volatile int got_sig;
static void segv_handler(int sig) { (void)sig; got_sig = SIGSEGV; siglongjmp(jb, 1); }

static uint8_t *rec;       /* the synthetic MFT record */

static void fill_record(const char *mode)
{
    /* start from a clean record: resident attribute + end marker */
    memset(rec, 0, REC_BYTES);
    uint16_t attroff = 0x48;                 /* mirror the sibling generator's 72 */
    memcpy(rec + attroff, "\x90\x00\x00\x00", 4);  /* a_type = NTFS_A_INDXROOT */
    /* reclen field at attroff+4 — set per mode below */
    uint32_t end = ATTR_END;
    memcpy(rec + attroff + 8, &end, 4);      /* placeholder end marker (overwritten) */
    /* place end-of-attributes marker after a 64-byte resident attr body */
    uint32_t real_end = ATTR_END;
    memcpy(rec + attroff + 64, &real_end, 4);

    /* (We don't write fr_attroff into a filerec header; we pass it directly.) */

    if (strcmp(mode, "clean") == 0) {
        uint32_t rl = 64;
        memcpy(rec + attroff + 4, &rl, 4);   /* reclen = 64 -> next is end marker */
    } else if (strcmp(mode, "loop") == 0) {
        uint32_t rl = 0;
        memcpy(rec + attroff + 4, &rl, 4);   /* reclen = 0 -> infinite loop */
    } else if (strcmp(mode, "oob_attroff") == 0) {
        /* caller will pass an out-of-range attroff to the walker */
        uint32_t rl = 64;
        memcpy(rec + attroff + 4, &rl, 4);
    } else if (strcmp(mode, "oob_reclen") == 0) {
        uint32_t rl = 0x1000;                /* reclen = 4096 -> next iter off end */
        memcpy(rec + attroff + 4, &rl, 4);
    } else {
        fprintf(stderr, "unknown mode '%s'\n", mode);
        exit(3);
    }
}

int main(int argc, char **argv)
{
    if (argc < 2) {
        fprintf(stderr, "usage: %s {clean|loop|oob_attroff|oob_reclen} [apply_fix]\n",
                argv[0]);
        return 3;
    }
    const char *mode = argv[1];
    int apply_fix = (argc >= 3 && strcmp(argv[2], "apply_fix") == 0);

    /* Allocate the record with a guard page immediately after it, so any OOB
       read faults deterministically (mimicking the kernel reading past the
       4096-byte kmalloc'd mfrp). */
    /* DragonFly's <unistd.h> doesn't always expose _SC_PAGESIZE; hardcode. */
    long pagesz = 4096;
    size_t mapsz = (REC_BYTES + pagesz - 1) & ~(pagesz - 1);   /* round up */
    uint8_t *base = mmap(NULL, mapsz + pagesz, PROT_READ | PROT_WRITE,
                         MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (base == MAP_FAILED) { perror("mmap"); return 3; }
    if (mprotect(base + mapsz, pagesz, PROT_NONE) != 0) { perror("mprotect"); return 3; }
    /* place rec at the END of the writable region so the guard page is right
       after byte REC_BYTES-1 (truest mirror of kmalloc(4096) on a slab). */
    rec = base + mapsz - REC_BYTES;

    fill_record(mode);

    /* install SIGSEGV handler */
    struct sigaction sa, oldsa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = segv_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGSEGV, &sa, &oldsa);

    /* choose the attroff we pass to the walker */
    uint16_t pass_attroff = 0x48;
    if (strcmp(mode, "oob_attroff") == 0)
        pass_attroff = 0x0FF0;   /* past resident attr list, inside allocation */

    got_sig = 0;
    int rc;
    if (sigsetjmp(jb, 1) == 0) {
        if (apply_fix)
            rc = fixed_walk(rec, pass_attroff, REC_BYTES);
        else
            rc = buggy_walk(rec, pass_attroff);
    } else {
        rc = 2;                                /* SIGSEGV caught */
    }
    sigaction(SIGSEGV, &oldsa, NULL);

    const char *verdict;
    switch (rc) {
        case 0:  verdict = "clean exit (end-of-attributes reached)"; break;
        case 1:  verdict = "ITERATION CAP HIT -> would be infinite loop in kernel"; break;
        case 2:  verdict = "SIGSEGV -> OOB read past record (into adjacent slab in kernel)"; break;
        case -1: verdict = "FIX REJECTED input ( EINVAL in kernel)"; break;
        default: verdict = "unknown"; break;
    }
    printf("mode=%-12s apply_fix=%-3d  -> rc=%d  %s\n",
           mode, apply_fix, rc, verdict);
    return (rc == 0 || rc == -1) ? 0 : rc;
}
