/*
 * harness.c - DF-0887 deterministic primitive characterizer.
 *
 * Transcribes the `ffs_truncate` symlink fast-path verbatim, with a poisoned
 * allocator that places the `struct inode` in a too-small slab and surrounds
 * it with sentinels. This proves, *without* depending on the kernel slab
 * layout, that the on-disk di_size copied verbatim into ip->i_din.di_size
 * drives an OOB write of (di_size - sizeof(i_shortlink)) bytes past the
 * i_shortlink (= di_db) buffer.
 *
 * Source model (sys/vfs/ufs/ffs_inode.c:159-168):
 *     if (ovp->v_type == VLNK &&
 *         (oip->i_size < mnt_maxsymlinklen || oip->i_din.di_blocks == 0)) {
 *         bzero((char *)&oip->i_shortlink, (uint)oip->i_size);
 *         ...
 *     }
 *
 * Layout facts (sys/vfs/ufs/):
 *   inode.h:105   struct ufs1_dinode i_din;   // LAST member of struct inode
 *   inode.h:126   #define i_shortlink  i_din.di_shortlink
 *   dinode.h:111  #define di_shortlink  di_db
 *   dinode.h:83   ufs_daddr_t di_db[UFS_NDADDR];  // UFS_NDADDR=12 -> 48 bytes
 *   dinode.h:114  #define UFS1_MAXSYMLINKLEN ((UFS_NDADDR + UFS_NIADDR) * sizeof(ufs_daddr_t))  // 60
 *
 * Build: cc -o harness harness.c
 * Run:   ./harness <di_size> [<di_blocks>]  (default di_blocks=0)
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define UFS_NDADDR 12
#define UFS_NIADDR 3
#define SHORTLINK_BYTES (UFS_NDADDR * 4)         /* 48 */
#define UFS1_MAXSYMLINKLEN ((UFS_NDADDR + UFS_NIADDR) * 4)   /* 60 */

struct ufs1_dinode {
    uint8_t raw[128];
};
struct inode {
    uint8_t  preamble[64];     /* stand-in for fields before i_din */
    struct ufs1_dinode i_din;
};

#define I_DIN_OFF     64                                  /* offset of i_din */
#define I_SHORTLINK_OFF (I_DIN_OFF + 40)                  /* offset of di_db in inode */
#define SLAB_PAD       4096

