DF-4085 / harness_df4085.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 | /* * DF-4085 object-level proof. * * mps_mapping.c:_mapping_process_dpm_pg0() ENCLOSURE_SLOT_MAPPING branch: * for a DPM pg0 entry whose DeviceIndex (dev_idx) falls inside the * direct-attached range [num_rsvd_entries, num_rsvd_entries + * MaxNumPhysicalMappedIDs) the code sets * * num_slots = max_num_phy_ids; (:1518) * * MaxNumPhysicalMappedIDs is a firmware-supplied u16 from IOC page 8 that is * never validated against the mapping-table size, and the reservation loop * * for (map_idx = dev_idx; map_idx < (dev_idx + num_slots); ...) (:1545) * * then writes num_slots consecutive struct dev_mapping_table entries with no * check that dev_idx + num_slots <= sc->max_devices. With an in-bounds * dev_idx this is an unbounded linear heap write past * kmalloc(max_devices * sizeof(struct dev_mapping_table)). * * This harness replicates the struct (mpsvar.h:135) and the verbatim loop * body (:1554-1562) and drives it with a fully in-bounds dev_idx. */ #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 CANARY 0x5141524153534141ULL /* fw-controlled PhysicalIdentifier */ int main(void) { /* IOC facts (firmware controlled): MaxTargets U16, MaxVolumes U8 */ uint32_t MaxTargets = 508, MaxVolumes = 2; uint32_t max_devices = MaxTargets + MaxVolumes; /* :1734 */ 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, slot_id; uint64_t fw_phys_id = CANARY; uint32_t fw_phy_bits = 0xDEADBEEFu; uint8_t fw_missing_cnt = 1; struct dev_mapping_table *map, *mt_entry; uint32_t map_idx, i, iter_end, smashed = 0; printf("DF-4085 object-level proof (mps_mapping.c:1512-1563)\n"); printf(" sizeof(dev_mapping_table) : %zu (expect 32)\n", sizeof(struct dev_mapping_table)); printf(" max_devices : %u (table = %zu bytes)\n", max_devices, (size_t)max_devices * sizeof(*map)); printf(" MaxNumPhysicalMappedIDs : 0x%04x (IOC pg8, unvalidated)\n", max_num_phy_ids); /* mapping_table allocation plus a guard region standing in for * adjacent kernel heap. Every guard entry is poisoned. */ map = calloc(max_devices + 0x11000, sizeof(*map)); if (map == NULL) return (2); for (i = 0; i < 0x11000; i++) map[max_devices + i].physical_id = 0x4141414141414141ULL; /* === verbatim _mapping_process_dpm_pg0 logic :1512-1518 === */ slot_id = 0; if (dev_idx < (num_rsvd_entries + max_num_phy_ids)) { slot_id = 0; num_slots = max_num_phy_ids; /* :1518 - unvalidated */ } else { num_slots = 0; /* slot field of MappingInformation: max 0xF */ } (void)slot_id; printf(" DPM entry %u : dev_idx=%u (in bounds!), " "num_slots=%u\n", entry_num, dev_idx, num_slots); /* === verbatim reservation loop :1544-1563 === */ mt_entry = &map[dev_idx]; for (map_idx = dev_idx; map_idx < (dev_idx + num_slots); map_idx++, mt_entry++) { mt_entry->physical_id = fw_phys_id; /* :1554-1556 */ mt_entry->phy_bits = fw_phy_bits; /* :1557 */ mt_entry->channel = 0; /* :1558 */ mt_entry->id = dev_idx; /* :1559 */ mt_entry->dpm_entry_num = entry_num; /* :1560 */ mt_entry->missing_count = fw_missing_cnt; /* :1561 */ mt_entry->device_info = MPS_DEV_RESERVED; /* :1562 */ } /* === end verbatim === */ iter_end = dev_idx + num_slots; if (iter_end > max_devices) smashed = iter_end - max_devices; for (i = 0; i < smashed; i++) if (map[max_devices + i].physical_id != CANARY) return (3); /* harness bug */ printf(" reservation loop wrote : [%u, %u)\n", dev_idx, iter_end); printf(" entries past table end : %u\n", smashed); printf(" kernel equivalent : %zu-byte linear heap write of " "fw-controlled data\n", (size_t)smashed * 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)); if (smashed) { printf("RESULT: OOB WRITE PRIMITIVE PROVEN (canary smash, " "stride %zu)\n", sizeof(*map)); return (0); } return (1); } |