memguard.c revision 141670
1/*
2 * Copyright (c) 2005,
3 *     Bosko Milekic <bmilekic@freebsd.org>
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>
28__FBSDID("$FreeBSD: head/sys/vm/memguard.c 141670 2005-02-10 22:36:05Z bmilekic $");
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 */
280static struct memguard_fifo *
281vtomgfifo(vm_offset_t va)
282{
283	vm_page_t p;
284	struct memguard_fifo *mgfifo;
285
286	p = PHYS_TO_VM_PAGE(pmap_kextract(va));
287	mgfifo = (struct memguard_fifo *)p->object;
288
289	/*
290	 * We use PG_SLAB, just like UMA does, even though we stash a
291	 * reference to a memguard_fifo, and not a slab.
292	 */
293	if ((p->flags & PG_SLAB) == 0)
294		panic("MEMGUARD: Expected memguard_fifo reference to be set!");
295	return mgfifo;
296}
297
298static void
299vsetmgfifo(vm_offset_t va, struct memguard_fifo *mgfifo)
300{
301	vm_page_t p;
302
303	p = PHYS_TO_VM_PAGE(pmap_kextract(va));
304	p->object = (vm_object_t)mgfifo;
305	/*
306	 * We use PG_SLAB, just like UMA does, even though we stash a reference
307	 * to a memguard_fifo, and not a slab.
308	 */
309	p->flags |= PG_SLAB;
310}
311
312static void vclrmgfifo(vm_offset_t va)
313{
314	vm_page_t p;
315
316	p = PHYS_TO_VM_PAGE(pmap_kextract(va));
317	p->object = NULL;
318	p->flags &= ~PG_SLAB;
319}
320