/*
 * DF-0795 deterministic harness.
 *
 * Replicates the stack layout of msdosfs_readdir exactly as the gcc-8.3
 * compiler lays it out in /boot/kernel/msdos.ko (verified by objdump):
 *
 *   -0x250(%rbp) : struct mbnambuf nb          (272 bytes)
 *       +0   nb_len       (size_t / 8 bytes)
 *       +8   nb_last_id   (int / 4 bytes + 4 pad)
 *       +16  nb_buf[256]
 *   -0x140(%rbp) : struct dirent dirbuf        (272 bytes)  <-- overflow target
 *       +0   d_ino (ino_t / 8 bytes)
 *       +8   d_namlen (uint16)
 *       +10  d_type (uint8)
 *       +11  d_unused1
 *       +12  d_unused2 (uint32)
 *       +16  d_name[256]
 *
 * The 4-byte (ASCII) overflow at nb_buf[247..259] lands in dirbuf.d_ino[0..3].
 * The 17-byte (KICONV, all 13 chars 2-byte) overflow lands in
 * dirbuf.d_ino[0..7], d_namlen, d_type, d_unused1, d_unused2, d_name[0].
 *
 * The harness reproduces mbnambuf_write() verbatim with the malicious
 * id=19, count=13 (and count=26 KICONV variant), with sentinel-poisoned
 * stack frames, and prints exactly which bytes are corrupted.
 *
 * Build (userland, 64-bit): cc -O2 -o df0795_harness df0795_harness.c
 * Run:                                       ./df0795_harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>

#define WIN_CHARS  13
#define WIN_MAXLEN 255

struct mbnambuf {
    size_t  nb_len;
    int     nb_last_id;
    char    nb_buf[WIN_MAXLEN + 1];
};

struct dirent_h {
    uint64_t d_ino;
    uint16_t d_namlen;
    uint8_t  d_type;
    uint8_t  d_unused1;
    uint32_t d_unused2;
    char     d_name[256];
};

/* Verbatim transcription of msdosfs_conv.c:1031-1067 (mbnambuf_write). */
static int
mbnambuf_write(struct mbnambuf *nbp, char *name, int id)
{
    char *slot;
    size_t count, newlen;

    if (nbp->nb_len != 0 && id != nbp->nb_last_id - 1) {
        return -1;
    }
    slot = &nbp->nb_buf[id * WIN_CHARS];
    count = strlen(name);
    newlen = nbp->nb_len + count;
    if (newlen > WIN_MAXLEN || newlen > 127) {
        return -1;
    }
    if (count > WIN_CHARS && nbp->nb_len != 0) {
        if ((id * WIN_CHARS + count + nbp->nb_len) > sizeof(nbp->nb_buf))
            return -1;
        memmove(slot + count, slot + WIN_CHARS, nbp->nb_len);
    }
    memcpy(slot, name, count);
    nbp->nb_len = newlen;
    nbp->nb_last_id = id;
    return 0;
}

static void
dump_bytes(const char *label, const unsigned char *p, size_t n)
{
    printf("  %-30s", label);
    for (size_t i = 0; i < n; i++) printf("%02x", p[i]);
    printf("\n");
}

