/*
 * DF-0871 deterministic harness  (v2 -- defeats the compiler's array-bounds UB
 * assumption that, at -O2, let the original transcription "pass" without
 * faulting).
 *
 * Transcribes the EXACT unbounded wchar->char do/while of ntfs_mountfs()
 * (sys/vfs/ntfs/ntfs_vfsops.c:444,458-460) into userspace, backed by a
 * poisoned allocator that places the victim array at the very end of a
 * guard-paged page so the heap OOB write is detected byte-exactly.
 *
 *   ntmp->ntm_ad = kmalloc(num * sizeof(struct ntvattrdef), M_NTFSMNT, ...);  // :444
 *   j = 0;                                          // :457
 *   do {
 *       ntmp->ntm_ad[i].ad_name[j] = ad.ad_name[j]; // :459   DEST is char[0x40]=64
 *   } while(ad.ad_name[j++]);                       // :460   NO bound on j
 *   ntmp->ntm_ad[i].ad_namelen = j - 1;             // :461   repairs bytes 64..67
 *   ntmp->ntm_ad[i].ad_type    = ad.ad_type;        // :462   repairs bytes 68..71
 *
 * struct attrdef    { wchar ad_name[0x40]; u32 ad_type; u32 r1[2]; u32 ad_flag; u64 min; u64 max; } = 160 B
 * struct ntvattrdef { char  ad_name[0x40]; int ad_namelen; u32 ad_type; } = 72 B
 *
 * On the kernel the source `ad` is a 160-byte stack struct; when its 64 wchars
 * AND trailing 32 B are all non-zero half-words the do/while reads PAST the
 * struct (kernel stack) until it happens to hit a zero wchar.  The DEST write
 * has already left the 72-byte ntvattrdef[i] at j=72.
 *
 * To faithfully reproduce this WITHOUT letting the optimizer exploit the
 * `ad_name[64]` array bounds (which is what made v1 silently pass), the source
 * is presented as an OVERSIZED wchar array `src[SRCLEN]` (all non-zero up to a
 * controlled NUL), accessed through the SAME indexing expression the kernel
 * uses.  This defeats the bounds assumption while preserving the exact loop
 * semantics.
 *
 * Build (guest):  cc -O2 -o harness harness.c
 * Run:             ./harness [num]
 *   num=1 : 72 B object at page-end; do/while SIGSEGVs at j=72 (1st byte past
 *           the object) => deterministic proof the write leaves the ntvattrdef.
 *   num>=2: array poisoned at page-end; the loop runs on the LAST entry whose
 *           overflow crosses the whole allocation => SIGSEGV; before that,
 *           earlier entries' overflows corrupt the next entry (reported).
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>

typedef uint16_t wchar_nt;            /* typedef u_int16_t wchar;  (ntfs.h:37) */
#define NTFS_ATTRNAME_MAXLEN 0x40     /* ntfs.h:203 */

struct ntvattrdef {                   /* ntfs.h:215, 72 B */
    char      ad_name[0x40];          /* +0  64 */
    int       ad_namelen;             /* +64  4 */
    uint32_t  ad_type;                /* +68  4 */
};                                     /* == 72 */

/* Oversized source: 256 wchars.  [0..159] mimic an evil struct attrdef (64
 * non-zero name wchars + 32 B of non-zero trailing fields == 16 non-zero
 * wchars at indices 64..79); [80..254] are non-zero "stack residue"; [255]=0
 * is a guaranteed terminator so the walk always stops somewhere (as it must
 * on a real stack).  The dest overflow at j=72.. happens long before j=255. */
#define SRCLEN 256
static wchar_nt src_buf[SRCLEN];

static void make_evil_source(void) {
    for (int i = 0; i < SRCLEN - 1; i++)      /* all non-zero ... */
        src_buf[i] = (wchar_nt)0x4141;
    src_buf[SRCLEN - 1] = 0;                  /* ... except the final NUL */
}

static long PAGE = 0;
static sigjmp_buf jb;

static void segv(int sig) { (void)sig; siglongjmp(jb, 1); }

