DF-5535 / mddiag.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 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | /* * mddiag.c - DF-5535 / DF-5536 diagnostic rig for * sys/platform/pc64/x86_64/minidump_machdep.c * * Modes (via `sysctl debug.mddiag=<mode>` as root): * report - print the PDP layout facts, do nothing * panic - print facts, then panic() so a real minidump is written * booby - print facts, plant a booby-trapped PD entry (bogus frame, V set) * in the slot-1 PD page, then panic() */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/module.h> #include <sys/sysctl.h> #include <sys/types.h> #include <vm/vm.h> #include <vm/vm_page.h> #include <vm/vm_page2.h> #include <vm/pmap.h> #include <machine/pmap.h> #include <machine/md_var.h> #include <machine/vmparam.h> /* 2^47 physical frame: far beyond phys_avail; targets bitmap+4GB (unused slot 5 VA) */ #define BOGUS_FRAME 0x0000800000000000UL static void report(void) { uint64_t *pdp; int slots[] = { 0, 1, 2, 8189, 8190, 8191 }; int s; pdp = (uint64_t *)PHYS_TO_DMAP(KPDPphys); kprintf("DFRIG: VM_MIN_KERNEL_ADDRESS = %p\n", (void *)VM_MIN_KERNEL_ADDRESS); kprintf("DFRIG: KPDPPHYS_KVA = %p\n", (void *)KPDPPHYS_KVA); kprintf("DFRIG: KERNBASE = %p\n", (void *)KERNBASE); kprintf("DFRIG: KvaEnd = %p\n", (void *)KvaEnd); kprintf("DFRIG: KPDPphys=0x%lx, pdp array entries = NKPML4E*NPDPEPG = %ld\n", (unsigned long)KPDPphys, (long)(NKPML4E * NPDPEPG)); for (s = 0; s < (int)(sizeof(slots) / sizeof(slots[0])); s++) { int i = slots[s]; kprintf("DFRIG: pdp[%d] = 0x%016lx (frame 0x%09lx V=%d)\n", i, (unsigned long)pdp[i], (unsigned long)(pdp[i] & PG_FRAME), (pdp[i] & kernel_pmap->pmap_bits[PG_V_IDX]) ? 1 : 0); } kprintf("DFRIG: walk1 computes i = (va - VM_MIN_KERNEL_ADDRESS) >> PDPSHIFT\n"); kprintf("DFRIG: for va=KERNBASE: walk1 i = %d, correct slot = %d\n", (int)(((unsigned long)KERNBASE - VM_MIN_KERNEL_ADDRESS) >> PDPSHIFT), (int)(((unsigned long)KERNBASE - KPDPPHYS_KVA) >> PDPSHIFT)); kprintf("DFRIG: walk1 max i (va=KvaEnd-1) = %d, so pdp[8191] is never read\n", (int)(((unsigned long)(KvaEnd - 1) - VM_MIN_KERNEL_ADDRESS) >> PDPSHIFT)); kprintf("DFRIG: hardware truth: vtophys(KERNBASE)=0x%lx = pdp[8190]&PG_FRAME=0x%lx\n", (unsigned long)vtophys(KERNBASE), (unsigned long)(pdp[8190] & PG_FRAME)); /* census of slot 8191's PD page: what actually lives there */ { uint64_t *pd; int n = 0, firstj = -1, j; pd = (uint64_t *)PHYS_TO_DMAP(pdp[8191] & PG_FRAME); for (j = 0; j < NPDEPG; j++) { if (pd[j] & kernel_pmap->pmap_bits[PG_V_IDX]) { if (firstj < 0) firstj = j; n++; } } kprintf("DFRIG: slot8191 PD page (0x%lx): %d/%ld present entries", (unsigned long)(pdp[8191] & PG_FRAME), n, (long)NPDEPG); if (firstj >= 0) kprintf(" (first j=%d val=0x%lx)", firstj, (unsigned long)pd[firstj]); kprintf("\n"); /* count leaf mappings inside slot8191's PTs */ { uint64_t psv = kernel_pmap->pmap_bits[PG_PS_IDX]; uint64_t v = kernel_pmap->pmap_bits[PG_V_IDX]; long leaf = 0, ps2m = 0, ptpages = 0; int jj; for (jj = 0; jj < NPDEPG; jj++) { if ((pd[jj] & v) == 0) continue; if (pd[jj] & psv) { ps2m++; continue; } ptpages++; { uint64_t *pt = (uint64_t *)PHYS_TO_DMAP( pd[jj] & PG_FRAME); int k; for (k = 0; k < NPTEPG; k++) if (pt[k] & v) leaf++; } } kprintf("DFRIG: slot8191 content: %ld PT pages, " "%ld 2M pages, %ld leaf 4K mappings LOST from dump\n", ptpages, ps2m, leaf); } } kprintf("DFRIG: vm_page_dump=%p size=%lu (%lu pages covered)\n", (void *)vm_page_dump, (unsigned long)vm_page_dump_size, (unsigned long)(vm_page_dump_size * NBBY)); kprintf("DFRIG: booby write target: vm_page_dump[0x%lx] = %p\n", BOGUS_FRAME >> 18, (void *)&vm_page_dump[BOGUS_FRAME >> 18]); } static int dumpbit(vm_paddr_t pa) { uint64_t pg = pa >> PAGE_SHIFT; return (vm_page_dump[pg >> 6] >> (pg & 63)) & 1; } static void maptest(void) { vm_offset_t va1 = 0xffffffffd0000000UL; /* slot 8191 (via PDP[8191]) */ vm_offset_t va2 = 0xffffffffa0000000UL; /* slot 8190 (via PDP[8190]) */ vm_page_t m1 = NULL, m2 = NULL; int t; for (t = 0; t < 128; t++) { m1 = vm_page_alloc(NULL, 0, VM_ALLOC_NORMAL | VM_ALLOC_SYSTEM); if (m1 == NULL) break; if (dumpbit(VM_PAGE_TO_PHYS(m1)) == 0) break; vm_page_free(m1); m1 = NULL; } for (t = 0; t < 128; t++) { m2 = vm_page_alloc(NULL, 0, VM_ALLOC_NORMAL | VM_ALLOC_SYSTEM); if (m2 == NULL) break; if (dumpbit(VM_PAGE_TO_PHYS(m2)) == 0) break; vm_page_free(m2); m2 = NULL; } if (m1 == NULL || m2 == NULL) kprintf("DFRIG: maptest: alloc failed m1=%p m2=%p\n", m1, m2); pmap_growkernel(va1, va1 + PAGE_SIZE); pmap_growkernel(va2, va2 + PAGE_SIZE); pmap_kenter(va1, VM_PAGE_TO_PHYS(m1)); pmap_kenter(va2, VM_PAGE_TO_PHYS(m2)); kprintf("DFRIG: maptest: va1=%p (slot 8191) -> pa 0x%lx, pre-panic bit=%d\n", (void *)va1, (unsigned long)VM_PAGE_TO_PHYS(m1), dumpbit(VM_PAGE_TO_PHYS(m1))); kprintf("DFRIG: maptest: va2=%p (slot 8190) -> pa 0x%lx, pre-panic bit=%d\n", (void *)va2, (unsigned long)VM_PAGE_TO_PHYS(m2), dumpbit(VM_PAGE_TO_PHYS(m2))); kprintf("DFRIG: maptest: after dump, pa(slot8191) must be CLEAR, " "pa(slot8190) must be SET\n"); } static void booby(void) { uint64_t *pdp; uint64_t vbit = kernel_pmap->pmap_bits[PG_V_IDX]; int i, nused = 0; pdp = (uint64_t *)PHYS_TO_DMAP(KPDPphys); /* * Find an unused PD slot far beyond the allocation frontier * (kernel_vm_end), so nothing allocates into it between now and * the dump walk. */ for (i = 64; i < NKPML4E * NPDPEPG; i++) { if (pdp[i] & vbit) nused++; else break; } if (i >= NKPML4E * NPDPEPG) { kprintf("DFRIG: booby: no unused slot; aborting\n"); return; } kprintf("DFRIG: booby: first unused PD slot = %d (%d in use); VA %p..%p unused\n", i, nused, (void *)(KPDPPHYS_KVA + ((vm_offset_t)i << PDPSHIFT)), (void *)(KPDPPHYS_KVA + ((vm_offset_t)(i + 1) << PDPSHIFT))); /* * Plant a fake PD entry pointing at a bogus physical frame. * Slot i has no mappings, so nothing live is disturbed. * Walk hits it: line 254 V-check passes, line 260 does the * unguarded dump_add_page(BOGUS_FRAME), line 263 does * PHYS_TO_DMAP(BOGUS_FRAME). */ pdp[i] = BOGUS_FRAME | vbit; kprintf("DFRIG: booby planted: pdp[%d] = 0x%lx|V\n", i, BOGUS_FRAME); kprintf("DFRIG: dump walk will call dump_add_page(0x%lx) unguarded -> " "vm_page_dump[0x%lx] = %p\n", BOGUS_FRAME, BOGUS_FRAME >> 18, (void *)&vm_page_dump[BOGUS_FRAME >> 18]); } static int sysctl_mddiag(SYSCTL_HANDLER_ARGS) { char buf[32]; int error; bzero(buf, sizeof(buf)); error = sysctl_handle_string(oidp, buf, sizeof(buf), req); if (error != 0 || req->newptr == NULL) return (error); if (strcmp(buf, "report") == 0) { report(); } else if (strcmp(buf, "panic") == 0) { report(); panic("DF-5535 rig: intentional panic for minidump capture"); /* NOTREACHED */ } else if (strcmp(buf, "maptest") == 0) { report(); maptest(); panic("DF-5535 maptest: slot-8191 mapping vs slot-8190 control"); /* NOTREACHED */ } else if (strcmp(buf, "booby") == 0) { report(); booby(); panic("DF-5536 rig: intentional panic with booby PD entry"); /* NOTREACHED */ } return (0); } SYSCTL_PROC(_debug, OID_AUTO, mddiag, CTLTYPE_STRING | CTLFLAG_RW, 0, 0, sysctl_mddiag, "A", "minidump diagnostic rig"); static int mddiag_load(module_t mod, int cmd, void *arg) { switch (cmd) { case MOD_LOAD: kprintf("mddiag loaded; use: sysctl debug.mddiag=report|panic|booby\n"); report(); break; default: break; } return (0); } static moduledata_t mddiag_mod = { "mddiag", mddiag_load, NULL }; DECLARE_MODULE(mddiag, mddiag_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |