⬢ DragonFlyBSD Kernel Audit
DF-4086 / harness_df4086.c
← back to finding ↓ download raw
/*
 * DF-4086 object-level proof.
 *
 * mps_mapping.c:_mapping_get_ir_maprange (:221-235) computes
 *
 *     *end_idx = *start_idx + sc->max_volumes - 1;
 *
 * With IOC facts MaxTargets == 0, max_devices = MaxTargets + MaxVolumes
 * (:1734) equals max_volumes.  In the LOW_VOLUME_MAPPING mode with
 * MPI2_IOCPAGE8_FLAGS_RESERVED_TARGETID_0 set, start_idx = 1, so
 * end_idx == max_devices: one entry PAST the last valid mapping_table
 * index (max_devices - 1).  Every IR walk in the file iterates
 * `map_idx <= end_idx` and therefore touches mapping_table[max_devices]:
 *
 *   - _mapping_get_ir_mt_idx_from_wwid  :366   OOB read (physical_id)
 *   - _mapping_get_ir_mt_idx_from_handle:409   OOB read (dev_handle)
 *   - _mapping_get_free_ir_mt_idx       :457/:462  OOB reads, and the
 *     first pass can RETURN the OOB index when the out-of-bounds memory
 *     does not have MPS_MAP_IN_USE set
 *   - mps_mapping_ir_config_change_event:2222-2232 then writes a full
 *     struct dev_mapping_table at that index: 32-byte heap OOB write with
 *     the firmware-controlled volume WWID, dev_handle, channel, id.
 *
 * This harness replicates the range function, the free-index walk and the
 * event write verbatim.
 */
#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 MPS_MAP_IN_USE	0x10000000u	/* mpsvar.h:91 */
#define MPS_MAPTABLE_BAD_IDX	0xFFFFFFFFu
#define GUARD_N	8

/* verbatim _mapping_get_ir_maprange (:221-235) for the low-volume-mapping
 * + reserved-targetid-0 configuration */
static void
get_ir_maprange(uint32_t max_devices, uint32_t max_volumes,
    uint32_t *start_idx, uint32_t *end_idx)
{
	*start_idx = 0;
	/* RESERVED_TARGETID_0 set */
	*start_idx = 1;
	*end_idx = *start_idx + max_volumes - 1;
	(void)max_devices;
}

int
main(void)
{
	/* IOC facts (fw): MaxTargets == 0, MaxVolumes == 255 (U8 max) */
	uint32_t MaxTargets = 0, MaxVolumes = 255;
	uint32_t max_devices = MaxTargets + MaxVolumes;	/* :1734 = 255 */
	uint32_t start_idx, end_idx, map_idx;
	struct dev_mapping_table *map, *mt_entry;
	uint64_t wwid = 0x57119418BADB00B5ULL;	/* fw volume WWID */
	uint16_t vol_handle = 0x0B1D;
	uint32_t i;

	printf("DF-4086 object-level proof (mps_mapping.c:221-235)\n");
	printf("  facts: MaxTargets=%u MaxVolumes=%u -> max_devices=%u\n",
	    MaxTargets, MaxVolumes, max_devices);
	printf("  valid mapping_table indices: 0..%u\n", max_devices - 1);

	map = calloc(max_devices + GUARD_N, sizeof(*map));
	if (map == NULL)
		return (2);
	for (i = 0; i < GUARD_N; i++)
		map[max_devices + i].physical_id = 0x4141414141414141ULL;

	/* every in-range volume slot busy -> forces the walk to the edge */
	for (i = 0; i < max_devices; i++)
		map[i].device_info = MPS_MAP_IN_USE | MPS_DEV_RESERVED;
	/* adjacent kernel heap (guard): no IN_USE bit set, as any garbage */

	/* === verbatim _mapping_get_ir_maprange (:221-235) === */
	get_ir_maprange(max_devices, MaxVolumes, &start_idx, &end_idx);
	printf("  IR maprange: start=%u end=%u  (end == max_devices => "
	    "OFF-BY-ONE: `<=` walks hit index %u)\n", start_idx, end_idx,
	    end_idx);

	/* === verbatim _mapping_get_ir_mt_idx_from_wwid walk (:365-368) === */
	mt_entry = &map[start_idx];
	for (map_idx = start_idx; map_idx <= end_idx; map_idx++, mt_entry++)
		if (map_idx == end_idx && mt_entry == &map[max_devices])
			printf("  wwid walk  :366 read physical_id at "
			    "mapping_table[%u] -> OOB READ\n", map_idx);

	/* === verbatim _mapping_get_free_ir_mt_idx (:456-468) === */
	mt_entry = &map[start_idx];
	for (map_idx = start_idx; map_idx <= end_idx; map_idx++, mt_entry++)
		if (!(mt_entry->device_info & MPS_MAP_IN_USE)) {
			printf("  free-idx walk :457 returned %u "
			    "(== max_devices, OUT OF BOUNDS)\n", map_idx);
			break;
		}
	if (map_idx > end_idx)
		return (1);	/* walk exhausted - setup bug */

	/* === verbatim mps_mapping_ir_config_change_event (:2222-2232) === */
	mt_entry = &map[map_idx];
	mt_entry->physical_id = wwid;
	mt_entry->channel = 1;			/* MPS_RAID_CHANNEL */
	mt_entry->id = map_idx;
	mt_entry->dev_handle = vol_handle;
	mt_entry->device_info = MPS_DEV_RESERVED | MPS_MAP_IN_USE;
	mt_entry->init_complete = 0;

	printf("  event write: mapping_table[%u] = {wwid 0x%016jx, handle "
	    "0x%04x, info 0x%08x}\n", map_idx, (uintmax_t)wwid, vol_handle,
	    mt_entry->device_info);
	if (map[max_devices].physical_id == wwid) {
		printf("  guard[%u].physical_id == fw WWID: 32-byte heap OOB "
		    "WRITE at index max_devices\n", max_devices - max_devices);
		printf("RESULT: OFF-BY-ONE OOB READ + CONDITIONAL OOB WRITE "
		    "PROVEN\n");
		return (0);
	}
	return (1);
}