4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice unmodified, this list of conditions, and the following 10 * disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27#include <sys/cdefs.h>
|
29 30/* 31 * MemGuard is a simple replacement allocator for debugging only 32 * which provides ElectricFence-style memory barrier protection on 33 * objects being allocated, and is used to detect tampering-after-free 34 * scenarios. 35 * 36 * See the memguard(9) man page for more information on using MemGuard. 37 */ 38 39#include <sys/param.h> 40#include <sys/systm.h> 41#include <sys/kernel.h> 42#include <sys/types.h> 43#include <sys/queue.h> 44#include <sys/lock.h> 45#include <sys/mutex.h> 46#include <sys/malloc.h> 47 48#include <vm/vm.h> 49#include <vm/vm_param.h> 50#include <vm/vm_page.h> 51#include <vm/vm_map.h> 52#include <vm/vm_extern.h> 53#include <vm/memguard.h> 54 55/* 56 * The maximum number of pages allowed per allocation. If you're using 57 * MemGuard to override very large items (> MAX_PAGES_PER_ITEM in size), 58 * you need to increase MAX_PAGES_PER_ITEM. 59 */ 60#define MAX_PAGES_PER_ITEM 64 61 62/* 63 * Global MemGuard data. 64 */ 65static vm_map_t memguard_map; 66static unsigned long memguard_mapsize; 67static unsigned long memguard_mapused; 68struct memguard_entry { 69 STAILQ_ENTRY(memguard_entry) entries; 70 void *ptr; 71}; 72static struct memguard_fifo { 73 struct memguard_entry *stqh_first; 74 struct memguard_entry **stqh_last; 75 int index; 76} memguard_fifo_pool[MAX_PAGES_PER_ITEM]; 77 78/* 79 * Local prototypes. 80 */ 81static void memguard_guard(void *addr, int numpgs); 82static void memguard_unguard(void *addr, int numpgs); 83static struct memguard_fifo *vtomgfifo(vm_offset_t va); 84static void vsetmgfifo(vm_offset_t va, struct memguard_fifo *mgfifo); 85static void vclrmgfifo(vm_offset_t va); 86 87/* 88 * Local macros. MemGuard data is global, so replace these with whatever 89 * your system uses to protect global data (if it is kernel-level 90 * parallelized). This is for porting among BSDs. 91 */ 92#define MEMGUARD_CRIT_SECTION_DECLARE static struct mtx memguard_mtx 93#define MEMGUARD_CRIT_SECTION_INIT \ 94 mtx_init(&memguard_mtx, "MemGuard mtx", NULL, MTX_DEF) 95#define MEMGUARD_CRIT_SECTION_ENTER mtx_lock(&memguard_mtx) 96#define MEMGUARD_CRIT_SECTION_EXIT mtx_unlock(&memguard_mtx) 97MEMGUARD_CRIT_SECTION_DECLARE; 98 99/* 100 * Initialize the MemGuard mock allocator. All objects from MemGuard come 101 * out of a single VM map (contiguous chunk of address space). 102 */ 103void 104memguard_init(vm_map_t parent_map, unsigned long size) 105{ 106 char *base, *limit; 107 int i; 108 109 /* size must be multiple of PAGE_SIZE */ 110 size /= PAGE_SIZE; 111 size++; 112 size *= PAGE_SIZE; 113 114 memguard_map = kmem_suballoc(parent_map, (vm_offset_t *)&base, 115 (vm_offset_t *)&limit, (vm_size_t)size); 116 memguard_map->system_map = 1; 117 memguard_mapsize = size; 118 memguard_mapused = 0; 119 120 MEMGUARD_CRIT_SECTION_INIT; 121 MEMGUARD_CRIT_SECTION_ENTER; 122 for (i = 0; i < MAX_PAGES_PER_ITEM; i++) { 123 STAILQ_INIT(&memguard_fifo_pool[i]); 124 memguard_fifo_pool[i].index = i; 125 } 126 MEMGUARD_CRIT_SECTION_EXIT; 127 128 printf("MEMGUARD DEBUGGING ALLOCATOR INITIALIZED:\n"); 129 printf("\tMEMGUARD map base: %p\n", base); 130 printf("\tMEMGUARD map limit: %p\n", limit); 131 printf("\tMEMGUARD map size: %ld (Bytes)\n", size); 132} 133 134/* 135 * Allocate a single object of specified size with specified flags (either 136 * M_WAITOK or M_NOWAIT). 137 */ 138void * 139memguard_alloc(unsigned long size, int flags) 140{ 141 void *obj; 142 struct memguard_entry *e = NULL; 143 int numpgs; 144 145 numpgs = size / PAGE_SIZE; 146 if ((size % PAGE_SIZE) != 0) 147 numpgs++; 148 if (numpgs > MAX_PAGES_PER_ITEM) 149 panic("MEMGUARD: You must increase MAX_PAGES_PER_ITEM " \ 150 "in memguard.c (requested: %d pages)", numpgs); 151 if (numpgs == 0) 152 return NULL; 153 154 /* 155 * If we haven't exhausted the memguard_map yet, allocate from 156 * it and grab a new page, even if we have recycled pages in our 157 * FIFO. This is because we wish to allow recycled pages to live 158 * guarded in the FIFO for as long as possible in order to catch 159 * even very late tamper-after-frees, even though it means that 160 * we end up wasting more memory, this is only a DEBUGGING allocator 161 * after all. 162 */ 163 MEMGUARD_CRIT_SECTION_ENTER; 164 if (memguard_mapused >= memguard_mapsize) { 165 e = STAILQ_FIRST(&memguard_fifo_pool[numpgs - 1]); 166 if (e != NULL) { 167 STAILQ_REMOVE(&memguard_fifo_pool[numpgs - 1], e, 168 memguard_entry, entries); 169 MEMGUARD_CRIT_SECTION_EXIT; 170 obj = e->ptr; 171 free(e, M_TEMP); 172 memguard_unguard(obj, numpgs); 173 if (flags & M_ZERO) 174 bzero(obj, PAGE_SIZE * numpgs); 175 return obj; 176 } 177 MEMGUARD_CRIT_SECTION_EXIT; 178 if (flags & M_WAITOK) 179 panic("MEMGUARD: Failed with M_WAITOK: " \ 180 "memguard_map too small"); 181 return NULL; 182 } 183 memguard_mapused += (PAGE_SIZE * numpgs); 184 MEMGUARD_CRIT_SECTION_EXIT; 185 186 obj = (void *)kmem_malloc(memguard_map, PAGE_SIZE * numpgs, flags); 187 if (obj != NULL) { 188 vsetmgfifo((vm_offset_t)obj, &memguard_fifo_pool[numpgs - 1]); 189 if (flags & M_ZERO) 190 bzero(obj, PAGE_SIZE * numpgs); 191 } else { 192 MEMGUARD_CRIT_SECTION_ENTER; 193 memguard_mapused -= (PAGE_SIZE * numpgs); 194 MEMGUARD_CRIT_SECTION_EXIT; 195 } 196 return obj; 197} 198 199/* 200 * Free specified single object. 201 */ 202void 203memguard_free(void *addr) 204{ 205 struct memguard_entry *e; 206 struct memguard_fifo *mgfifo; 207 int idx; 208 int *temp; 209 210 addr = (void *)trunc_page((unsigned long)addr); 211 212 /* 213 * Page should not be guarded by now, so force a write. 214 * The purpose of this is to increase the likelihood of catching a 215 * double-free, but not necessarily a tamper-after-free (the second 216 * thread freeing might not write before freeing, so this forces it 217 * to and, subsequently, trigger a fault). 218 */ 219 temp = (int *)((unsigned long)addr + (PAGE_SIZE/2)); /* in page */ 220 *temp = 0xd34dc0d3; 221 222 mgfifo = vtomgfifo((vm_offset_t)addr); 223 idx = mgfifo->index; 224 memguard_guard(addr, idx + 1); 225 e = malloc(sizeof(struct memguard_entry), M_TEMP, M_NOWAIT); 226 if (e == NULL) { 227 MEMGUARD_CRIT_SECTION_ENTER; 228 memguard_mapused -= (PAGE_SIZE * (idx + 1)); 229 MEMGUARD_CRIT_SECTION_EXIT; 230 memguard_unguard(addr, idx + 1); /* just in case */ 231 vclrmgfifo((vm_offset_t)addr); 232 kmem_free(memguard_map, (vm_offset_t)addr, 233 PAGE_SIZE * (idx + 1)); 234 return; 235 } 236 e->ptr = addr; 237 MEMGUARD_CRIT_SECTION_ENTER; 238 STAILQ_INSERT_TAIL(mgfifo, e, entries); 239 MEMGUARD_CRIT_SECTION_EXIT; 240} 241 242/* 243 * Guard a page containing specified object (make it read-only so that 244 * future writes to it fail). 245 */ 246static void 247memguard_guard(void *addr, int numpgs) 248{ 249 void *a = (void *)trunc_page((unsigned long)addr); 250 if (vm_map_protect(memguard_map, (vm_offset_t)a, 251 (vm_offset_t)((unsigned long)a + (PAGE_SIZE * numpgs)), 252 VM_PROT_READ, FALSE) != KERN_SUCCESS) 253 panic("MEMGUARD: Unable to guard page!"); 254} 255 256/* 257 * Unguard a page containing specified object (make it read-and-write to 258 * allow full data access). 259 */ 260static void 261memguard_unguard(void *addr, int numpgs) 262{ 263 void *a = (void *)trunc_page((unsigned long)addr); 264 if (vm_map_protect(memguard_map, (vm_offset_t)a, 265 (vm_offset_t)((unsigned long)a + (PAGE_SIZE * numpgs)), 266 VM_PROT_DEFAULT, FALSE) != KERN_SUCCESS) 267 panic("MEMGUARD: Unable to unguard page!"); 268} 269 270/* 271 * vtomgfifo() converts a virtual address of the first page allocated for 272 * an item to a memguard_fifo_pool reference for the corresponding item's 273 * size. 274 * 275 * vsetmgfifo() sets a reference in an underlying page for the specified 276 * virtual address to an appropriate memguard_fifo_pool. 277 * 278 * These routines are very similar to those defined by UMA in uma_int.h. 279 * The difference is that these routines store the mgfifo in one of the 280 * page's fields that is unused when the page is wired rather than the 281 * object field, which is used. 282 */ 283static struct memguard_fifo * 284vtomgfifo(vm_offset_t va) 285{ 286 vm_page_t p; 287 struct memguard_fifo *mgfifo; 288 289 p = PHYS_TO_VM_PAGE(pmap_kextract(va)); 290 KASSERT(p->wire_count != 0 && p->queue == PQ_NONE, 291 ("MEMGUARD: Expected wired page in vtomgfifo!")); 292 mgfifo = (struct memguard_fifo *)p->pageq.tqe_next; 293 return mgfifo; 294} 295 296static void 297vsetmgfifo(vm_offset_t va, struct memguard_fifo *mgfifo) 298{ 299 vm_page_t p; 300 301 p = PHYS_TO_VM_PAGE(pmap_kextract(va)); 302 KASSERT(p->wire_count != 0 && p->queue == PQ_NONE, 303 ("MEMGUARD: Expected wired page in vsetmgfifo!")); 304 p->pageq.tqe_next = (vm_page_t)mgfifo; 305} 306 307static void vclrmgfifo(vm_offset_t va) 308{ 309 vm_page_t p; 310 311 p = PHYS_TO_VM_PAGE(pmap_kextract(va)); 312 KASSERT(p->wire_count != 0 && p->queue == PQ_NONE, 313 ("MEMGUARD: Expected wired page in vclrmgfifo!")); 314 p->pageq.tqe_next = NULL; 315}
|