/*
 * harness.c - DF-0903 deterministic transcription of the ffs_write math.
 *
 * Reproduces the exact blkoff()/xfersize arithmetic from
 * ufs_readwrite.c:290-356 with a POISONED allocator, WITHOUT requiring a
 * real mount.  Crucially, this harness models the ACTUAL kernel behavior
 * including the size_t/int promotion at line 294-295 that the finding's
 * narrative overlooked:
 *
 *   uio->uio_resid is `size_t` (sys/sys/_uio.h:69), xfersize is `int`
 *   (ufs_readwrite.c:220).  Line 294 `if (uio->uio_resid < xfersize)`
 *   promotes the int to size_t, so a NEGATIVE xfersize becomes a huge
 *   size_t, the comparison is TRUE, and line 295 CLAMPS xfersize to
 *   uio_resid (a small positive).  The negative xfersize therefore never
 *   reaches line 356's uiomove; instead VOP_BALLOC at line 329 is called
 *   with the clamped (small positive) xfersize, and ffs_balloc panics with
 *   "blk too big" because blkoffset+xfersize > fs_bsize.
 *
 * The finding's claimed end-state (OOB write via negative xfersize as
 * size_t at uiomove) is NOT reachable on master.  The real, demonstrated
 * impact is a kernel panic (DoS) via crafted fs_qbmask.
 *
 * Build: cc -o harness harness.c
 * Run:   ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>

/* Mirror the kernel macros from sys/vfs/ufs/fs.h:487-488. */
static int64_t fs_qbmask;          /* attacker-controlled on-disk field */
static int32_t fs_bsize;

#define blkoff(offset)  ((int64_t)(offset) & fs_qbmask)

/* A simulated bp->b_data buffer (kernel buffer of fs_bsize bytes). */
static unsigned char *bp_data;
static size_t bp_bufsize;

static size_t uiomove_oob(unsigned char *cp, size_t n, size_t resid,
                          const unsigned char *src){
    /* Transcribe kern_subr.c uiomove(): loops while n>0 && resid>0, copies
     * min(iov_len, n) per iter.  Here iov_len = resid (single iovec). */
    size_t copied = 0;
    while (n > 0 && resid > 0) {
        size_t cnt = resid;
        if (cnt > n) cnt = n;
        for (size_t i = 0; i < cnt; i++) cp[i] = src[i];
        copied += cnt;
        cp += cnt;
        n -= cnt;
        resid -= cnt;
    }
    return copied;
}

