xref: /freebsd/sys/vm/memguard.c (revision eca64e79)
1e4eb384bSBosko Milekic /*
2e4eb384bSBosko Milekic  * Copyright (c) 2005,
3e4eb384bSBosko Milekic  *     Bosko Milekic <bmilekic@freebsd.org>
4e4eb384bSBosko Milekic  *
5e4eb384bSBosko Milekic  * Redistribution and use in source and binary forms, with or without
6e4eb384bSBosko Milekic  * modification, are permitted provided that the following conditions
7e4eb384bSBosko Milekic  * are met:
8e4eb384bSBosko Milekic  * 1. Redistributions of source code must retain the above copyright
9e4eb384bSBosko Milekic  *    notice unmodified, this list of conditions, and the following
10e4eb384bSBosko Milekic  *    disclaimer.
11e4eb384bSBosko Milekic  * 2. Redistributions in binary form must reproduce the above copyright
12e4eb384bSBosko Milekic  *    notice, this list of conditions and the following disclaimer in the
13e4eb384bSBosko Milekic  *    documentation and/or other materials provided with the distribution.
14e4eb384bSBosko Milekic  *
15e4eb384bSBosko Milekic  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16e4eb384bSBosko Milekic  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17e4eb384bSBosko Milekic  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18e4eb384bSBosko Milekic  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19e4eb384bSBosko Milekic  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20e4eb384bSBosko Milekic  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21e4eb384bSBosko Milekic  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22e4eb384bSBosko Milekic  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23e4eb384bSBosko Milekic  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24e4eb384bSBosko Milekic  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25e4eb384bSBosko Milekic  */
26e4eb384bSBosko Milekic 
27e4eb384bSBosko Milekic #include <sys/cdefs.h>
28e4eb384bSBosko Milekic __FBSDID("$FreeBSD$");
29e4eb384bSBosko Milekic 
30e4eb384bSBosko Milekic /*
31e4eb384bSBosko Milekic  * MemGuard is a simple replacement allocator for debugging only
32e4eb384bSBosko Milekic  * which provides ElectricFence-style memory barrier protection on
33e4eb384bSBosko Milekic  * objects being allocated, and is used to detect tampering-after-free
34e4eb384bSBosko Milekic  * scenarios.
35e4eb384bSBosko Milekic  *
36e4eb384bSBosko Milekic  * See the memguard(9) man page for more information on using MemGuard.
37e4eb384bSBosko Milekic  */
38e4eb384bSBosko Milekic 
39e4eb384bSBosko Milekic #include <sys/param.h>
40e4eb384bSBosko Milekic #include <sys/systm.h>
41e4eb384bSBosko Milekic #include <sys/kernel.h>
42e4eb384bSBosko Milekic #include <sys/types.h>
43e4eb384bSBosko Milekic #include <sys/queue.h>
44e4eb384bSBosko Milekic #include <sys/lock.h>
45e4eb384bSBosko Milekic #include <sys/mutex.h>
46e4eb384bSBosko Milekic #include <sys/malloc.h>
47e4eb384bSBosko Milekic 
48e4eb384bSBosko Milekic #include <vm/vm.h>
49e4eb384bSBosko Milekic #include <vm/vm_page.h>
50e4eb384bSBosko Milekic #include <vm/vm_map.h>
51e4eb384bSBosko Milekic #include <vm/vm_extern.h>
52e4eb384bSBosko Milekic #include <vm/memguard.h>
53e4eb384bSBosko Milekic 
54e4eb384bSBosko Milekic /*
55e4eb384bSBosko Milekic  * Global MemGuard data.
56e4eb384bSBosko Milekic  */
57e4eb384bSBosko Milekic static vm_map_t memguard_map;
58e4eb384bSBosko Milekic static unsigned long memguard_mapsize;
59e4eb384bSBosko Milekic static unsigned long memguard_mapused;
60e4eb384bSBosko Milekic struct memguard_entry {
61e4eb384bSBosko Milekic 	STAILQ_ENTRY(memguard_entry) entries;
62e4eb384bSBosko Milekic 	void *ptr;
63e4eb384bSBosko Milekic };
64e4eb384bSBosko Milekic static STAILQ_HEAD(memguard_fifo, memguard_entry) memguard_fifo_pool;
65e4eb384bSBosko Milekic 
66e4eb384bSBosko Milekic /*
67e4eb384bSBosko Milekic  * Local prototypes.
68e4eb384bSBosko Milekic  */
69e4eb384bSBosko Milekic static void	memguard_guard(void *addr);
70e4eb384bSBosko Milekic static void	memguard_unguard(void *addr);
71e4eb384bSBosko Milekic 
72e4eb384bSBosko Milekic /*
73e4eb384bSBosko Milekic  * Local macros.  MemGuard data is global, so replace these with whatever
74e4eb384bSBosko Milekic  * your system uses to protect global data (if it is kernel-level
75e4eb384bSBosko Milekic  * parallelized).  This is for porting among BSDs.
76e4eb384bSBosko Milekic  */
77e4eb384bSBosko Milekic #define	MEMGUARD_CRIT_SECTION_DECLARE	static struct mtx memguard_mtx
78e4eb384bSBosko Milekic #define	MEMGUARD_CRIT_SECTION_INIT				\
79e4eb384bSBosko Milekic 	mtx_init(&memguard_mtx, "MemGuard mtx", NULL, MTX_DEF)
80e4eb384bSBosko Milekic #define	MEMGUARD_CRIT_SECTION_ENTER	mtx_lock(&memguard_mtx)
81e4eb384bSBosko Milekic #define	MEMGUARD_CRIT_SECTION_EXIT	mtx_unlock(&memguard_mtx)
82e4eb384bSBosko Milekic MEMGUARD_CRIT_SECTION_DECLARE;
83e4eb384bSBosko Milekic 
84e4eb384bSBosko Milekic /*
85e4eb384bSBosko Milekic  * Initialize the MemGuard mock allocator.  All objects from MemGuard come
86e4eb384bSBosko Milekic  * out of a single VM map (contiguous chunk of address space).
87e4eb384bSBosko Milekic  */
88e4eb384bSBosko Milekic void
89e4eb384bSBosko Milekic memguard_init(vm_map_t parent_map, unsigned long size)
90e4eb384bSBosko Milekic {
91e4eb384bSBosko Milekic 	char *base, *limit;
92e4eb384bSBosko Milekic 
93e4eb384bSBosko Milekic 	/* size must be multiple of PAGE_SIZE */
94e4eb384bSBosko Milekic 	size /= PAGE_SIZE;
95e4eb384bSBosko Milekic 	size++;
96e4eb384bSBosko Milekic 	size *= PAGE_SIZE;
97e4eb384bSBosko Milekic 
98e4eb384bSBosko Milekic 	memguard_map = kmem_suballoc(parent_map, (vm_offset_t *)&base,
99e4eb384bSBosko Milekic 	    (vm_offset_t *)&limit, (vm_size_t)size);
100e4eb384bSBosko Milekic 	memguard_map->system_map = 1;
101e4eb384bSBosko Milekic 	memguard_mapsize = size;
102e4eb384bSBosko Milekic 	memguard_mapused = 0;
103e4eb384bSBosko Milekic 
104e4eb384bSBosko Milekic 	MEMGUARD_CRIT_SECTION_INIT;
105e4eb384bSBosko Milekic 	MEMGUARD_CRIT_SECTION_ENTER;
106e4eb384bSBosko Milekic 	STAILQ_INIT(&memguard_fifo_pool);
107e4eb384bSBosko Milekic 	MEMGUARD_CRIT_SECTION_EXIT;
108e4eb384bSBosko Milekic 
109e4eb384bSBosko Milekic 	printf("MEMGUARD DEBUGGING ALLOCATOR INITIALIZED:\n");
110e4eb384bSBosko Milekic 	printf("\tMEMGUARD map base: %p\n", base);
111e4eb384bSBosko Milekic 	printf("\tMEMGUARD map limit: %p\n", limit);
112e4eb384bSBosko Milekic 	printf("\tMEMGUARD map size: %ld (Bytes)\n", size);
113e4eb384bSBosko Milekic }
114e4eb384bSBosko Milekic 
115e4eb384bSBosko Milekic /*
116e4eb384bSBosko Milekic  * Allocate a single object of specified size with specified flags (either
117e4eb384bSBosko Milekic  * M_WAITOK or M_NOWAIT).
118e4eb384bSBosko Milekic  */
119e4eb384bSBosko Milekic void *
120e4eb384bSBosko Milekic memguard_alloc(unsigned long size, int flags)
121e4eb384bSBosko Milekic {
122e4eb384bSBosko Milekic 	void *obj = NULL;
123e4eb384bSBosko Milekic 	struct memguard_entry *e = NULL;
124e4eb384bSBosko Milekic 
125e4eb384bSBosko Milekic 	/* XXX: MemGuard does not handle > PAGE_SIZE objects. */
126e4eb384bSBosko Milekic 	if (size > PAGE_SIZE)
127e4eb384bSBosko Milekic 		panic("MEMGUARD: Cannot handle objects > PAGE_SIZE");
128e4eb384bSBosko Milekic 
129e4eb384bSBosko Milekic 	/*
130e4eb384bSBosko Milekic 	 * If we haven't exhausted the memguard_map yet, allocate from
131e4eb384bSBosko Milekic 	 * it and grab a new page, even if we have recycled pages in our
132e4eb384bSBosko Milekic 	 * FIFO.  This is because we wish to allow recycled pages to live
133e4eb384bSBosko Milekic 	 * guarded in the FIFO for as long as possible in order to catch
134e4eb384bSBosko Milekic 	 * even very late tamper-after-frees, even though it means that
135e4eb384bSBosko Milekic 	 * we end up wasting more memory, this is only a DEBUGGING allocator
136e4eb384bSBosko Milekic 	 * after all.
137e4eb384bSBosko Milekic 	 */
138e4eb384bSBosko Milekic 	MEMGUARD_CRIT_SECTION_ENTER;
139e4eb384bSBosko Milekic 	if (memguard_mapused >= memguard_mapsize) {
140e4eb384bSBosko Milekic 		e = STAILQ_FIRST(&memguard_fifo_pool);
141e4eb384bSBosko Milekic 		if (e != NULL) {
142e4eb384bSBosko Milekic 			STAILQ_REMOVE(&memguard_fifo_pool, e,
143e4eb384bSBosko Milekic 			    memguard_entry, entries);
144e4eb384bSBosko Milekic 			MEMGUARD_CRIT_SECTION_EXIT;
145e4eb384bSBosko Milekic 			obj = e->ptr;
146e4eb384bSBosko Milekic 			free(e, M_TEMP);
147e4eb384bSBosko Milekic 			memguard_unguard(obj);
148e4eb384bSBosko Milekic 			if (flags & M_ZERO)
149e4eb384bSBosko Milekic 				bzero(obj, PAGE_SIZE);
150e4eb384bSBosko Milekic 			return obj;
151e4eb384bSBosko Milekic 		}
152e4eb384bSBosko Milekic 		MEMGUARD_CRIT_SECTION_EXIT;
153e4eb384bSBosko Milekic 		if (flags & M_WAITOK)
154e4eb384bSBosko Milekic 			panic("MEMGUARD: Failed with M_WAITOK: " \
155e4eb384bSBosko Milekic 			    "memguard_map too small");
156e4eb384bSBosko Milekic 		return NULL;
157e4eb384bSBosko Milekic 	} else
158e4eb384bSBosko Milekic 		memguard_mapused += PAGE_SIZE;
159e4eb384bSBosko Milekic 	MEMGUARD_CRIT_SECTION_EXIT;
160e4eb384bSBosko Milekic 
161e4eb384bSBosko Milekic 	if (obj == NULL)
162e4eb384bSBosko Milekic 		obj = (void *)kmem_malloc(memguard_map, PAGE_SIZE, flags);
163e4eb384bSBosko Milekic 	if (obj != NULL) {
164e4eb384bSBosko Milekic 		memguard_unguard(obj);
165e4eb384bSBosko Milekic 		if (flags & M_ZERO)
166e4eb384bSBosko Milekic 			bzero(obj, PAGE_SIZE);
167e4eb384bSBosko Milekic 	} else {
168e4eb384bSBosko Milekic 		MEMGUARD_CRIT_SECTION_ENTER;
169e4eb384bSBosko Milekic 		memguard_mapused -= PAGE_SIZE;
170e4eb384bSBosko Milekic 		MEMGUARD_CRIT_SECTION_EXIT;
171e4eb384bSBosko Milekic 	}
172e4eb384bSBosko Milekic 	return obj;
173e4eb384bSBosko Milekic }
174e4eb384bSBosko Milekic 
175e4eb384bSBosko Milekic /*
176e4eb384bSBosko Milekic  * Free specified single object.
177e4eb384bSBosko Milekic  */
178e4eb384bSBosko Milekic void
179e4eb384bSBosko Milekic memguard_free(void *addr)
180e4eb384bSBosko Milekic {
181e4eb384bSBosko Milekic 	struct memguard_entry *e;
182e4eb384bSBosko Milekic 
183e4eb384bSBosko Milekic 	memguard_guard(addr);
184e4eb384bSBosko Milekic 	e = malloc(sizeof(struct memguard_entry), M_TEMP, M_NOWAIT);
185e4eb384bSBosko Milekic 	if (e == NULL) {
186e4eb384bSBosko Milekic 		MEMGUARD_CRIT_SECTION_ENTER;
187e4eb384bSBosko Milekic 		memguard_mapused -= PAGE_SIZE;
188e4eb384bSBosko Milekic 		MEMGUARD_CRIT_SECTION_EXIT;
189eca64e79SBosko Milekic 		kmem_free(memguard_map, (vm_offset_t)trunc_page(
190e4eb384bSBosko Milekic 		    (unsigned long)addr), PAGE_SIZE);
191e4eb384bSBosko Milekic 		return;
192e4eb384bSBosko Milekic 	}
193eca64e79SBosko Milekic 	e->ptr = (void *)trunc_page((unsigned long)addr);
194e4eb384bSBosko Milekic 	MEMGUARD_CRIT_SECTION_ENTER;
195e4eb384bSBosko Milekic 	STAILQ_INSERT_TAIL(&memguard_fifo_pool, e, entries);
196e4eb384bSBosko Milekic 	MEMGUARD_CRIT_SECTION_EXIT;
197e4eb384bSBosko Milekic }
198e4eb384bSBosko Milekic 
199e4eb384bSBosko Milekic /*
200e4eb384bSBosko Milekic  * Guard a page containing specified object (make it read-only so that
201e4eb384bSBosko Milekic  * future writes to it fail).
202e4eb384bSBosko Milekic  */
203e4eb384bSBosko Milekic static void
204e4eb384bSBosko Milekic memguard_guard(void *addr)
205e4eb384bSBosko Milekic {
206eca64e79SBosko Milekic 	void *a = (void *)trunc_page((unsigned long)addr);
207e4eb384bSBosko Milekic 	(void)vm_map_protect(memguard_map, (vm_offset_t)a,
208e4eb384bSBosko Milekic 	    (vm_offset_t)((unsigned long)a + PAGE_SIZE), VM_PROT_READ, 0);
209e4eb384bSBosko Milekic }
210e4eb384bSBosko Milekic 
211e4eb384bSBosko Milekic /*
212e4eb384bSBosko Milekic  * Unguard a page containing specified object (make it read-and-write to
213e4eb384bSBosko Milekic  * allow full data access).
214e4eb384bSBosko Milekic  */
215e4eb384bSBosko Milekic static void
216e4eb384bSBosko Milekic memguard_unguard(void *addr)
217e4eb384bSBosko Milekic {
218eca64e79SBosko Milekic 	void *a = (void *)trunc_page((unsigned long)addr);
219e4eb384bSBosko Milekic 	(void)vm_map_protect(memguard_map, (vm_offset_t)a,
220e4eb384bSBosko Milekic 	    (vm_offset_t)((unsigned long)a + PAGE_SIZE),
221e4eb384bSBosko Milekic 	    VM_PROT_READ | VM_PROT_WRITE, 0);
222e4eb384bSBosko Milekic }
223