/*
 * DF-3914 harness: firmware-controlled enclosure NumSlots drives an
 * unbounded heap OOB write in the DIRECT-ATTACHED reservation path of
 * _mapping_get_dev_info() / _mapping_get_pcie_dev_info()
 * (sys/dev/raid/mpr/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 SAS3 HBA present - only
 * virtio devices, see env.txt), so the vulnerable kernel path is not
 * runtime-reachable on this guest. This harness replicates the exact
 * kernel structs and the exact unguarded loops from mpr_mapping.c to
 * prove the OOB write primitive is real.
 *
 * Cited path (pass 2, GLM 5.3):
 *   mpr_mapping.c:2843  et_entry->num_slots = le16toh(event_data->NumSlots)
 *                       (only a NumSlots==0 check at :2760; no upper bound)
 *   mpr_mapping.c:1241-1243 (SAS) / :1392-1394 (PCIe)
 *                       if (!topo_change->exp_handle) {
 *                           map_idx = sc->num_rsvd_entries;
 *                           et_entry->start_index = map_idx;   // NO num_slots check
 *                       }
 *   mpr_mapping.c:1292-1299 (SAS) / :1443-1450 (PCIe)
 *                       mt_entry = &sc->mapping_table[map_idx];
 *                       for (index = map_idx;
 *                            index < (et_entry->num_slots + map_idx);
 *                            index++, mt_entry++) {
 *                           mt_entry->device_info = MPR_DEV_RESERVED;
 *                           mt_entry->physical_id = et_entry->enclosure_id;
 *                           mt_entry->phy_bits   = et_entry->phy_bits;
 *                           mt_entry->missing_count = 0;
 *                       }                       // OOB when num_rsvd+num_slots>max_devices
 *   mpr_mapping.c:2140-2141 mapping_table = kmalloc(max_devices * 32)
 *   mpr_mapping.c:2565      max_devices = facts->MaxTargets + MaxVolumes (u16)
 *
 * Contrast (why this is NOT the expander path): _mapping_find_enc_map_space()
 * only returns a start_idx when num_slots CONTIGUOUS free entries were found
 * below end_of_table <= max_devices (:979-993), so the expander branch is
 * bounded. The direct-attached branch (:1241) has no such bound at all.
 *
 * Distinct from DF-1282 (DPM pg0 DeviceIndex write in
 * _mapping_process_dpm_pg0:2327) and DF-1283 (slot-start_slot underflow in
 * _mapping_add_new_device:1637): different entry path (live topology event,
 * not DPM ingestion) and different arithmetic (missing table-size bound on
 * NumSlots, no slot subtraction).
 *
 * Build: cc -O2 -Wall -o harness_df3914 harness_df3914.c
 * Run:   ./harness_df3914
 */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>

/* Mirror of sys/dev/raid/mpr/mprvar.h:113 (32 bytes on LP64) */
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;
};

struct enc_mapping_table {          /* mprvar.h:141 - relevant fields only */
	uint64_t enclosure_id;
	uint32_t start_index;
	uint32_t phy_bits;
	uint16_t dpm_entry_num;
	uint16_t enc_handle;
	uint16_t num_slots;
	uint16_t start_slot;
	uint8_t  missing_count;
	uint8_t  removal_flag;
	uint8_t  skip_search;
	uint8_t  init_complete;
};

#define MPR_MAPTABLE_BAD_IDX 0xFFFFFFFFu
#define MPR_DEV_RESERVED     0x20000000u

#define MAX_DEVICES     264u     /* MaxTargets(256)+MaxVolumes(8), typical SAS3 */
#define NUM_RSVD        1u       /* MPI2_IOCPAGE8_FLAGS_RESERVED_TARGETID_0 */
#define GUARD_ENTRIES   4096u    /* entries of guard following the allocation */
#define ATTACK_SLOTS    0x0800u  /* enclosure event NumSlots = 2048 (u16 max 65535) */
#define ATTACK_ENC_ID   0x5141524153534141ULL /* "AASSRAAQ" - firmware-chosen SAS addr */

