DF-1375 / harness.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 | /* * DF-1375 harness: OOB heap write via unsigned slot underflow in * _mapping_add_new_device (mps_mapping.c). * * This is the MPS driver's twin of DF-1283 (the mpr driver's identical bug). * OBJECT-LEVEL proof of the primitive: the mps driver cannot attach on the * audit QEMU guest (no LSI/Avago SAS HBA present), so the vulnerable path is * not runtime-reachable here. This harness replicates the exact kernel struct * (dev_mapping_table, enc_mapping_table) and the exact unguarded arithmetic * from _mapping_add_new_device with a firmware-controlled Slot < start_slot * to PROVE the OOB write primitive is real. * * Cited path: sys/dev/raid/mps/mps_mapping.c:1165 * map_idx = et_entry->start_index + phy_change->slot - * et_entry->start_slot; // u32, slot<start_slot -> wrap * sys/dev/raid/mps/mps_mapping.c:1167 * mt_entry = &sc->mapping_table[map_idx]; // OOB * sys/dev/raid/mps/mps_mapping.c:992-998 * reservation loop trusts firmware num_slots without bounds check * * phy_change->slot comes from le16toh(sas_device_pg0.Slot) (firmware). When * slot < start_slot, the u32 subtraction wraps to ~0 and map_idx becomes a * huge value -> mapping_table[map_idx] is a massive OOB write. * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> 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; }; /* Mirror enc_mapping_table (the fields used by the buggy arithmetic). */ struct enc_mapping_table { uint64_t enclosure_id; uint64_t phys_id; uint32_t device_info; uint32_t phy_bits; uint16_t num_slots; uint16_t slot; uint16_t enclosure_id_short; uint16_t start_index; uint16_t start_slot; uint16_t dpm_entry_num; uint8_t missing_count; uint8_t init_complete; uint16_t reserved; }; #define MAX_DEVICES 264u int main(void) { /* We allocate one big table so we can show the wrapping arithmetic in a * controlled way. A real underflow (slot=0, start_slot=2) yields a wrapped * map_idx of ~0xFFFFFFFE which is unrepresentable as an in-process index; * instead we demonstrate (a) the exact buggy expression and the wrap, and * (b) a "small underflow" (start_index=10, slot=0, start_slot=4 => map_idx * =6 which IS in-range but WRONG, plus a negative-wrap case showing the * OOB pointer lands before the table). */ struct dev_mapping_table *table = calloc(MAX_DEVICES + 8, sizeof(*table)); if (!table) { perror("calloc"); return 1; } struct enc_mapping_table et; memset(&et, 0, sizeof(et)); et.start_index = 10; et.start_slot = 4; printf("[DF-1375] mapping_table max_devices=%u; start_index=%u start_slot=%u\n", MAX_DEVICES, et.start_index, et.start_slot); /* Case A: slot < start_slot with start_index large enough that the u32 * arithmetic wraps to a huge value (the real bug). */ uint16_t slot_a = 0; /* firmware sas_device_pg0.Slot */ uint32_t map_idx = (uint32_t)et.start_index + (uint32_t)slot_a - (uint32_t)et.start_slot; /* mps_mapping.c:1165 */ printf("[DF-1375] Case A: slot=%u < start_slot=%u -> map_idx = %u (0x%08x)\n", slot_a, et.start_slot, map_idx, map_idx); /* If start_index is 0 and slot<start_slot, the wrap is catastrophic: */ et.start_index = 0; et.start_slot = 2; slot_a = 0; map_idx = (uint32_t)et.start_index + (uint32_t)slot_a - (uint32_t)et.start_slot; printf("[DF-1375] Case A2: start_index=0, slot=0, start_slot=2 -> " "map_idx = %u (0x%08x) -- wraps to ~0xFFFFFFFE -> &mapping_table[~0xFFFFFFFE]\n", map_idx, map_idx); if ((int32_t)((int32_t)et.start_index + (int16_t)slot_a - (int16_t)et.start_slot) < 0 || map_idx >= MAX_DEVICES) { printf("[DF-1375] BUG CONFIRMED: slot < start_slot makes map_idx wrap/underflow " "-> mapping_table[%u] is a %s OOB write\n", map_idx, map_idx >= MAX_DEVICES ? "massive" : "before-table"); } /* Case B: demonstrate the in-table WRONG write (slot<start_slot but * start_index keeps it in-range): the wrong entry gets overwritten. */ et.start_index = 10; et.start_slot = 4; slot_a = 1; /* < start_slot */ map_idx = (uint32_t)et.start_index + (uint32_t)slot_a - (uint32_t)et.start_slot; printf("[DF-1375] Case B: slot=%u -> map_idx=%u (expected >= start_index=%u); " "writes attacker physical_id/device_info into WRONG entry\n", slot_a, map_idx, et.start_index); if (map_idx < et.start_index) { struct dev_mapping_table *mt = &table[map_idx]; /* :1167 */ mt->physical_id = 0x4141414141414141ULL; mt->device_info = 0x43434343; printf("[DF-1375] wrote attacker data into mapping_table[%u] " "(below the enclosure's start_index=%u) -> confirmed wrong-target " "corruption\n", map_idx, et.start_index); } /* The num_slots reservation loop (mps_mapping.c:992-998) has the same * class of issue: trusts firmware num_slots without an upper bound. */ uint16_t num_slots_fw = 600; /* firmware NumSlots, > max_devices */ uint32_t base = 250; uint32_t end = base + num_slots_fw; printf("[DF-1375] reservation loop: base=%u + num_slots(fw)=%u = %u %s " "max_devices=%u -> %s\n", base, num_slots_fw, end, end > MAX_DEVICES ? ">" : "<=", MAX_DEVICES, end > MAX_DEVICES ? "OOB write past mapping_table" : "ok"); printf("[DF-1375] FIX: validate slot >= start_slot AND map_idx < max_devices; " "validate map_idx + num_slots <= max_devices before the loops\n"); free(table); return 0; } |