/*
 * DF-0821 deterministic harness.
 *
 * Transcribes the linear-iterator guard + array-index logic from
 * hammer2_bmap_alloc() (sys/vfs/hammer2/hammer2_freemap.c:616-803) VERBATIM,
 * then drives it with a poisoned bmap->linear value (0x80001000, int32
 * -2147479552) to demonstrate the OOB extent that the KKASSERT at :631-633
 * masks on INVARIANTS-ON (default X86_64_GENERIC) kernels.
 *
 * On the default GENERIC kernel the KKASSERT panics before the OOB; this
 * harness removes the KKASSERT (simulating INVARIANTS-OFF / production) and
 * shows:
 *   - the 3 linear-iterator guards at :616-619 ALL PASS for the negative value
 *   - the resulting array index `i` is NEGATIVE (-4095 for linear=0x80001000)
 *   - bmap->bitmapq[i] reads/writes 32760 bytes BEFORE bitmapq[0] (OOB)
 *
 * The harness uses a "poisoned allocator": bitmapq[] is embedded in a struct
 * preceded by a poison canary, so the OOB read into the negative index is
 * visibly reading the canary (proving the out-of-bounds extent).
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

/* --- hammer2 constants (sys/vfs/hammer2/hammer2_disk.h) -------------------- */
#define HAMMER2_FREEMAP_LEVEL0_RADIX  22
#define HAMMER2_SEGSIZE               (1 << HAMMER2_FREEMAP_LEVEL0_RADIX) /* 4MB */
#define HAMMER2_FREEMAP_BLOCK_RADIX   14
#define HAMMER2_FREEMAP_BLOCK_SIZE    (1 << HAMMER2_FREEMAP_BLOCK_RADIX)  /* 16KB */
#define HAMMER2_FREEMAP_BLOCK_MASK    (HAMMER2_FREEMAP_BLOCK_SIZE - 1)    /* 0x3FFF */
#define HAMMER2_BMAP_ELEMENTS         8
#define HAMMER2_ALLOC_MIN             1024
#define HAMMER2_BMAP_BITS_PER_ELEMENT 64

typedef uint64_t hammer2_bitmap_t;

/* --- struct hammer2_bmap_data (hammer2_disk.h:871-885) --------------------- */
struct hammer2_bmap_data {
    int32_t          linear;                          /* 00 */
    uint16_t         class;                           /* 04 */
    uint8_t          reserved06, reserved07;          /* 06-07 */
    uint32_t         reserved08, reserved0C, reserved10, reserved14, reserved18;
    uint32_t         avail;                           /* 1C */
    uint32_t         reserved20[8];                   /* 20-3F */
    hammer2_bitmap_t bitmapq[HAMMER2_BMAP_ELEMENTS];  /* 40-7F */
} __attribute__((packed));

/* Poisoned allocator: place the bmap inside a larger buffer with a known
 * canary BEFORE the bitmapq[] array.  An in-bounds access sees zeros (the
 * bmap is zero-initialized except for the fields we set); an OOB access into
 * a negative bitmapq[] index reads the canary bytes, proving the extent. */
#define CANARY_BYTES  65536
static unsigned char POISON[CANARY_BYTES];

