/*
 * harness.c - DF-0820 primitive characterizer.
 *
 * Transcribes the size arithmetic and bzero/init-loop from
 * sys/vfs/ufs/ffs_vfsops.c:680-709 VERBATIM (with `int size` kept as int,
 * exactly as the kernel), but with a bounded "allocation" and a poisoned
 * allocator so we can OBSERVE the corruption extent without segfaulting.
 *
 * The kernel does:
 *   680:   size = fs->fs_cssize;
 *   681:   blks = howmany(size, fs->fs_fsize);
 *   682:   if (fs->fs_contigsumsize > 0)
 *   683:       size += fs->fs_ncg * sizeof(int32_t);     <-- size_t product, int sum
 *   684:   size += fs->fs_ncg * sizeof(uint8_t);
 *   685:   space = kmalloc((u_long)size, ...);
 *   ...
 *   701:   if (fs->fs_contigsumsize > 0) {
 *   702:       fs->fs_maxcluster = lp = space;
 *   703:       for (i = 0; i < fs->fs_ncg; i++)
 *   704:           *lp++ = fs->fs_contigsumsize;          <-- controlled overflow if size wrapped
 *   705:       space = lp;
 *   706:   }
 *   707:   size = fs->fs_ncg * sizeof(uint8_t);           <-- int
 *   708:   fs->fs_contigdirs = (uint8_t *)space;
 *   709:   bzero(fs->fs_contigdirs, size);                <-- bzero(ptr, size_t size)
 *
 * We model the allocation as a fixed-size poisoned buffer (filled with 0xAA
 * guard pattern) and count how many bytes bzero/the loop would write PAST
 * the nominal allocation. This proves the primitive without crashing.
 *
 * Build: cc -o harness harness.c
 * Usage: harness <fs_ncg> <fs_cssize> <fs_contigsumsize> <fs_fsize>
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define howmany(x, y)   (((x)+((y)-1))/(y))

/* Simulated kernel allocation: a poisoned arena of fixed size. We pretend
 * kmalloc returned a pointer in the middle; we count how far the write
 * would extend past the end. */
#define ARENA  (1<<20)   /* 1 MiB poisoned arena to observe the write pattern */

