/*
 * DF-1919 source-confirmation harness — mrsas_passthru integer truncation
 * size_t -> int in iov_len splits the DMA allocation size from the copyin
 * length, producing a deterministic large kernel heap overflow with
 * attacker-supplied bytes.
 *
 * sys/dev/raid/mrsas/mrsas_ioctl.c:161
 *   int i, adapter, ioctl_data_size, ioctl_sense_size, ret=0;
 *
 * sys/dev/raid/mrsas/mrsas_ioctl.c:228
 *   ioctl_data_size = user_ioc->sgl[i].iov_len;   <-- size_t -> int truncation
 *
 * sys/dev/raid/mrsas/mrsas_ioctl.c:233-235
 *   bus_dma_tag_create(..., ioctl_data_size, 1, ioctl_data_size, ...)
 *                                  ^maxsize    ^maxsegsize    (both int)
 *
 * sys/dev/raid/mrsas/mrsas_ioctl.c:241-242
 *   bus_dmamem_alloc(ioctl_data_tag[i], &ioctl_data_mem[i], ...)  <-- allocates
 *                                                                     ioctl_data_size
 *                                                                     bytes
 *
 * sys/dev/raid/mrsas/mrsas_ioctl.c:255
 *   kern_sge32[i].length = user_ioc->sgl[i].iov_len;  <-- size_t -> u32:
 *                                                          firmware sees the
 *                                                          low 32 bits only.
 *
 * sys/dev/raid/mrsas/mrsas_ioctl.c:258-259
 *   ret = copyin(user_ioc->sgl[i].iov_base, ioctl_data_mem[i],
 *                user_ioc->sgl[i].iov_len);   <-- uses the ORIGINAL size_t
 *                                                  iov_len, NOT ioctl_data_size
 *
 * user_ioc->sgl[i].iov_len is `size_t` (sys/_iovec.h:45) = u64 on amd64.
 * ioctl_data_size is `int` (signed 32).  Picking iov_len = 0x100000008:
 *
 *   ioctl_data_size = (int)0x100000008  = (int)0x00000008 = 8        (no sign)
 *   -> bus_dma_tag_create maxsize=8, bus_dmamem_alloc allocates 8 bytes
 *   kern_sge32[i].length = (u32)0x100000008 = 8                     (firmware
 *                                                                     DMA stays small)
 *   copyin(iov_base, 8-byte-buf, (size_t)0x100000008 = 4 GiB + 8)
 *
 * i.e. the kernel allocates 8 bytes and then copies up to 4 GiB+8 of
 * attacker-supplied data into that 8-byte DMA buffer — a deterministic,
 * attacker-supplied-byte kernel heap overflow.  copyin stops on the first
 * unmapped user page, but a mmap/MADV_POPULATE-preloaded (or simply
 * heap-spray-filled) user buffer keeps it going.  The SGE handoff to the
 * firmware is small (8 bytes), so the firmware itself does not amplify or
 * fault — the corruption is purely kernel-side via copyin.
 *
 * Variant picks: iov_len = 0x100000000 (ioctl_data_size = 0 -> bus_dma_tag
 * refuses 0 maxsize, ENOMEM), iov_len = 0x1FFFFFFFF (ioctl_data_size = -1
 * -> tag create may reject; depends on bus_dma backend).  0x100000008 is
 * the cleanest positive small value.  Picking larger low-32 values like
 * 0x100001000 gives ioctl_data_size = 0x1000 = 4096 (still << iov_len).
 *
 * Why a harness: same Phase-6 blocker as DF-1917/1918 — no LSI MegaRAID
 * SAS HBA in the QEMU audit guest (verified: pciconf -lv lists none;
 * /dev/mrsas* absent), so mrsas_passthru is unreachable at runtime, and
 * /dev/mrsas0 would be 0660 root:operator (mrsas.c:790) which maxx
 * (uid 1001, not in operator) cannot open anyway.
 *
 * Build:  cc -O2 -o harness harness.c
 * Run:    ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#define MRSAS_MFI_FRAME_SIZE 1024   /* mrsas.h:876 -- not used directly here */

