DF-4093 / harness_df4093.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 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 | /* * 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); } |