⬢ DragonFlyBSD Kernel Audit
DF-4093 / harness_df4093.c
← back to finding ↓ download raw
/*
 * DF-4093 object-level proof (mpr twin of DF-4086).
 *
 * sys/dev/raid/mpr/mpr_mapping.c:_mapping_get_ir_maprange (:241-255)
 * computes
 *
 *     *end_idx = *start_idx + sc->max_volumes - 1;		(:254)
 *
 * unclamped.  With IOC facts MaxTargets == 0, max_devices =
 * MaxTargets + MaxVolumes (:2565) equals max_volumes.  In
 * LOW_VOLUME_MAPPING mode (:248) with MPI2_IOCPAGE8_FLAGS_RESERVED_TARGETID_0
 * set (:250-251), 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], one 32-byte entry past the
 * kmalloc(max_devices * 32) (:2140-2141):
 *
 *   - _mapping_get_ir_mt_idx_from_wwid   :387  OOB read (physical_id)
 *   - _mapping_get_ir_mt_idx_from_handle :430  OOB read (dev_handle)
 *   - _mapping_get_free_ir_mt_idx        :484/:486  OOB reads, and the walk
 *     RETURNS the OOB index when the out-of-bounds memory does not have
 *     MPR_MAP_IN_USE set
 *   - _mapping_process_dpm_pg0 volume branch :2260-2271 writes a full
 *     entry at mapping_table[dev_idx] for dev_idx == end_idx (DPM pg0
 *     DeviceIndex, fw-controlled) -> unconditional 32-byte OOB write
 *   - mpr_mapping_get_raid_tid           :2708  OOB read (CAM-facing)
 *   - mpr_mapping_ir_config_change_event :3082-3088 then writes a full
 *     struct dev_mapping_table at the returned index: 32-byte heap OOB
 *     write with the firmware-controlled volume WWID, dev_handle,
 *     device_info
 *
 * This harness replicates the range function, the walks, the DPM volume
 * branch and the event write verbatim.
 */
#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_MAP_IN_USE		0x10000000u	/* mprvar.h:92 */
#define MPR_MAPTABLE_BAD_IDX	0xFFFFFFFFu	/* mprvar.h:87 */
#define GUARD_N	8

/* verbatim _mapping_get_ir_maprange (:241-255) 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)
{
	/* volume_mapping_flags == MPI2_IOCPAGE8_IRFLAGS_LOW_VOLUME_MAPPING */
	*start_idx = 0;
	/* ioc_pg8_flags & MPI2_IOCPAGE8_FLAGS_RESERVED_TARGETID_0 */
	*start_idx = 1;
	*end_idx = *start_idx + max_volumes - 1;		/* :254 */
	(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;	/* :2565 = 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-4093 object-level proof (mpr_mapping.c:241-255)\n");
	printf("  sizeof(dev_mapping_table) : %zu (expect 32 on amd64)\n",
	    sizeof(struct dev_mapping_table));
	printf("  facts: MaxTargets=%u MaxVolumes=%u -> max_devices=%u "
	    "(:2565)\n", MaxTargets, MaxVolumes, max_devices);
	printf("  valid mapping_table indices: 0..%u (kmalloc %zu bytes "
	    ":2140)\n", max_devices - 1,
	    (size_t)max_devices * sizeof(*map));

	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 = MPR_MAP_IN_USE | MPR_DEV_RESERVED;
	/* adjacent kernel heap (guard): no IN_USE bit set, as any garbage */

	/* === verbatim _mapping_get_ir_maprange (:241-255) === */
	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);
	if (end_idx != max_devices)
		return (1);	/* setup bug */

	/* === verbatim _mapping_get_ir_mt_idx_from_wwid walk (:385-391) === */
	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 :387 read physical_id at "
			    "mapping_table[%u] -> OOB READ\n", map_idx);

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

	/* === verbatim mpr_mapping_ir_config_change_event (:3082-3088) === */
	mt_entry = &map[map_idx];
	mt_entry->physical_id = wwid;				/* :3083 */
	mt_entry->id = (uint16_t)map_idx;			/* :3084 */
	mt_entry->dev_handle = vol_handle;			/* :3085 */
	mt_entry->device_info = MPR_DEV_RESERVED | MPR_MAP_IN_USE; /* :3087 */
	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)
		return (1);
	printf("  guard[%u].physical_id == fw WWID: 32-byte heap OOB WRITE "
	    "at index max_devices\n", max_devices);

	/* === verbatim _mapping_process_dpm_pg0 volume branch (:2260-2271):
	 * DPM entry with DeviceIndex == end_idx -> full OOB entry write === */
	map_idx = max_devices;		/* DPM DeviceIndex u16, fw-controlled */
	if (map_idx >= start_idx && map_idx <= end_idx) {
		mt_entry = &map[map_idx];			 /* :2262 */
		mt_entry->physical_id = 0x600DCAFE600DCAFEULL; /* :2263 */
		mt_entry->id = (uint16_t)map_idx;		 /* :2267 */
		mt_entry->dpm_entry_num = 1;			 /* :2269 */
		mt_entry->device_info = MPR_DEV_RESERVED;	 /* :2270 */
	}
	if (map[max_devices].physical_id != 0x600DCAFE600DCAFEULL)
		return (1);
	printf("  DPM volume branch :2261 accepted DeviceIndex %u == end_idx "
	    "-> second 32-byte OOB WRITE\n", max_devices);

	printf("RESULT: OFF-BY-ONE OOB READS + TWO 32-BYTE OOB WRITES "
	    "PROVEN\n");
	return (0);
}