/*
 * DF-1282 harness: OOB heap write via unchecked firmware-controlled
 * DeviceIndex in _mapping_process_dpm_pg0 (mpr_mapping.c).
 *
 * This is an OBJECT-LEVEL proof of the primitive. The mpr driver cannot
 * attach on the audit QEMU guest (no LSI/Avago SAS HBA present), so the
 * vulnerable path is not runtime-reachable here. This harness replicates
 * the exact kernel struct (dev_mapping_table) and the exact unguarded
 * indexing from _mapping_process_dpm_pg0 with an attacker/firmware-
 * controlled DeviceIndex to PROVE the OOB write primitive is real.
 *
 * Cited path: sys/dev/raid/mpr/mpr_mapping.c:2251 (dev_idx source),
 *   :2260-2272 (IR path WITH bounds check), :2327/:2358 (Enc/Slot and
 *   Device-Persistence paths WITHOUT bounds check), :2140 (allocation
 *   sized by max_devices), :2565 (max_devices = MaxTargets + max_volumes).
 *
 * Build: cc -O2 -o harness harness.c
 * Run:   ./harness
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

/* Mirror of sys/dev/raid/mpr/mprvar.h:113 dev_mapping_table */
struct dev_mapping_table {
	uint64_t physical_id;
	uint32_t device_info;
	uint32_t phy_bits;
	uint16_t dpm_entry_num;
	uint16_t dev_handle;
	uint16_t reserved1;
	uint16_t id;
	uint8_t  missing_count;
	uint8_t  init_complete;
	uint8_t  TLR_bits;
	uint8_t  reserved2;
};

#define MAX_DEVICES   264u          /* MaxTargets(256)+max_volumes(8) typical */
#define CANARY        0xDEADBEEFu
#define GUARD_N       64            /* entries of guard after the allocation  */

int main(void)
{
    /* Heap allocation exactly as the kernel does at mpr_mapping.c:2140,
     * followed by a guard region full of canaries. In the kernel, writes
     * past mapping_table[max_devices] corrupt the next slab object. */
    struct dev_mapping_table *table =
        calloc(MAX_DEVICES + GUARD_N, sizeof(*table));
    if (!table) { perror("calloc"); return 1; }
    /* paint the guard region (entries MAX_DEVICES..MAX_DEVICES+GUARD_N-1) */
    for (unsigned g = MAX_DEVICES; g < MAX_DEVICES + GUARD_N; g++)
        memset(&table[g], 0xAA, sizeof(table[g]));

    printf("[DF-1282] mapping_table allocated for max_devices=%u entries "
           "(sizeof(entry)=%zu)\n", MAX_DEVICES, sizeof(*table));

    /* === Replicate the Enc/Slot path of _mapping_process_dpm_pg0 ===
     * dev_idx comes straight from firmware: mpr_mapping.c:2251
     *   dev_idx = le16toh(dpm_entry->DeviceIndex);          // u32, 0..65535
     * IR path bounds-checks it (:2260). Enc/Slot (:2327) and Device
     * Persistence (:2358) do NOT. num_slots is firmware-controlled too
     * (DPM MappingInformation slot field). */
    /* Realistic adjacent-slab case: dev_idx just past max_devices lands the
     * write in the NEXT kernel heap object (the classic exploitation target).
     * A huge dev_idx (e.g. 0x1000) also works but lands far away; the precise
     * overflow shown here is the controlled primitive an attacker grooms for. */
    uint32_t dev_idx   = MAX_DEVICES + 2;  /* firmware-controlled, == max_devices+2 */
    uint16_t num_slots = 4;        /* writes 4 consecutive entries               */
    uint32_t map_idx;
    struct dev_mapping_table *mt_entry;

    printf("[DF-1282] attacker/firmware DeviceIndex=%u, num_slots=%u\n",
           dev_idx, num_slots);

    /* The ONLY guard present in the kernel is for the IR path. The Enc/Slot
     * path reaches the indexing with no check: */
    if (dev_idx >= MAX_DEVICES) {
        printf("[DF-1282] BUG CONFIRMED: dev_idx=%u >= max_devices=%u -> "
               "write at mapping_table[%u] is %zu bytes PAST the allocation "
               "(into the adjacent slab object)\n",
               dev_idx, MAX_DEVICES, dev_idx,
               (size_t)(dev_idx - MAX_DEVICES) * sizeof(*table));
    }

    /* Execute the unguarded loop exactly as mpr_mapping.c:2327-2347 */
    mt_entry = &table[dev_idx];                 /* :2327 OOB pointer */
    for (map_idx = dev_idx; map_idx < (dev_idx + num_slots);
         map_idx++, mt_entry++) {               /* :2328 */
        mt_entry->physical_id   = 0x4141414141414141ULL; /* attacker-shaped */
        mt_entry->phy_bits      = 0x42424242;
        mt_entry->id            = dev_idx;
        mt_entry->dpm_entry_num = 1;
        mt_entry->missing_count = 0;
        mt_entry->device_info   = 0x43434343;   /* MPR_DEV_RESERVED stand-in */
    }

    /* Show the guard region got corrupted (== next slab object trashed) */
    unsigned hits = 0;
    for (unsigned g = MAX_DEVICES; g < MAX_DEVICES + GUARD_N; g++) {
        uint8_t *p = (uint8_t *)&table[g];
        uint8_t *end = p + sizeof(table[g]);
        for (; p < end; p++)
            if (*p != 0xAA) { hits++; break; }
    }
    printf("[DF-1282] guard/slab region corrupted: %s (%u of %u entries "
           "touched)\n", hits ? "YES -> next heap object(s) overwritten"
                               : "no", hits, GUARD_N);

    /* Now show the FIX: clamping dev_idx/num_slots prevents the OOB. */
    if (dev_idx >= MAX_DEVICES ||
        dev_idx + num_slots > MAX_DEVICES) {
        printf("[DF-1282] FIX: with bounds check, write SKIPPED "
               "(dev_idx=%u >= max_devices=%u) -> no corruption\n",
               dev_idx, MAX_DEVICES);
    }

    free(table);
    return hits ? 0 : 2;   /* exit 0 == primitive demonstrated */
}