static void test_linear(int32_t linear_in, int radix) {
    /* Simulate the layout: bmap struct placed at the END of the poison buffer
     * so that bitmapq[0] is near the end, and negative indices reach back
     * into the poison canary. */
    struct hammer2_bmap_data *bmap;
    size_t size = (size_t)1 << radix;
    int bmradix, i, j;
    int g1, g2, g3, guard_pass;
    hammer2_bitmap_t bmmask;
    int offset;
    hammer2_bitmap_t oob_read_val;
    long oob_byte_offset;

    /* Place bmap so bitmapq[0] is at POISON + CANARY_BYTES - sizeof(bitmapq).
     * Then bitmapq[-1] reaches back into the poison canary region. */
    bmap = (struct hammer2_bmap_data *)(POISON + CANARY_BYTES - sizeof(*bmap));
    memset(bmap, 0, sizeof(*bmap));
    bmap->linear = linear_in;
    bmap->avail  = HAMMER2_SEGSIZE; /* mark available */
    bmap->class  = 0;               /* matches any allocation class */

    /* bmradix: 2 bits per block for radix <= FREEMAP_BLOCK_RADIX */
    if (radix <= HAMMER2_FREEMAP_BLOCK_RADIX) {
        bmradix = 2;
    } else {
        bmradix = 2 << (radix - HAMMER2_FREEMAP_BLOCK_RADIX);
    }

    /* === VERBATIM guard from hammer2_freemap.c:616-619 === */
    g1 = (((uint32_t)bmap->linear & HAMMER2_FREEMAP_BLOCK_MASK) + size <=
          HAMMER2_FREEMAP_BLOCK_SIZE);
    g2 = ((bmap->linear & HAMMER2_FREEMAP_BLOCK_MASK) != 0);
    g3 = (bmap->linear < HAMMER2_SEGSIZE);
    guard_pass = (g1 && g2 && g3);

    printf("linear=%#010x (int32=%d) size=%zu bmradix=%d\n",
           (unsigned)bmap->linear, bmap->linear, size, bmradix);
    printf("  guard1 ((uint32)linear & MASK) + size <= BLOCK_SIZE : %s  "
           "(0x%x + %zu = %zu <= %d)\n",
           g1 ? "PASS" : "fail",
           (unsigned)((uint32_t)bmap->linear & HAMMER2_FREEMAP_BLOCK_MASK),
           size,
           (size_t)((uint32_t)bmap->linear & HAMMER2_FREEMAP_BLOCK_MASK) + size,
           HAMMER2_FREEMAP_BLOCK_SIZE);
    printf("  guard2 (linear & MASK) nonzero                    : %s  (0x%x)\n",
           g2 ? "PASS" : "fail",
           (unsigned)(bmap->linear & HAMMER2_FREEMAP_BLOCK_MASK));
    printf("  guard3 (int32 linear < SEGSIZE)                   : %s  (%d < %d)\n",
           g3 ? "PASS" : "fail", bmap->linear, HAMMER2_SEGSIZE);
    printf("  => linear-iterator path TAKEN: %s\n", guard_pass ? "YES (BUG)" : "no (bitmap scan)");

    if (!guard_pass) {
        printf("\n");
        return;
    }

    /* === VERBATIM KKASSERT from :631-633 (DISABLED here to simulate noinv) === */
    /* On GENERIC (INVARIANTS ON) this panics right here.  We skip it to show
     * the OOB that follows on a production / noinv kernel. */
    /* KKASSERT(bmap->linear >= 0 && bmap->linear + size <= HAMMER2_SEGSIZE
     *          && (bmap->linear & (HAMMER2_ALLOC_MIN - 1)) == 0); */
    if (!(bmap->linear >= 0)) {
        printf("  [GENERIC] KKASSERT(bmap->linear >= 0) would PANIC here (line 631-633)\n");
        printf("  [noinv]   KKASSERT compiled out; continuing to OOB array index...\n");
    }

    /* === VERBATIM index computation from :634-636 === */
    offset = bmap->linear;
    i = offset / (HAMMER2_SEGSIZE / HAMMER2_BMAP_ELEMENTS);
    j = (offset / (HAMMER2_FREEMAP_BLOCK_SIZE / 2)) & 62;

    printf("  offset = linear          = %d\n", offset);
    printf("  i = offset / %d          = %d   (SEGSIZE/BMAP_ELEMENTS)\n",
           HAMMER2_SEGSIZE / HAMMER2_BMAP_ELEMENTS, i);
    printf("  j = (offset / %d) & 62   = %d\n",
           HAMMER2_FREEMAP_BLOCK_SIZE / 2, j);

    if (i < 0 || i >= HAMMER2_BMAP_ELEMENTS) {
        long elem_bytes = (long)i * (long)sizeof(hammer2_bitmap_t);
        printf("  *** OOB ARRAY INDEX: bitmapq[%d] is %ld bytes %s bitmapq[0] ***\n",
               i,
               elem_bytes < 0 ? -elem_bytes : elem_bytes,
               elem_bytes < 0 ? "BEFORE" : "AFTER");
        /* Demonstrate the OOB read at :727 (test) and OOB write at :803 (set).
         * In the real kernel this reads/writes adjacent kernel heap.  Here we
         * show the read would reach into the poison canary. */
        printf("  [noinv] simulating OOB READ at :727 (bmap->bitmapq[i] & bmmask == 0):\n");
        /* In the real kernel, bmmask has been computed; for radix<=BLOCK it is
         * (1<<bmradix)-1 = 3, shifted left by j. */
        bmmask = ((hammer2_bitmap_t)1 << bmradix) - 1;
        bmmask <<= j;
        oob_read_val = bmap->bitmapq[i];   /* THE OOB READ */
        oob_byte_offset = (long)((unsigned char *)&bmap->bitmapq[i] -
                                 (unsigned char *)&bmap->bitmapq[0]);
        printf("          bitmapq[%d] @ bitmapq[0] + %ld bytes = 0x%016llx\n",
               i, oob_byte_offset, (unsigned long long)oob_read_val);
        printf("          bmmask = 0x%016llx\n", (unsigned long long)bmmask);
        printf("          (bitmapq[i] & bmmask) == 0  -> %s\n",
               (oob_read_val & bmmask) == 0 ? "TRUE (test passes, falls through)" : "FALSE");
        printf("  [noinv] simulating OOB WRITE at :803 (bmap->bitmapq[i] |= bmmask):\n");
        bmap->bitmapq[i] |= bmmask;        /* THE OOB WRITE */
        printf("          wrote 0x%016llx into bitmapq[%d] @ bitmapq[0] + %ld bytes\n",
               (unsigned long long)bmmask, i, oob_byte_offset);
        /* Confirm the canary was modified (proving the OOB reached outside bitmapq). */
        if (oob_byte_offset < 0) {
            /* find first non-zero byte in the canary region before bitmapq[0] */
            unsigned char *start = (unsigned char *)&bmap->bitmapq[0] + oob_byte_offset;
            int hit = 0;
            for (long k = 0; k < (long)sizeof(hammer2_bitmap_t); k++) {
                if (start[k] != 0) { hit = 1; break; }
            }
            printf("          canary region BEFORE bitmapq[0] modified: %s\n",
                   hit ? "YES (OOB write confirmed)" : "no");
        }
    }
    printf("\n");
}

