DF-4086 / harness_df4086.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | /* * 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); } |