/*
 * harness.c - DF-0914 deterministic OOB-read characterization.
 *
 * Transcribes the in_off derivation in ufs_getlbns (ufs_bmap.c:315) and the
 * OOB array READ at ufs_bmaparray (ufs_bmap.c:221) with a poisoned allocator,
 * proving the OOB extent deterministically without needing the live kernel.
 *
 * The key difference from DF-0894's harness: this proves the READ-path OOB at
 * ufs_bmap.c:221 (daddr = bap[in_off]), where the OOB value becomes a disk
 * block address used for a subsequent read (confused-deputy / page-fault).
 *
 * Build: cc -o harness harness.c
 * Usage: ./harness [<bsize> <nindir> <lbn>]
 *   defaults: bsize=16384 nindir=8192 lbn=8203
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

#define UFS_NDADDR 12
#define UFS_NIADDR 3

/*
 * Transcribe ufs_getlbns (ufs_bmap.c:256-328) to compute in_off for a given
 * logical block number under a forged MNINDIR. Returns numlevels and fills
 * the indir array.
 */
struct indir { long in_lbn; int in_off; };

static int
getlbns(long bn, u_long mnindir, struct indir *ap, int *nump)
{
    long blockcnt, metalbn, realbn;
    int i, numlevels, off;
    int64_t qblockcnt;

    numlevels = 0;
    realbn = bn;
    if ((long)bn < 0)
        bn = -(long)bn;
    if (bn < UFS_NDADDR)
        return (0);

    for (blockcnt = 1, i = UFS_NIADDR, bn -= UFS_NDADDR;; i--, bn -= blockcnt) {
        if (i == 0)
            return (-1); /* EFBIG */
        qblockcnt = (int64_t)blockcnt * mnindir;
        if (bn < qblockcnt)
            break;
        blockcnt = qblockcnt;
    }
    metalbn = -(realbn - bn + UFS_NIADDR - i);
    ap->in_lbn = metalbn;
    ap->in_off = off = UFS_NIADDR - i;
    ap++;
    for (++numlevels; i <= UFS_NIADDR; i++) {
        if (metalbn == realbn)
            break;
        off = (bn / blockcnt) % mnindir;
        ++numlevels;
        ap->in_lbn = metalbn;
        ap->in_off = off;
        ++ap;
        metalbn -= -1 + off * blockcnt;
        blockcnt /= mnindir;
    }
    *nump = numlevels;
    return (0);
}

int main(int argc, char **argv)
{
    uint32_t bsize  = (argc > 1) ? (uint32_t)strtoul(argv[1], NULL, 0) : 16384;
    u_long  mnindir = (argc > 2) ? strtoul(argv[2], NULL, 0) : 8192;
    long    lbn     = (argc > 3) ? strtol(argv[3], NULL, 0) : 8203;

    uint32_t buf_entries = bsize / sizeof(uint32_t); /* fs_bsize/4 */
    uint32_t correct_nindir = buf_entries;

    printf("=== DF-0914 OOB-READ harness (ufs_bmaparray READ path) ===\n");
    printf("fs_bsize:          %u\n", bsize);
    printf("correct fs_nindir: %u (= fs_bsize/4 = bap[] entries)\n", correct_nindir);
    printf("forged  fs_nindir: %lu (MNINDIR)\n", mnindir);
    printf("buffer size:       %u bytes = %u ufs_daddr_t entries\n",
           bsize, buf_entries);
    printf("valid bap[] range: 0..%u\n", buf_entries - 1);
    printf("read target lbn:   %ld\n", lbn);
    printf("\n");

    if (mnindir <= buf_entries) {
        printf("WARN: forged nindir (%lu) <= buf entries (%u); no OOB possible\n",
               mnindir, buf_entries);
        return 0;
    }

    struct indir ap[UFS_NIADDR + 1];
    int num;
    int rc = getlbns(lbn, mnindir, ap, &num);
    if (rc != 0) {
        printf("ERROR: getlbns returned %d (EFBIG)\n", rc);
        return 1;
    }
    printf("ufs_getlbns returned numlevels=%d:\n", num);
    for (int i = 0; i < num; i++)
        printf("  xap[%d]: in_lbn=%ld in_off=%d\n", i, ap[i].in_lbn, ap[i].in_off);
    printf("\n");

    if (num < 2) {
        printf("num < 2: block is direct or meta-only; ufs_bmaparray:221 not reached\n");
        return 0;
    }

    /* The last xap entry is what ufs_bmaparray:221 indexes into the indirect
     * block buffer:  daddr = ((ufs_daddr_t*)bp->b_data)[xap[num-1].in_off]
     * The buffer has `buf_entries` valid entries (0..buf_entries-1).
     */
    int idx = ap[num - 1].in_off;
    uint32_t byte_off = (uint32_t)idx * 4;
    printf("ufs_bmaparray:221  daddr = bap[%d]  = bp->b_data + %u\n", idx, byte_off);
    printf("buffer end         = bp->b_data + %u\n", bsize);

    if (idx >= (int)buf_entries) {
        uint32_t oob = byte_off - bsize + 4;
        printf("*** OOB READ: bap[%d] reads %u bytes past the %u-byte buffer ***\n",
               idx, oob, bsize);
        printf("*** (buffer has %u entries 0..%u; index %d is out of bounds) ***\n",
               buf_entries, buf_entries - 1, idx);
        printf("*** OOB value becomes `daddr`, used as disk block address at :241 ***\n");
        printf("*** -> confused-deputy read of arbitrary disk block, or     ***\n");
        printf("***    page-fault panic if OOB memory is unmapped (INVARIANTS)***\n");

        /* simulate with a poisoned buffer to show what gets read */
        unsigned char *buf = calloc(1, bsize + oob + 4096);
        memset(buf + bsize, 0xAB, oob + 4096); /* poison past buffer */
        uint32_t leaked = *(uint32_t *)(buf + byte_off);
        printf("simulated OOB read = 0x%08x (poison pattern from past buffer)\n",
               leaked);
        free(buf);
        return 0;
    } else {
        printf("index %d is IN BOUNDS (0..%u); no OOB\n", idx, buf_entries - 1);
        return 1;
    }
}
