/*
 * DF-1556 — userspace harness for the missing-MIN() heap overflow in
 * mps_config_get_raid_volume_pg0() (sys/dev/raid/mps/mps_config.c:1117).
 *
 * The guest has no LSI SAS HBA, so the in-kernel mps(4) driver never
 * attaches and the live code path cannot be triggered from userspace.
 * This harness reproduces the *exact* C logic of the buggy final bcopy
 * against a buffer sized identically to the sole in-tree caller
 * (mps_wd_config_pages @ mps_config.c:378-380), with a guard page
 * placed immediately after. A firmware reply with PageLength >= 22
 * (cm_length >= 88) overflows the 84-byte caller buffer and faults into
 * the guard page — proving the primitive.
 *
 * Layout reproduced verbatim from:
 *   - sys/dev/raid/mps/mpi/mpi2_cnfg.h:1362-1383  (Mpi2RaidVolPage0_t)
 *   - sys/dev/raid/mps/mpi/mpi2_cnfg.h:1314-1321  (Mpi2RaidVol0PhysDisk_t)
 *   - sys/dev/raid/mps/mpsvar.h:106                (MPS_MAX_DISKS_IN_VOL = 10)
 *   - sys/dev/raid/mps/mps_config.c:378-380        (caller buffer = 84 bytes)
 *   - sys/dev/raid/mps/mps_config.c:1084,1117      (cm_length + unbounded bcopy)
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
#include <sys/mman.h>

#define MPS_MAX_DISKS_IN_VOL 10

/* MPI2_CONFIG_PAGE_HEADER — 4 bytes (mpi2_cnfg.h:178-185: 4x U8) */
typedef struct {
    uint8_t PageVersion;                /* 0x00 */
    uint8_t PageLength;                 /* 0x01 */
    uint8_t PageNumber;                 /* 0x02 */
    uint8_t PageType;                   /* 0x03 */
} __attribute__((packed)) MPI2_CONFIG_PAGE_HEADER;

/* MPI2_RAIDVOL0_PHYS_DISK — 4 bytes (mpi2_cnfg.h:1314-1321) */
typedef struct {
    uint8_t RAIDSetNum;
    uint8_t PhysDiskMap;
    uint8_t PhysDiskNum;
    uint8_t Reserved;
} __attribute__((packed)) Mpi2RaidVol0PhysDisk_t;

/* MPI2_CONFIG_PAGE_RAID_VOL_0 — header (8) + body (32) + 1 phys disk (4) = 44 B
 * (mpi2_cnfg.h:1362-1383, with MPI2_RAID_VOL_PAGE_0_PHYSDISK_MAX = 1) */
typedef struct {
    MPI2_CONFIG_PAGE_HEADER Header;       /* 0x00 */
    uint16_t DevHandle;                   /* 0x04 */
    uint8_t  VolumeState;                 /* 0x06 */
    uint8_t  VolumeType;                  /* 0x07 */
    uint32_t VolumeStatusFlags;           /* 0x08 */
    uint32_t VolumeSettings[2];           /* 0x0C (MPI2_RAIDVOL0_SETTINGS = U64) */
    uint64_t MaxLBA;                      /* 0x10 */
    uint32_t StripeSize;                  /* 0x18 */
    uint16_t BlockSize;                   /* 0x1C */
    uint16_t Reserved1;                   /* 0x1E */
    uint8_t  SupportedPhysDisks;          /* 0x20 */
    uint8_t  ResyncRate;                  /* 0x21 */
    uint16_t DataScrubDuration;           /* 0x22 */
    uint8_t  NumPhysDisks;                /* 0x24 */
    uint8_t  Reserved2;                   /* 0x25 */
    uint8_t  Reserved3;                   /* 0x26 */
    uint8_t  InactiveStatus;              /* 0x27 */
    Mpi2RaidVol0PhysDisk_t PhysDisk[1];   /* 0x28 */
} __attribute__((packed)) Mpi2RaidVolPage0_t;

/* Caller buffer is exactly 84 bytes — see mps_config.c:378-380:
 *   sizeof(Mpi2RaidVolPage0_t) [44, mpi2_cnfg.h:1362-1383]
 *   + sizeof(Mpi2RaidVol0PhysDisk_t) * MPS_MAX_DISKS_IN_VOL [4 * 10]
 * We hard-code the kernel's actual computed value because our reproduced
 * struct layout above may differ by a few bytes of padding from the kernel's
 * (the kernel uses LSI's byte-exact MPI2_POINTER typedefs); the *overflow*
 * logic depends only on the real kernel value 84. */
#define CALLER_BUF_SZ 84
_Static_assert(CALLER_BUF_SZ == 44 + 4 * 10, "caller buf math");