int main(void) {
    /* Fill the poison buffer with a recognizable canary so OOB reads are visible */
    memset(POISON, 0xCD, sizeof(POISON));

    printf("=== DF-0821 harness: hammer2_freemap.c:616-803 linear-iterator OOB ===\n\n");

    printf("--- [1] Control: normal positive linear (8192 = 0x2000), radix=10 (1KB) ---\n");
    test_linear(0x2000, 10);

    printf("--- [2] Control: block-aligned linear (16384 = 0x4000), radix=14 (16KB) ---\n");
    /* Note: linear & MASK == 0 here, so guard2 fails -> bitmap scan path */
    test_linear(0x4000, 14);

    printf("--- [3] BUG: negative linear 0x80001000 (int32 -2147479552), radix=10 ---\n");
    printf("    This is the DF-0821 trigger: passes all 3 guards, OOB index i=-4095.\n");
    test_linear((int32_t)0x80001000, 10);

    printf("--- [4] BUG variant: negative linear 0xFFFF2000 (int32 -57344), radix=10 ---\n");
    printf("    Different negative -> different OOB offset (smaller).\n");
    test_linear((int32_t)0xFFFF2000, 10);

    printf("=== Impact summary ===\n");
    printf("On default GENERIC (INVARIANTS ON): KKASSERT at :631 panics -> DoS.\n");
    printf("On INVARIANTS-OFF / production: OOB read at :727/:749/:785, OOB write at :803.\n");
    printf("For linear=0x80001000: OOB extent is %ld bytes BEFORE bitmapq[0] in kernel heap.\n",
           (long)-((long)((int32_t)0x80001000 / (HAMMER2_SEGSIZE / HAMMER2_BMAP_ELEMENTS)) *
                   (long)sizeof(hammer2_bitmap_t)));
    return 0;
}
