/*
 * DF-1917 source-confirmation harness — mrsas_passthru unbounded sgl_off
 * yields an OOB SGE write past the 1024-byte cmd->frame DMA allocation.
 *
 * sys/dev/raid/mrsas/mrsas_ioctl.c:219-220
 *   kern_sge32 = (struct mrsas_sge32 *)
 *       ((unsigned long)cmd->frame + user_ioc->sgl_off);
 *
 * sys/dev/raid/mrsas/mrsas_ioctl.c:225-255
 *   for (i = 0; i < user_ioc->sge_count; i++) {
 *       ...
 *       kern_sge32[i].phys_addr = (u_int32_t)ioctl_data_phys_addr[i];
 *       kern_sge32[i].length    = user_ioc->sgl[i].iov_len;
 *   }
 *
 * user_ioc->sgl_off is a u_int32_t (mrsas_ioctl.h:83) taken verbatim from
 * userspace and NEVER validated against MRSAS_MFI_FRAME_SIZE (1024,
 * mrsas.h:876).  Each SGE writes 8 bytes (mrsas_sge32 { u32 phys_addr; u32
 * length; }, mrsas.h:1883-1886, #pragma pack(1)) at frame+sgl_off+8*i.
 * With sgl_off=1016 and sge_count=1, the 8-byte SGE occupies bytes
 * 1016..1023 — right at the edge.  sgl_off=1020 spills 4 bytes past the
 * allocation; sgl_off>=1024 spills entirely.  sgl_off=0xFFFFFFFF wraps the
 * pointer to cmd->frame - 1, an arbitrary-offset 8-byte write into kernel
 * heap relative to the DMA allocation.  MAX_IOCTL_SGE=16 (mrsas_ioctl.h:66)
 * multiplies the primitive: a single call can land up to 16*8 = 128 bytes
 * of attacker-offset writes.
 *
 * Why a harness: /dev/mrsas0 is created only when an LSI MegaRAID SAS HBA
 * is detected by the PCI probe (mrsas_attach, mrsas.c:790-792 make_dev).
 * The QEMU audit guest has no such HBA (verified: pciconf -lv lists none;
 * /dev/mrsas* absent), so mrsas_passthru is unreachable at runtime.
 * Additionally the node is created mode 0660 root:operator (mrsas.c:790),
 * and maxx (uid 1001) is not in operator (verified) — so even on a host
 * with the HBA, the unprivileged ceiling is "operator-group member".  The
 * harness reproduces the verbatim buggy arithmetic against a 1024-byte
 * model of cmd->frame.
 *
 * 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 */
#define MAX_IOCTL_SGE        16     /* mrsas_ioctl.h:66 */

struct mrsas_sge32 {                /* mrsas.h:1883-1886 (#pragma pack(1)) */
    uint32_t phys_addr;
    uint32_t length;
};

/* Verbatim mrsas_ioctl.c:219-255 control flow, model cmd->frame as a
 * 1024-byte heap allocation. */
static unsigned int oob_count(const uint32_t sgl_off,
                              const uint32_t sge_count)
{
    unsigned char *frame = calloc(1, MRSAS_MFI_FRAME_SIZE + 256); /* +spill pad */
    unsigned char *base   = frame;
    unsigned int oob = 0;

    /* kern_sge32 = (struct mrsas_sge32 *)((unsigned long)cmd->frame + sgl_off) */
    struct mrsas_sge32 *kern_sge32 =
        (struct mrsas_sge32 *)(base + sgl_off);

    for (unsigned int i = 0; i < sge_count; i++) {
        /* the two unchecked stores at mrsas_ioctl.c:254-255 */
        unsigned char *pa = (unsigned char *)&kern_sge32[i].phys_addr;
        unsigned char *le = (unsigned char *)&kern_sge32[i].length;
        for (unsigned int b = 0; b < sizeof(uint32_t); b++) {
            if (pa - base + b >= MRSAS_MFI_FRAME_SIZE) oob++;
            if (le - base + b >= MRSAS_MFI_FRAME_SIZE) oob++;
        }
    }
    free(frame);
    return oob;
}

static void case_(const char *label, uint32_t sgl_off, uint32_t sge_count)
{
    unsigned int oob = oob_count(sgl_off, sge_count);
    printf("  sgl_off=0x%08x sge_count=%2u -> %u OOB bytes %s\n",
           sgl_off, sge_count, oob,
           oob ? "(OOB WRITE past 1024-byte frame)" : "(in-bounds)");
    (void)label;
}

int main(void)
{
    printf("DF-1917: mrsas_passthru unbounded sgl_off (mrsas_ioctl.c:219-255)\n");
    printf("  cmd->frame is bus_dmamem_alloc'd at MRSAS_MFI_FRAME_SIZE=1024\n");
    printf("  bytes (mrsas.c:451-462 mrsas_create_frame_pool).  sgl_off is a\n");
    printf("  u32 from userspace (mrsas_ioctl.h:83), never validated.\n\n");

    /* In-bounds reference: sgl_off=128, sge_count=1, occupies 128..135 */
    case_("in-bounds",        128u,            1);
    /* Edge case: last byte written is byte 1023 */
    case_("edge",             MRSAS_MFI_FRAME_SIZE - 8u, 1);
    /* Minimal OOB: 4 bytes past end */
    case_("oob-4",            MRSAS_MFI_FRAME_SIZE - 4u, 1);
    /* Fully OOB single SGE */
    case_("oob-full",         MRSAS_MFI_FRAME_SIZE,      1);
    /* Max spill: 16 SGEs starting at offset 1020 -> 4 + 15*8 = 124 bytes OOB */
    case_("oob-max",          MRSAS_MFI_FRAME_SIZE - 4u, MAX_IOCTL_SGE);
    /* Pointer-wrap: same arithmetic as kernel ((unsigned long)base + 0xFFFFFFFF).
     * On amd64 this is base + 4GiB - 1, an arbitrary-offset write into kernel
     * heap relative to the DMA allocation. */
    case_("wrap",             0xFFFFFFFFu,               1);

    printf("\n  Confirmed: every case with OOB bytes > 0 writes attacker-supplied\n");
    printf("  phys_addr/length past the cmd->frame DMA allocation.\n");
    return 0;
}