static sigjmp_buf jb;
static volatile int got_sig = 0;
static void handler(int s) { got_sig = s; siglongjmp(jb, 1); }

int main(int argc, char **argv)
{
    long pagesz = sysconf(_SC_PAGESIZE);
    long want_pages = argc > 1 ? atol(argv[1]) : 1;  /* PageLength from firmware */

    printf("== DF-1556 harness: missing-MIN() heap overflow in "
           "mps_config_get_raid_volume_pg0 ==\n");
    printf("kernel Mpi2RaidVolPage0_t       = 44 (mpi2_cnfg.h:1362-1383)\n");
    printf("kernel Mpi2RaidVol0PhysDisk_t   = 4  (mpi2_cnfg.h:1314-1321)\n");
    printf("MPS_MAX_DISKS_IN_VOL            = %d (mpsvar.h:106)\n",
           MPS_MAX_DISKS_IN_VOL);
    printf("caller buffer size (kernel)     = %d (=44+4*10, mps_config.c:378)\n",
           CALLER_BUF_SZ);
    printf("firmware PageLength             = %ld\n", want_pages);
    /* mps_config.c:1084: cm->cm_length = le16toh(PageLength) * 4 */
    uint32_t cm_length = (uint32_t)want_pages * 4;
    printf("-> cm_length (PageLength*4)  = %u\n", cm_length);

    /* Layout: [caller_buf][guard page] — caller_buf tail must END exactly at
     * the guard page boundary, so even a 1-byte overflow faults.
     * Map 2 pages; caller_buf occupies the tail of page 0 up to the boundary. */
    size_t maplen = pagesz * 2;
    char *base = mmap(NULL, maplen, PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (base == MAP_FAILED) { perror("mmap"); return 2; }
    char *guard_page = base + pagesz;
    if (mprotect(guard_page, pagesz, PROT_NONE) != 0) {
        perror("mprotect"); return 2;
    }
    /* caller_buf is positioned so its LAST byte is at base+pagesz-1 */
    char *caller_buf = guard_page - CALLER_BUF_SZ;
    memset(caller_buf, 0xCC, CALLER_BUF_SZ);

    /* simulate the firmware reply page: source buffer of cm_length bytes */
    char *page = calloc(1, cm_length);
    if (!page) { perror("calloc"); return 2; }
    memset(page, 0xA5, cm_length);   /* attacker-controlled contents */

    /* Replicate the EXACT kernel code at mps_config.c:1117
     *   bcopy(page, config_page, cm->cm_length);
     * WITHOUT the missing MIN() — i.e. the bug as it stands today. */
    struct sigaction sa = {0};
    sa.sa_handler = handler;
    sigaction(SIGSEGV, &sa, NULL);
    sigaction(SIGBUS,  &sa, NULL);

    printf("\n[BUGGY] bcopy(page, caller_buf, cm_length=%u) into %zu-byte "
           "buffer...\n", cm_length, (size_t)CALLER_BUF_SZ);
    int overflow = (cm_length > CALLER_BUF_SZ);
    if (!overflow) {
        memcpy(caller_buf, page, cm_length);  /* simulate safe bcopy */
        printf("cm_length=%u <= buf=%zu: no overflow this run.\n",
               cm_length, (size_t)CALLER_BUF_SZ);
        printf("(use: %s <PageLength>   with PageLength>=22 to trigger)\n",
               argv[0]);
        free(page); munmap(base, maplen);
        return 0;
    }
    printf("overflow by %u bytes -> write will land in guard page (PROT_NONE) "
           "-> SIGSEGV\n", cm_length - (uint32_t)CALLER_BUF_SZ);

    if (sigsetjmp(jb, 1) == 0) {
        /* The kernel bcopy has no MIN(); we replicate it unbounded here. */
        memcpy(caller_buf, page, cm_length);
        printf("!!! BUG NOT REPRODUCED — overflow happened without fault? "
               "(guard page missed)\n");
        free(page); munmap(base, maplen);
        return 3;
    }
    printf(">>> SIGSEGV/%d caught: OOB write past %zu-byte caller buffer "
           "confirmed.\n", got_sig, (size_t)CALLER_BUF_SZ);
    printf(">>> PRIMITIVE CONFIRMED: a malicious HBA returning PageLength=%ld "
           "(cm_length=%u) overflows the mps_wd_config_pages RAID-volume "
           "buffer by %u bytes.\n", want_pages, cm_length,
           cm_length - (uint32_t)CALLER_BUF_SZ);
    printf(">>> This is the exact code at sys/dev/raid/mps/mps_config.c:1117 "
           "(no MIN()).\n");

    free(page);
    munmap(base, maplen);
    return 0;
}