int
main(void)
{
	struct dev_mapping_table *mapping_table;
	struct enc_mapping_table et;
	uint32_t map_idx, index;
	struct dev_mapping_table *mt_entry;
	size_t i;
	int smashed = 0;
	size_t first_smash = (size_t)-1;
	size_t entries_past_end = 0;

	/*
	 * Heap allocation exactly as the kernel does at mpr_mapping.c:2140
	 * (kmalloc(max_devices * 32, M_MPR)), followed by a guard region of
	 * canaries standing in for the adjacent slab objects that the kernel
	 * overflow would corrupt.
	 */
	mapping_table = calloc(MAX_DEVICES + GUARD_ENTRIES,
	    sizeof(*mapping_table));
	if (mapping_table == NULL) {
		perror("calloc");
		return (1);
	}
	for (i = MAX_DEVICES; i < MAX_DEVICES + GUARD_ENTRIES; i++)
		memcpy(&mapping_table[i], &(uint32_t){0xDEADBEEFu}, sizeof(uint32_t));

	/* Enclosure Status Change event (mpr_mapping.c:2833-2845): the only
	 * validation of NumSlots in the whole file is != 0 (:2760-2765). */
	memset(&et, 0, sizeof(et));
	et.enclosure_id  = ATTACK_ENC_ID;	/* firmware-chosen 64-bit value */
	et.num_slots     = ATTACK_SLOTS;	/* e.g. lying/broken backplane SES */
	et.start_slot    = 0;
	et.start_index   = MPR_MAPTABLE_BAD_IDX;	/* fresh, unmapped (:2841) */

	/* SAS Topology Change List event, direct-attached devices:
	 * topo_change->exp_handle == 0  ->  mpr_mapping.c:1241-1243 */
	map_idx = NUM_RSVD;
	et.start_index = map_idx;		/* NO bound on num_slots here */

	/* mpr_mapping.c:1292-1299 - verbatim reservation loop */
	mt_entry = &mapping_table[map_idx];
	for (index = map_idx; index < (et.num_slots + map_idx);
	    index++, mt_entry++) {
		mt_entry->device_info = MPR_DEV_RESERVED;
		mt_entry->physical_id = et.enclosure_id;
		mt_entry->phy_bits = et.phy_bits;
		mt_entry->missing_count = 0;
	}

	printf("DF-3914 object-level proof\n");
	printf("  mapping_table alloc      : %u entries (%zu bytes)\n",
	    MAX_DEVICES, (size_t)MAX_DEVICES * sizeof(*mapping_table));
	printf("  num_rsvd_entries         : %u\n", NUM_RSVD);
	printf("  enclosure NumSlots (fw)  : %u\n", et.num_slots);
	printf("  reservation loop wrote   : [%u, %u)\n", map_idx,
	    et.num_slots + map_idx);
	printf("  table end                : %u  -> overflow of %u entries "
	    "(%zu bytes)\n", MAX_DEVICES,
	    (et.num_slots + map_idx) - MAX_DEVICES,
	    (size_t)((et.num_slots + map_idx) - MAX_DEVICES) *
	        sizeof(*mapping_table));

	/* Prove the guard region now carries the attacker value at the
	 * 32-byte stride (physical_id, fully firmware-controlled 8 bytes). */
	for (i = MAX_DEVICES; i < MAX_DEVICES + GUARD_ENTRIES; i++) {
		if (mapping_table[i].physical_id == ATTACK_ENC_ID) {
			smashed = 1;
			if (first_smash == (size_t)-1)
				first_smash = i;
			entries_past_end++;
		}
	}

	if (!smashed) {
		printf("RESULT: no guard corruption - primitive NOT proven\n");
		free(mapping_table);
		return (1);
	}
	printf("  guard[+%zu..] physical_id == 0x%016jx at %zu/%zu entries "
	    "(stride %zu)\n",
	    first_smash - MAX_DEVICES, (uintmax_t)ATTACK_ENC_ID,
	    entries_past_end, (size_t)GUARD_ENTRIES,
	    sizeof(struct dev_mapping_table));
	printf("  kernel equivalent        : %zu-byte write of fw-controlled "
	    "data past a %zu-byte kmalloc slab object\n",
	    (size_t)((et.num_slots + map_idx) - MAX_DEVICES) *
	        sizeof(*mapping_table),
	    (size_t)MAX_DEVICES * sizeof(*mapping_table));
	printf("RESULT: OOB WRITE PRIMITIVE PROVEN (canary smash with "
	    "attacker value, 32-byte stride)\n");
	free(mapping_table);
	return (0);
}