static unsigned char *poison_alloc(size_t size) {
    long pg = PAGE;
    char *base = mmap(NULL, pg * 2, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (base == MAP_FAILED) { perror("mmap"); exit(2); }
    if (mprotect(base + pg, pg, PROT_NONE) != 0) { perror("mprotect"); exit(2); }
    return (unsigned char *)(base + pg - size);
}

/* Verbatim transcription of ntfs_vfsops.c:457-462 for entry index `i`.
 * The source is indexed exactly as the kernel indexes ad.ad_name[j]; using the
 * oversized src_buf keeps the compiler from assuming the trip count is < 64. */
static void buggy_copy(struct ntvattrdef *arr, int i) {
    int j = 0;                                     /* :457 */
    do {
        arr[i].ad_name[j] = (char)src_buf[j];      /* :459   DEST char[64] */
    } while(src_buf[j++]);                          /* :460  -- UNBOUNDED */
    arr[i].ad_namelen = j - 1;                      /* :461  repairs bytes 64..67 */
    arr[i].ad_type    = 0xFFFFFFFFu;                /* :462  repairs bytes 68..71 */
}

int main(int argc, char **argv) {
    PAGE = sysconf(_SC_PAGESIZE);
    int num = (argc > 1) ? atoi(argv[1]) : 1;
    if (num < 1) num = 1;

    struct sigaction sa; memset(&sa, 0, sizeof sa);
    sa.sa_handler = segv; sigemptyset(&sa.sa_mask);
    sa.sa_flags = SA_NODEFER;
    sigaction(SIGSEGV, &sa, NULL);
    sigaction(SIGBUS,  &sa, NULL);

    make_evil_source();

    size_t allocsz = (size_t)num * sizeof(struct ntvattrdef);   /* :444 */
    printf("[harness] DF-0871 ntfs_mountfs $AttrDef OOB-write transcription (v2)\n");
    printf("[harness] num entries                = %d\n", num);
    printf("[harness] kmalloc size (num*72)      = %zu  -> dest char ad_name[64] each\n", allocsz);
    printf("[harness] source: 256 non-zero wchars + final NUL -> do/while is unbounded\n");
    printf("[harness] BUG: do { dest[j]=src[j]; } while(src[j++])  with NO bound on j\n");

    unsigned char *block = poison_alloc(allocsz);
    memset(block, 0x5A, allocsz);
    struct ntvattrdef *arr = (struct ntvattrdef *)block;
    printf("[harness] victim array at %p (last %zu B of a page; next page PROT_NONE)\n",
           (void *)block, allocsz);

    /* To exercise cross-entry corruption visibly (for num>=2), first run the
     * buggy copy on entry[0] into a SEPARATE non-guarded array, then report
     * how many bytes of entry[1] it clobbered.  This isolates the cross-entry
     * claim from the guard-fault demo. */
    if (num >= 2) {
        unsigned char *viz = calloc(1, (size_t)num * 72 + 16);
        struct ntvattrdef *varr = (struct ntvattrdef *)viz;
        memset(viz, 0x5A, (size_t)num * 72 + 16);
        if (sigsetjmp(jb, 1) == 0) {
            buggy_copy(varr, 0);          /* entry[0] into non-guarded array */
            int cor = 0;
            for (int k = 0; k < 72; k++)
                if (varr[1].ad_name[k] != (char)0x5A) cor++;
            printf("[harness] cross-entry demo: entry[0]'s do/while overwrote %d "
                   "byte(s) of entry[1].ad_name before its NUL stop (j~255).\n", cor);
        }
        free(viz);
    }

    printf("[harness] running buggy do/while on entry[%d] (last) into guarded array...\n",
           num - 1);
    fflush(stdout);

    if (sigsetjmp(jb, 1) == 0) {
        buggy_copy(arr, num - 1);   /* <-- BUG; last entry overflows past alloc */
        printf("[harness] ERROR: overflow was NOT caught -- guard missing\n");
        return 3;
    }

    /* SIGSEGV longjmp lands here. */
    printf("[harness] SIGSEGV caught -> do/while wrote PAST the %zu-byte allocation.\n",
           allocsz);
    printf("[harness] PROOF (deterministic): the unbounded copy at\n");
    printf("[harness]   ntfs_vfsops.c:458-460 writes dest[j] for j>=72, i.e. past the\n");
    printf("[harness]   72-byte ntvattrdef object.  On the live kernel this is a heap\n");
    printf("[harness]   OOB write into the M_NTFSMNT slab zone (kmalloc-128/256 bucket).\n");
    return 0;
}