int main(int argc, char **argv){
    if (argc < 5) {
        fprintf(stderr,
            "usage: %s fs_ncg fs_cssize fs_contigsumsize fs_fsize\n", argv[0]);
        fprintf(stderr, "  (decimal; fs_ncg may be negative)\n");
        return 2;
    }
    int32_t fs_ncg = (int32_t)strtoll(argv[1], NULL, 0);
    int32_t fs_cssize = (int32_t)strtoll(argv[2], NULL, 0);
    int32_t fs_contigsumsize = (int32_t)strtoll(argv[3], NULL, 0);
    int32_t fs_fsize = (int32_t)strtoll(argv[4], NULL, 0);

    /* --- verbatim kernel arithmetic --- */
    int size;
    int blks;
    int i;

    size = fs_cssize;                                  /* line 680 */
    if (fs_fsize)
        blks = howmany(size, fs_fsize);                /* line 681 */
    else
        blks = 0;
    if (fs_contigsumsize > 0)
        size += fs_ncg * sizeof(int32_t);              /* line 683 */
    size += fs_ncg * sizeof(uint8_t);                  /* line 684 */

    /* kmalloc((u_long)size) -- model the allocation size */
    unsigned long ksize = (unsigned long)(unsigned int)size; /* (u_long) cast of int */

    printf("=== kernel size arithmetic (int size) ===\n");
    printf("fs_ncg           = %d (0x%08x)\n", fs_ncg, (unsigned)fs_ncg);
    printf("fs_cssize        = %d\n", fs_cssize);
    printf("fs_contigsumsize = %d\n", fs_contigsumsize);
    printf("fs_fsize         = %d\n", fs_fsize);
    printf("size after L680  = %d\n", fs_cssize);
    printf("size after L683  = %d (fs_ncg*4 product as int)\n",
           fs_contigsumsize>0 ? (int)(fs_cssize + fs_ncg*sizeof(int32_t)) : fs_cssize);
    printf("size after L684  = %d   <-- passed to kmalloc as (u_long)\n", size);
    printf("kmalloc((u_long)size) -> kmalloc(%lu) bytes\n", ksize);
    printf("blks (howmany(cssize,fsize)) = %d\n", blks);

    /* --- now simulate the writes into a poisoned arena --- */
    unsigned char *arena = malloc(ARENA);
    if (!arena) { perror("malloc"); return 2; }
    memset(arena, 0xAA, ARENA);
    void *space = arena;       /* the kmalloc return */
    int32_t *lp;

    /* line 701-705: maxcluster init loop writes fs_ncg int32 values of contigsumsize.
     * NOTE: the loop is `for (i = 0; i < fs_ncg; i++)` with i,fs_ncg both int.
     * If fs_ncg < 0 (e.g. -1), the loop body never runs (0 < -1 is false).
     * It only overflows when fs_ncg is a large POSITIVE value. */
    unsigned long loop_writes = 0;
    long long loop_iters = 0;
    if (fs_contigsumsize > 0) {
        lp = (int32_t *)space;
        /* faithful iteration count respecting the `i < fs_ncg` guard */
        if (fs_ncg > 0) {
            loop_iters = fs_ncg;
            loop_writes = (unsigned long)loop_iters * sizeof(int32_t);
            for (i = 0; i < fs_ncg && (unsigned long)i*sizeof(int32_t) < ARENA; i++)
                lp[i] = fs_contigsumsize;
        }
        printf("\n[maxcluster loop L703-704] guard i<fs_ncg(%d): %s\n",
               fs_ncg, fs_ncg > 0 ? "RUNS" : "SKIPPED (fs_ncg<=0)");
        if (fs_ncg > 0) {
            printf("   iters=%lld writes=%lu bytes of value 0x%x into kmalloc(%lu)\n",
                   loop_iters, loop_writes, (unsigned)fs_contigsumsize, ksize);
            printf("   -> %s -> %s\n",
                   loop_writes > ksize ? "OVERFLOWS the kmalloc region" : "fits in kmalloc",
                   loop_writes > ksize ? "controlled-value HEAP CORRUPTION" : "no overflow");
        }
        space = arena + (loop_writes < ARENA ? loop_writes : ARENA);
    }

    /* line 707-709: bzero(fs_contigdirs, size) where size = fs_ncg*1 (int) */
    int bsz = fs_ncg * sizeof(uint8_t);                 /* line 707, int */
    unsigned long bzero_size = (unsigned long)(unsigned int)bsz; /* (u_long)(int) -> bzero size */
    printf("\n[bzero L707-709] size = fs_ncg*1 = %d (int); bzero(ptr, (u_long)%d) = bzero %lu bytes\n",
           bsz, bsz, bzero_size);

    /* model the bzero: count how far past kmalloc it goes */
    unsigned long past_alloc;
    if (loop_writes > 0) {
        /* contigdirs starts after maxcluster region; offset within allocation */
        unsigned long cd_off = ksize > loop_writes ? 0 : 0; /* simplified */
        past_alloc = bzero_size; /* bzero from contigdirs forward */
    } else {
        past_alloc = bzero_size > ksize ? bzero_size - ksize : 0;
    }
    printf("  -> bzero writes %lu bytes; ", bzero_size);
    if (bzero_size > ksize)
        printf("OVERFLOWS kmalloc(%lu) by %lu bytes -> heap corruption\n",
               ksize, bzero_size - ksize);
    else
        printf("fits within kmalloc(%lu)\n", ksize);

    if (bzero_size == (unsigned long)-1 || bzero_size > (ARENA/2)) {
        printf("  >>> UNBOUNDED WRITE: kernel bzero runs until it page-faults on an\n");
        printf("      unmapped page -> deterministic panic (DoS). Synchronous in the\n");
        printf("      mount syscall; no return to userspace -> no clean LPE conversion.\n");
    }

    free(arena);
    return 0;
}