int main(void){
    /* Default reproduction: crafted fs_qbmask = 0x3FFF, fs_bsize = 8192. */
    fs_qbmask = 0x3fff;
    fs_bsize  = 8192;
    bp_bufsize = fs_bsize;
    bp_data = calloc(1, bp_bufsize + 16384); /* +guard */

    /* Sentinel pattern in the guard region. */
    memset(bp_data + bp_bufsize, 0xCC, 16384);

    printf("=== DF-0903 ffs_write math harness (accurate kernel model) ===\n");
    printf("crafted fs_qbmask = 0x%llx (vs normal 0x%x = fs_bsize-1)\n",
           (unsigned long long)fs_qbmask, (unsigned)(fs_bsize - 1));
    printf("fs_bsize = %d\n", fs_bsize);
    printf("buffer bp_data = [%p, %p)  size=%zu\n",
           (void*)bp_data, (void*)(bp_data + bp_bufsize), bp_bufsize);
    printf("\nNOTE: uio_resid is size_t (unsigned); xfersize is int (signed).\n");
    printf("Line 294 `uio_resid < xfersize' promotes xfersize to size_t,\n");
    printf("so negative xfersize becomes huge => comparison TRUE => clamp.\n");

    long long offsets[] = {8192, 8193, 10000, 12000, 16383, 16384};
    size_t uio_resid = 16;   /* pwrite(fd, buf, 16, offset) */
    for (size_t i = 0; i < sizeof(offsets)/sizeof(offsets[0]); i++) {
        long long offset = offsets[i];
        long long blkoffset = blkoff(offset);
        int xfersize_pre = (int)((int64_t)fs_bsize - blkoffset);
        int xfersize = xfersize_pre;
        /* Model line 294-295 with size_t promotion: */
        if ((size_t)uio_resid < (size_t)xfersize)   /* promotion */
            xfersize = (int)uio_resid;              /* clamp */
        printf("\n--- pwrite offset=%lld uio_resid=%zu ---\n", offset, uio_resid);
        printf("  blkoff(offset)         = offset & 0x%llx = %lld\n",
               (unsigned long long)fs_qbmask, blkoffset);
        printf("  xfersize (line 293)    = fs_bsize - blkoffset = %d\n",
               xfersize_pre);
        if (xfersize_pre != xfersize)
            printf("  xfersize (after clamp) = %d  (clamped to uio_resid by line 294-295)\n",
                   xfersize);
        else
            printf("  xfersize (after clamp) = %d  (no clamp)\n", xfersize);

        /* balloc size check (ffs_balloc.c:89-91): */
        long long balloc_size = blkoffset + xfersize;
        printf("  balloc size = blkoffset + xfersize = %lld + %d = %lld",
               blkoffset, xfersize, balloc_size);
        if (balloc_size > fs_bsize) {
            printf(" > fs_bsize=%d => PANIC 'ffs_balloc: blk too big'\n", fs_bsize);
            printf("  >>> ACTUAL KERNEL BEHAVIOR: panic here, never reaches uiomove <<<\n");
            continue;
        }
        printf(" <= fs_bsize => no balloc panic\n");

        /* size clamp at line 352-354 (only reached if balloc passed): */
        int size_352 = fs_bsize - 0; /* assume b_resid=0, BLKSIZE=fs_bsize */
        if (size_352 < xfersize) xfersize = size_352;

        /* uiomove dst: */
        unsigned char *dst = bp_data + blkoffset;
        long long oob_extent = (long long)blkoffset - (long long)bp_bufsize;
        printf("  uiomove dst = bp_data + %lld = %p", blkoffset, (void*)dst);
        if (oob_extent > 0) {
            printf("  -- %lld bytes PAST buffer end\n", oob_extent);
            size_t xfersize_st = (size_t)xfersize;
            unsigned char src[16]; memset(src, 0x41, sizeof(src));
            size_t copied = uiomove_oob(dst, xfersize_st, uio_resid, src);
            printf("  uiomove would copy %zu bytes (defeated by clamp above; unreachable in kernel)\n",
                   copied);
            memset(bp_data + bp_bufsize, 0xCC, 16384);
        } else if (xfersize <= 0) {
            printf("  xfersize=%d (non-positive); ffs_read:147 would panic, ffs_write does not\n",
                   xfersize);
            size_t xfersize_st = (size_t)xfersize;
            unsigned char src[16]; memset(src, 0x41, sizeof(src));
            size_t copied = uiomove_oob(dst, xfersize_st, uio_resid, src);
            printf("  uiomove would copy %zu bytes (defeated by clamp above; unreachable in kernel)\n",
                   copied);
            memset(bp_data + bp_bufsize, 0xCC, 16384);
        } else {
            printf("  in-bounds, xfersize=%d (normal write)\n", xfersize);
        }
    }

    printf("\n=== Conclusion ===\n");
    printf("Crafted fs_qbmask=0x3FFF (vs normal 0x1FFF) is loaded verbatim by\n");
    printf("ffs_mountfs (ffs_vfsops.c:673 bcopy; ffs_oldfscompat only re-derives\n");
    printf("for fs_inodefmt < FS_44INODEFMT, but newfs sets =2).  blkoffset =\n");
    printf("offset & 0x3FFF can exceed fs_bsize=8192, momentarily driving\n");
    printf("xfersize = fs_bsize - blkoffset negative.\n\n");
    printf("HOWEVER the finding's claimed OOB-write chain (negative xfersize as\n");
    printf("size_t at uiomove) is NOT reachable: line 294-295 promotes the int\n");
    printf("xfersize to size_t for the comparison against size_t uio_resid,\n");
    printf("clamping xfersize to a small positive value first.  The clamped\n");
    printf("xfersize then triggers ffs_balloc's unconditional panic\n");
    printf("'ffs_balloc: blk too big' (ffs_balloc.c:91) because\n");
    printf("blkoffset + xfersize > fs_bsize.  Net impact: kernel PANIC (DoS)\n");
    printf("from a root-mounted crafted filesystem -- not a heap OOB write.\n");

    free(bp_data);
    return 0;
}