int main(void)
{
    /* Mimic msdosfs_readdir stack: nb immediately followed by dirbuf. */
    struct mbnambuf nb;
    struct dirent_h dirbuf;

    /* ---- ASCII variant: count=13, 4-byte overflow into dirbuf.d_ino ---- */
    memset(&nb,     0xAA, sizeof(nb));       /* poison */
    memset(&dirbuf, 0xBB, sizeof(dirbuf));   /* poison */
    nb.nb_len = 0;
    nb.nb_last_id = -1;

    char name13[14];
    memset(name13, 0, sizeof(name13));
    for (int i = 0; i < 13; i++) name13[i] = 'A' + i;   /* ABCDEFGHIJKLM */

    printf("== DF-0795 ASCII variant (weCnt=0x54, id=19, count=13) ==\n");
    int rc = mbnambuf_write(&nb, name13, 19);
    printf("mbnambuf_write returned %d (0 = OK, overflow happened)\n", rc);
    printf("nb_buf size     = %zu\n", sizeof(nb.nb_buf));
    printf("slot offset     = 19*13 = %d (writes nb_buf[247..259])\n", 19*13);
    printf("overflow bytes  = nb_buf[256..259] = 4 bytes past end\n");
    printf("These 4 bytes land in dirbuf.d_ino low 4 bytes.\n\n");
    dump_bytes("nb_buf[244..259] =", (unsigned char *)&nb.nb_buf[244], 16);

    /* Walk the 4 overflow bytes and report what they hit in dirbuf. */
    printf("byte-by-byte corruption map:\n");
    for (int i = 0; i < 4; i++) {
        unsigned char *p = (unsigned char *)&nb.nb_buf[256 + i];
        /* nb_buf[256+i] is at offset offsetof(dirbuf)+i, i.e. dirbuf byte i */
        ptrdiff_t dir_off = p - (unsigned char *)&dirbuf;
        const char *field =
            dir_off < 8  ? "dirbuf.d_ino" :
            dir_off < 10 ? "dirbuf.d_namlen" :
            dir_off == 10 ? "dirbuf.d_type" :
            dir_off == 11 ? "dirbuf.d_unused1" :
            dir_off < 16 ? "dirbuf.d_unused2" :
                           "dirbuf.d_name";
        printf("  nb_buf[%d] -> dirbuf byte %td (%s) = 0x%02x\n",
               256+i, dir_off, field, *p);
    }
    printf("\n");
    printf("Result: dirbuf.d_ino low 32 bits corrupted with 'HIJK' "
           "(0x4a4b4849 little-endian).\n");
    printf("-> NO control-flow primitive corrupted (no fn ptr, no rbp, no ret addr).\n");
    printf("-> In msdosfs_readdir, dirbuf.d_ino is unconditionally OVERWRITTEN\n");
    printf("   at msdosfs_vnops.c:1684/1694/1698 before vop_write_dirent, so the\n");
    printf("   corruption is discarded.  Bug is real (CWE-787) but contained.\n\n");

    /* ---- KICONV variant: count=26, 17-byte overflow ---- */
    memset(&nb,     0xAA, sizeof(nb));
    memset(&dirbuf, 0xBB, sizeof(dirbuf));
    nb.nb_len = 0;
    nb.nb_last_id = -1;

    char name26[27];
    memset(name26, 0, sizeof(name26));
    for (int i = 0; i < 26; i++) name26[i] = 'a' + (i % 26);  /* abc...z */

    printf("== DF-0795 KICONV variant (weCnt=0x54, id=19, count=26) ==\n");
    /* NB: in real msdosfs, count>WIN_CHARS would trip the shift-check on a
     * SECOND call (nb_len!=0).  On the FIRST call (nb_len==0) the shift
     * check is skipped, so the unchecked memcpy(slot, name, 26) fires. */
    rc = mbnambuf_write(&nb, name26, 19);
    printf("mbnambuf_write returned %d (0 = OK, overflow happened)\n", rc);
    printf("slot offset = 247, count = 26 -> writes nb_buf[247..272]\n");
    printf("overflow bytes = nb_buf[256..272] = 17 bytes past end\n");
    printf("These 17 bytes land in:\n");
    printf("  dirbuf.d_ino (8), d_namlen (2), d_type (1),\n");
    printf("  d_unused1 (1), d_unused2 (4), d_name[0] (1)\n\n");
    dump_bytes("nb_buf[244..271] =", (unsigned char *)&nb.nb_buf[244], 28);

    for (int i = 0; i < 17; i++) {
        unsigned char *p = (unsigned char *)&nb.nb_buf[256 + i];
        ptrdiff_t dir_off = p - (unsigned char *)&dirbuf;
        const char *field =
            dir_off < 8  ? "dirbuf.d_ino" :
            dir_off < 10 ? "dirbuf.d_namlen" :
            dir_off == 10 ? "dirbuf.d_type" :
            dir_off == 11 ? "dirbuf.d_unused1" :
            dir_off < 16 ? "dirbuf.d_unused2" :
                           "dirbuf.d_name";
        printf("  nb_buf[%d] -> dirbuf byte %td (%s) = 0x%02x\n",
               256+i, dir_off, field, *p);
    }
    printf("\nConclusion: KICONV variant corrupts more of dirbuf but STILL\n");
    printf("no control-flow primitive (no fn ptr / rbp / ret addr).\n");
    printf("All corrupted dirbuf fields are unconditionally overwritten by\n");
    printf("msdosfs_readdir before vop_write_dirent uses them.\n");

    return 0;
}