int main(int argc, char **argv){
    if (argc < 2) {
        fprintf(stderr,"usage: %s <di_size> [<di_blocks>]\n", argv[0]);
        return 2;
    }
    unsigned long long disize = strtoull(argv[1], NULL, 0);
    int di_blocks = (argc >= 3) ? (int)strtol(argv[2], NULL, 0) : 0;

    /* allocate a "slab" with sentinels; place the inode in the middle */
    uint8_t *slab = malloc(SLAB_PAD + sizeof(struct inode) + SLAB_PAD);
    if (!slab) { perror("malloc"); return 2; }
    memset(slab, 0xAA, SLAB_PAD + sizeof(struct inode) + SLAB_PAD);
    struct inode *oip = (struct inode *)(slab + SLAB_PAD);

    /* forge the inode */
    oip->preamble[0] = 0x55;                  /* marker */
    /* di_size at i_din offset 8 (uint64) */
    uint64_t sz = disize;
    memcpy(&oip->i_din.raw[8], &sz, 8);
    /* di_blocks at i_din offset 104 (int32) */
    int32_t blocks = di_blocks;
    memcpy(&oip->i_din.raw[104], &blocks, 4);

    /* simulate vp->v_mount->mnt_maxsymlinklen (default UFS1 = 60) */
    int mnt_maxsymlinklen = UFS1_MAXSYMLINKLEN;
    int v_type_VLNK = 1;

    printf("=== DF-0887 primitive characterization ===\n");
    printf("i_shortlink buffer size = %d bytes (di_db[UFS_NDADDR=12])\n", SHORTLINK_BYTES);
    printf("UFS1_MAXSYMLINKLEN      = %d bytes\n", UFS1_MAXSYMLINKLEN);
    printf("forged di_size          = %llu bytes\n", disize);
    printf("forged di_blocks        = %d\n", di_blocks);
    printf("mnt_maxsymlinklen       = %d\n", mnt_maxsymlinklen);

    int enter = (v_type_VLNK &&
        (((uint64_t)disize < (unsigned)mnt_maxsymlinklen) || di_blocks == 0));
    printf("fast-path enter (VLNK && (i_size<maxlen || di_blocks==0)) = %s\n",
           enter ? "YES" : "no");

    if (!enter) {
        printf("fast-path NOT taken -> no overflow (set di_blocks=0)\n");
        free(slab);
        return 0;
    }

    /* verbatim bzero(&oip->i_shortlink, (uint)oip->i_size) — but bounded so we
     * don't actually corrupt our own host memory. We count what it WOULD do. */
    unsigned int bzero_len = (unsigned int)disize;
    long overflow_bytes = (long)bzero_len - SHORTLINK_BYTES;
    /* inode tail bytes after i_shortlink within the struct (di_ib[3]+flags+...)
     * di_db ends at +48 within i_din; i_din is 128 bytes -> 80 bytes of tail */
    long tail_in_inode = sizeof(struct inode) - I_SHORTLINK_OFF;  /* past the shortlink start */

    printf("\nbzero length            = %u\n", bzero_len);
    printf("first OOB byte (past shortlink) at inode+%zu\n",
           (size_t)(I_SHORTLINK_OFF + SHORTLINK_BYTES));
    printf("OOB write length        = %ld bytes (di_size - %d)\n",
           overflow_bytes, SHORTLINK_BYTES);
    printf("OOB past end of struct inode = %ld bytes (overflow past inode end)\n",
           bzero_len - tail_in_inode);

    /* actually simulate the bzero but bounded to the slab region, so the sentinels
     * reveal exactly how far past the inode the kernel would write */
    long max_safe = (long)(SLAB_PAD + sizeof(struct inode) + SLAB_PAD) -
                    (long)((uint8_t*)oip - slab) - I_SHORTLINK_OFF;
    long do_bytes = bzero_len;
    if (do_bytes > max_safe) {
        printf("(capping simulated bzero to slab size %ld; real kernel has no cap)\n",
               max_safe);
        do_bytes = max_safe;
    }
    memset((uint8_t*)oip + I_SHORTLINK_OFF, 0x00, (size_t)do_bytes);

    /* count zeroed bytes past the end of struct inode (the OOB into adjacent heap) */
    uint8_t *inode_end = (uint8_t*)oip + sizeof(struct inode);
    uint8_t *shortlink_end = (uint8_t*)oip + I_SHORTLINK_OFF + SHORTLINK_BYTES;
    uint8_t *write_end     = (uint8_t*)oip + I_SHORTLINK_OFF + bzero_len;
    long past_inode = write_end - inode_end;
    long zeroed_past_inode = 0;
    if (past_inode > 0) {
        long capped_end = write_end - slab;
        long capped_max = SLAB_PAD + sizeof(struct inode) + SLAB_PAD;
        long eff_end = capped_end < capped_max ? capped_end : capped_max;
        long eff_start = inode_end - slab;
        for (long i = eff_start; i < eff_end; i++)
            if (slab[i] == 0x00) zeroed_past_inode++;
    }
    printf("\nSIMULATED bzero overflow:\n");
    printf("  bytes written past shortlink end   = %ld\n",
           (long)(write_end - shortlink_end));
    printf("  bytes written past end of inode    = %ld (-> adjacent heap/slab)\n",
           past_inode > 0 ? past_inode : 0);
    printf("  zeroed past inode (within slab)    = %ld\n", zeroed_past_inode);

    /* verdict */
    if (bzero_len > SHORTLINK_BYTES) {
        printf("\nRESULT: OOB WRITE of %ld bytes past the 48-byte i_shortlink buffer.\n",
               overflow_bytes);
        printf("        Heap corruption into M_FFSNODE slab (and beyond) confirmed.\n");
        free(slab);
        return 0;
    } else {
        printf("\nRESULT: bzero in-bounds (no overflow).\n");
        free(slab);
        return 1;
    }
}
