DF-1282 / 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 | /* * DF-1282 harness: OOB heap write via unchecked firmware-controlled * DeviceIndex in _mapping_process_dpm_pg0 (mpr_mapping.c). * * This is an OBJECT-LEVEL proof of the primitive. The mpr 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) and the exact unguarded * indexing from _mapping_process_dpm_pg0 with an attacker/firmware- * controlled DeviceIndex to PROVE the OOB write primitive is real. * * Cited path: sys/dev/raid/mpr/mpr_mapping.c:2251 (dev_idx source), * :2260-2272 (IR path WITH bounds check), :2327/:2358 (Enc/Slot and * Device-Persistence paths WITHOUT bounds check), :2140 (allocation * sized by max_devices), :2565 (max_devices = MaxTargets + max_volumes). * * Build: cc -O2 -o harness harness.c * Run: ./harness */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> /* Mirror of sys/dev/raid/mpr/mprvar.h:113 dev_mapping_table */ 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 MAX_DEVICES 264u /* MaxTargets(256)+max_volumes(8) typical */ #define CANARY 0xDEADBEEFu #define GUARD_N 64 /* entries of guard after the allocation */ int main(void) { /* Heap allocation exactly as the kernel does at mpr_mapping.c:2140, * followed by a guard region full of canaries. In the kernel, writes * past mapping_table[max_devices] corrupt the next slab object. */ struct dev_mapping_table *table = calloc(MAX_DEVICES + GUARD_N, sizeof(*table)); if (!table) { perror("calloc"); return 1; } /* paint the guard region (entries MAX_DEVICES..MAX_DEVICES+GUARD_N-1) */ for (unsigned g = MAX_DEVICES; g < MAX_DEVICES + GUARD_N; g++) memset(&table[g], 0xAA, sizeof(table[g])); printf("[DF-1282] mapping_table allocated for max_devices=%u entries " "(sizeof(entry)=%zu)\n", MAX_DEVICES, sizeof(*table)); /* === Replicate the Enc/Slot path of _mapping_process_dpm_pg0 === * dev_idx comes straight from firmware: mpr_mapping.c:2251 * dev_idx = le16toh(dpm_entry->DeviceIndex); // u32, 0..65535 * IR path bounds-checks it (:2260). Enc/Slot (:2327) and Device * Persistence (:2358) do NOT. num_slots is firmware-controlled too * (DPM MappingInformation slot field). */ /* Realistic adjacent-slab case: dev_idx just past max_devices lands the * write in the NEXT kernel heap object (the classic exploitation target). * A huge dev_idx (e.g. 0x1000) also works but lands far away; the precise * overflow shown here is the controlled primitive an attacker grooms for. */ uint32_t dev_idx = MAX_DEVICES + 2; /* firmware-controlled, == max_devices+2 */ uint16_t num_slots = 4; /* writes 4 consecutive entries */ uint32_t map_idx; struct dev_mapping_table *mt_entry; printf("[DF-1282] attacker/firmware DeviceIndex=%u, num_slots=%u\n", dev_idx, num_slots); /* The ONLY guard present in the kernel is for the IR path. The Enc/Slot * path reaches the indexing with no check: */ if (dev_idx >= MAX_DEVICES) { printf("[DF-1282] BUG CONFIRMED: dev_idx=%u >= max_devices=%u -> " "write at mapping_table[%u] is %zu bytes PAST the allocation " "(into the adjacent slab object)\n", dev_idx, MAX_DEVICES, dev_idx, (size_t)(dev_idx - MAX_DEVICES) * sizeof(*table)); } /* Execute the unguarded loop exactly as mpr_mapping.c:2327-2347 */ mt_entry = &table[dev_idx]; /* :2327 OOB pointer */ for (map_idx = dev_idx; map_idx < (dev_idx + num_slots); map_idx++, mt_entry++) { /* :2328 */ mt_entry->physical_id = 0x4141414141414141ULL; /* attacker-shaped */ mt_entry->phy_bits = 0x42424242; mt_entry->id = dev_idx; mt_entry->dpm_entry_num = 1; mt_entry->missing_count = 0; mt_entry->device_info = 0x43434343; /* MPR_DEV_RESERVED stand-in */ } /* Show the guard region got corrupted (== next slab object trashed) */ unsigned hits = 0; for (unsigned g = MAX_DEVICES; g < MAX_DEVICES + GUARD_N; g++) { uint8_t *p = (uint8_t *)&table[g]; uint8_t *end = p + sizeof(table[g]); for (; p < end; p++) if (*p != 0xAA) { hits++; break; } } printf("[DF-1282] guard/slab region corrupted: %s (%u of %u entries " "touched)\n", hits ? "YES -> next heap object(s) overwritten" : "no", hits, GUARD_N); /* Now show the FIX: clamping dev_idx/num_slots prevents the OOB. */ if (dev_idx >= MAX_DEVICES || dev_idx + num_slots > MAX_DEVICES) { printf("[DF-1282] FIX: with bounds check, write SKIPPED " "(dev_idx=%u >= max_devices=%u) -> no corruption\n", dev_idx, MAX_DEVICES); } free(table); return hits ? 0 : 2; /* exit 0 == primitive demonstrated */ } |