DF-4092 / harness_df4092.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 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | /* * DF-4092 object-level proof (mpr twin of DF-4085). * * sys/dev/raid/mpr/mpr_mapping.c:_mapping_process_dpm_pg0() * ENCLOSURE_SLOT_MAPPING branch: * * u16 max_num_phy_ids = le16toh(sc->ioc_pg8.MaxNumPhysicalMappedIDs); (:2216) * ... * if (dev_idx < (sc->num_rsvd_entries + max_num_phy_ids)) { (:2284) * ... * num_slots = max_num_phy_ids; (:2290) * } * ... * mt_entry = &sc->mapping_table[dev_idx]; (:2327) * for (map_idx = dev_idx; map_idx < (dev_idx + num_slots); (:2328) * map_idx++, mt_entry++) { * if (mt_entry->dpm_entry_num != MPR_DPM_BAD_IDX) (:2330) * goto fail; (:2336) * mt_entry->physical_id = <fw PhysicalIdentifier>; (:2340) * mt_entry->phy_bits = phy_bits; (:2342) * mt_entry->id = dev_idx; (:2343) * mt_entry->dpm_entry_num = entry_num; (:2344) * mt_entry->missing_count = missing_cnt; (:2345) * mt_entry->device_info = MPR_DEV_RESERVED; (:2346) * } * * MaxNumPhysicalMappedIDs is a firmware-supplied u16 from IOC page 8 that * is never validated against the mapping-table size anywhere in the driver * (only other use is as a search-skip hint :955/:979/:1045/:1718/:1975). * mapping_table is kmalloc(sizeof(struct dev_mapping_table) * * sc->max_devices) (mpr_mapping.c:2140-2141) and max_devices = * facts->MaxTargets + facts->MaxVolumes (:2565). Nothing checks * dev_idx + num_slots <= max_devices, so with an IN-BOUNDS dev_idx inside * the direct-attached range the loop walks past the table for as many * consecutive entries as keep passing the per-entry conflict gate * (dpm_entry_num == MPR_DPM_BAD_IDX). In-bounds entries always pass * (all were cleared to BAD_IDX by _mapping_clear_map_entry via * mpr_mapping_initialize :2593-2594), so [dev_idx, max_devices) is fully * overwritten and the walk then reads/writes ADJACENT KERNEL HEAP: a * 2-byte OOB read at offset 16 of each 32-byte block, and a full 32-byte * fw-controlled write for every block whose garbage u16 == 0xFFFF. * * Distinct from DF-3914 (event NumSlots path, _mapping_get_dev_info) and * DF-3915 (num_slots==0 _mapping_commit_enc_entry path): different num_slots * source (IOC pg8 MaxNumPhysicalMappedIDs vs event u16), different loop. * * This harness replicates the mpr struct (mprvar.h:113-125) and the * verbatim loop (:2327-2347) and drives it with a fully in-bounds dev_idx * under two adjacent-heap scenarios. */ #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_DPM_BAD_IDX 0xFFFFu /* mprvar.h:88 */ #define CANARY 0x5141524153534141ULL /* fw PhysicalIdentifier */ #define GUARD_N 0x11000 /* stands for adjacent kernel heap */ static struct dev_mapping_table *map; static uint32_t max_devices; /* * Verbatim reservation loop :2327-2347. Returns the number of entries * written; sets *failed when the conflict gate hits (goto fail :2336). */ static uint32_t reserve(uint32_t dev_idx, uint32_t num_slots, uint16_t entry_num, uint64_t fw_phys_id, uint32_t fw_phy_bits, uint8_t fw_missing_cnt, int *failed) { struct dev_mapping_table *mt_entry; uint32_t map_idx, written = 0; *failed = 0; mt_entry = &map[dev_idx]; for (map_idx = dev_idx; map_idx < (dev_idx + num_slots); map_idx++, mt_entry++) { if (mt_entry->dpm_entry_num != MPR_DPM_BAD_IDX) /* :2330 */ { *failed = 1; break; } /* :2336 */ mt_entry->physical_id = fw_phys_id; /* :2340 */ mt_entry->phy_bits = fw_phy_bits; /* :2342 */ mt_entry->id = (uint16_t)dev_idx; /* :2343 */ mt_entry->dpm_entry_num = entry_num; /* :2344 */ mt_entry->missing_count = fw_missing_cnt; /* :2345 */ mt_entry->device_info = MPR_DEV_RESERVED; /* :2346 */ written++; } return (written); } static void reset_table(uint32_t guard_badidx_blocks) { uint32_t i; memset(map, 0, (size_t)(max_devices + GUARD_N) * sizeof(*map)); /* driver init: every in-bounds entry cleared, dpm_entry_num=BAD_IDX */ for (i = 0; i < max_devices; i++) { map[i].dpm_entry_num = MPR_DPM_BAD_IDX; /* :2593, clear :65-75 */ map[i].id = 0xFFFF; } /* adjacent kernel heap: garbage, except the first K blocks whose * garbage u16 happens to be 0xFFFF (stale table from a previous * attach cycle, freed zone content, 0xFFFF-laden objects). */ for (i = 0; i < GUARD_N; i++) map[max_devices + i].physical_id = 0x4141414141414141ULL; for (i = 0; i < guard_badidx_blocks; i++) map[max_devices + i].dpm_entry_num = MPR_DPM_BAD_IDX; } int main(void) { /* IOC facts (firmware controlled): MaxTargets U16, MaxVolumes U8 */ uint32_t MaxTargets = 508, MaxVolumes = 2; 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; uint64_t fw_phys_id = CANARY; uint32_t fw_phy_bits = 0xDEADBEEFu; uint8_t fw_missing_cnt = 1; uint32_t in_bounds, i, oob_written, failed_runs; int failed; max_devices = MaxTargets + MaxVolumes; /* :2565 */ printf("DF-4092 object-level proof (mpr_mapping.c:2216,2284-2347)\n"); printf(" sizeof(dev_mapping_table) : %zu (expect 32 on amd64)\n", sizeof(struct dev_mapping_table)); printf(" max_devices : %u (kmalloc %zu bytes, :2140)\n", max_devices, (size_t)max_devices * sizeof(*map)); printf(" MaxNumPhysicalMappedIDs : 0x%04x (IOC pg8, unvalidated)\n", max_num_phy_ids); map = calloc(max_devices + GUARD_N, sizeof(*map)); if (map == NULL) return (2); /* === scenario A: adjacent heap garbage != 0xFFFF at offset 16 === */ reset_table(0); if (dev_idx < (num_rsvd_entries + max_num_phy_ids)) num_slots = max_num_phy_ids; /* :2290 */ printf(" DPM entry %u : dev_idx=%u (in bounds!), " "num_slots=%u\n", entry_num, dev_idx, num_slots); in_bounds = reserve(dev_idx, num_slots, entry_num, fw_phys_id, fw_phy_bits, fw_missing_cnt, &failed); printf(" scenario A (cold garbage) : wrote %u entries [%u,%u), " "conflict gate: %s\n", in_bounds, dev_idx, dev_idx + in_bounds, failed ? "TRIPPED at first OOB block (goto fail :2336)" : "no"); if (in_bounds != max_devices - dev_idx || !failed) return (3); /* harness bug */ /* the trip itself was an OOB READ of dpm_entry_num at * mapping_table[max_devices] - a 2-byte read past the kmalloc */ /* === scenario B: first 4096 OOB blocks hold 0xFFFF at offset 16 === */ reset_table(4096); in_bounds = reserve(dev_idx, num_slots, entry_num, fw_phys_id, fw_phy_bits, fw_missing_cnt, &failed); oob_written = in_bounds - (max_devices - dev_idx); printf(" scenario B (0xFFFF groom) : wrote %u entries, %u of them " "PAST the table\n", in_bounds, oob_written); if (in_bounds != (max_devices - dev_idx) + 4096 || !failed) return (4); /* harness bug */ for (i = 0; i < oob_written; i++) if (map[max_devices + i].physical_id != CANARY) return (5); /* harness bug */ printf(" reservation loop wrote : [%u, %u) (table ends at " "%u)\n", dev_idx, dev_idx + in_bounds, max_devices); printf(" kernel equivalent : %zu-byte heap write of " "fw-controlled data\n", (size_t)oob_written * 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)); /* === unbounded worst case: entire guard BAD_IDX (2MB smash) === */ reset_table(GUARD_N); failed_runs = 0; in_bounds = reserve(dev_idx, num_slots, entry_num, fw_phys_id, fw_phy_bits, fw_missing_cnt, &failed); oob_written = in_bounds - (max_devices - dev_idx); (void)failed_runs; printf(" scenario C (all 0xFFFF) : %u entries past table = %zu " "bytes linear smash\n", oob_written, (size_t)oob_written * sizeof(*map)); for (i = 0; i < oob_written; i++) if (map[max_devices + i].physical_id != CANARY) return (6); /* harness bug */ if (oob_written) { printf("RESULT: OOB WRITE PRIMITIVE PROVEN (canary smash at " "stride %zu; per-entry dpm_entry_num==BAD_IDX gate " "modeled verbatim)\n", sizeof(*map)); return (0); } return (1); } |