/* Userspace-provided iovec length, as the kernel sees it (size_t / u64). */
typedef uint64_t size_t_model;

/* Verbatim mrsas_ioctl.c:161 / 228 / 241 / 255 / 258 arithmetic.
 * Returns: alloc_size (what bus_dmamem_alloc gave), firmware_sge_length
 * (what kern_sge32[i].length becomes), copyin_len (what copyin actually
 * copies), and the resulting overflow = copyin_len - alloc_size. */
struct outcome {
    long alloc_size;
    uint32_t fw_sge_len;
    unsigned long long copyin_len;
    unsigned long long overflow;
};

static struct outcome model(size_t_model iov_len)
{
    int ioctl_data_size;            /* mrsas_ioctl.c:161 */
    struct outcome o;

    /* mrsas_ioctl.c:228: ioctl_data_size = user_ioc->sgl[i].iov_len */
    ioctl_data_size = (int)iov_len; /* explicit size_t->int truncation */

    /* mrsas_ioctl.c:241-242: bus_dmamem_alloc(tag, &mem, ...) allocates
     * ioctl_data_size bytes when ioctl_data_size > 0.  (If <= 0 the tag
     * create fails; we model the success path.) */
    o.alloc_size = (ioctl_data_size > 0) ? (long)ioctl_data_size : -1L;

    /* mrsas_ioctl.c:255: kern_sge32[i].length = iov_len (size_t -> u32) */
    o.fw_sge_len = (uint32_t)iov_len;

    /* mrsas_ioctl.c:258-259: copyin(iov_base, mem, iov_len)  -- uses
     * the ORIGINAL size_t iov_len, NOT ioctl_data_size. */
    o.copyin_len = (unsigned long long)iov_len;
    o.overflow   = (o.alloc_size > 0 && o.copyin_len > (unsigned long long)o.alloc_size)
                       ? o.copyin_len - (unsigned long long)o.alloc_size
                       : 0ULL;
    return o;
}

static void case_(const char *label, size_t_model iov_len)
{
    struct outcome o = model(iov_len);
    printf("  %-22s iov_len=0x%016llx -> ioctl_data_size=%ld, alloc=%ld bytes,\n"
           "                          fw_sge.length=0x%08x, copyin=%llu bytes\n"
           "                          => heap OVERFLOW = %llu bytes %s\n",
           label,
           (unsigned long long)iov_len,
           (long)(int)iov_len, o.alloc_size,
           o.fw_sge_len, o.copyin_len, o.overflow,
           o.overflow ? "(ATTACKER-SUPPLIED BYTES PAST ALLOC)" : "(no overflow)");
}

int main(void)
{
    printf("DF-1919: mrsas_passthru size_t->int truncation in iov_len\n");
    printf("  (mrsas_ioctl.c:161 decl, :228 truncation, :241 alloc,\n");
    printf("   :255 fw_sge.length, :258 copyin)\n\n");

    case_("benign",            0x40ull);
    case_("truncated-small",   0x100000008ull);  /* THE classic: alloc 8, copyin 4GiB+8 */
    case_("truncated-page",    0x100001000ull);  /* alloc 4096, copyin 4GiB+4096 */
    case_("truncated-zero-lo", 0x100000000ull);  /* alloc 0 -> tag fails */
    case_("truncated-neg",     0x1FFFFFFFFull);  /* alloc = -1 -> tag fails */
    case_("truncated-large",   0x200000800ull);  /* alloc 2048, copyin 8GiB+2048 */

    printf("\n  Confirmed: every truncated-small / truncated-page / truncated-large\n");
    printf("  case allocates a small DMA buffer (the int-low-32 of iov_len) and then\n");
    printf("  issues copyin with the ORIGINAL 64-bit iov_len -> deterministic kernel\n");
    printf("  heap overflow with attacker-supplied bytes from mmap'd user memory.\n");
    return 0;
}
