1 /*
2 * Derived from Botan's Mlock Allocator
3 * 
4 * This is a more advanced base allocator.
5 * 
6 * (C) 2012,2014 Jack Lloyd
7 * (C) 2014-2015 Etienne Cimon
8 * (C) 2014,2015 Etienne Cimon
9 *
10 * Distributed under the terms of the Simplified BSD License (see Botan's license.txt)
11 */
12 module memutils.securepool;
13 import memutils.constants;
14 static if (HasSecurePool):
15 
16 package:
17 
18 import memutils.rbtree;
19 import std.algorithm;
20 import core.sync.mutex;
21 import std.conv : to;
22 import memutils.allocators;
23 import memutils.utils : Malloc;
24 
25 version(Posix) {
26 
27 	version(linux) import core.sys.linux.sys.mman;
28 	else version(Posix) import core.sys.posix.sys.mman;
29 	import core.sys.posix.sys.resource;
30 	
31 	enum {
32 		RLIMIT_MEMLOCK = 8
33 	}
34 	version(OSX) 
35 		enum MAP_LOCKED = 0;
36 	enum { MADV_DONTDUMP = 16 }
37 }
38 version(Windows) {
39 	@nogc private nothrow pure:
40 	import core.sys.windows.windows;
41 
42 	enum PROT_READ = 0;
43 	enum PROT_WRITE = 0;
44 	enum MAP_ANON = 0;
45 	enum MAP_SHARED = 0;
46 	enum MAP_LOCKED = 0;
47 	void* MAP_FAILED = null;
48 
49 	extern(Windows) {
50 		BOOL VirtualLock(LPVOID lpAddress, SIZE_T dwSize);
51 		BOOL VirtualUnlock(LPVOID lpAddress, SIZE_T dwSize);
52 		LPVOID VirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
53 		BOOL VirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType);
54 		BOOL SetProcessWorkingSetSize(HANDLE hProcess, SIZE_T dwMinimumWorkingSetSize, SIZE_T dwMaximumWorkingSetSize);
55 	}
56 	int mlock(void* ptr, size_t sz) {
57 		return cast(int) VirtualLock(cast(LPVOID) ptr, cast(SIZE_T) sz);
58 	}
59 	
60 	int munlock(void* ptr, size_t sz) {
61 		
62 		return cast(int) VirtualUnlock(cast(LPVOID) ptr, cast(SIZE_T) sz);
63 	}
64 	
65 	private void* mmap(void* ptr, size_t length, int prot, int flags, int fd, size_t offset) {
66 		enum MEM_RESERVE     = 0x00002000;
67 		enum MEM_COMMIT     = 0x00001000;
68 		enum PAGE_READWRITE = 0x04;
69 		return cast(void*) VirtualAlloc(cast(LPVOID) null, cast(SIZE_T) length, cast(DWORD) (MEM_RESERVE | MEM_COMMIT), cast(DWORD) PAGE_READWRITE);
70 	}
71 	
72 	void munmap(void* ptr, size_t sz)
73 	{
74 		enum MEM_RELEASE = 0x8000;
75 		VirtualFree(cast(LPVOID) ptr, cast(SIZE_T) sz, cast(DWORD) MEM_RELEASE);
76 	}
77 	
78 }
79 
80 final class SecurePool
81 {
82 	__gshared immutable size_t alignment = 0x08;
83 public:
84 	void[] alloc(size_t n)
85 	{
86 		synchronized(m_mtx) {
87 			if (!m_pool)
88 				return null;
89 			
90 			if (n > m_pool.length || n > SecurePool_MLock_Max)
91 				return null;
92 			
93 			void[]* best_fit_ref;
94 			void[] best_fit;
95 			size_t i;
96 			foreach (ref slot; m_freelist[])
97 			{
98 				// If we have a perfect fit, use it immediately
99 				if (slot.length == n && (cast(size_t)slot.ptr % alignment) == 0)
100 				{
101 					m_freelist.remove(slot);					
102 					assert(cast(size_t)(slot.ptr - m_pool.ptr) % alignment == 0, "Returning correctly aligned pointer");
103 					
104 					return slot;
105 				}
106 				
107 				if (slot.length >= (n + padding_for_alignment(cast(size_t)slot.ptr, alignment) ) &&
108 					( !best_fit_ref || (best_fit.length > slot.length) ) )
109 				{
110 					best_fit_ref = &slot;
111 					best_fit = slot;
112 				}
113 				i++;
114 			}
115 			
116 			if (best_fit_ref)
117 			{
118 				const size_t alignment_padding = padding_for_alignment(cast(size_t)best_fit.ptr, alignment);
119 				
120 				// absorb misalignment
121 				void[] remainder = (best_fit.ptr + n + alignment_padding)[0 .. best_fit.length - (n + alignment_padding)];
122 				if (remainder.length > 0) {
123 					*best_fit_ref = remainder;
124 				} else {
125 					m_freelist.remove(best_fit);
126 				}
127 
128 				size_t offset = best_fit.ptr - m_pool.ptr;
129 				
130 				assert((cast(size_t)(m_pool.ptr) + offset + alignment_padding) % alignment == 0, "Returning correctly aligned pointer");
131 				
132 				return (best_fit.ptr + alignment_padding)[0 .. n];
133 			}
134 			
135 			return null;
136 		}
137 	}
138 
139 	bool has(void[] mem) {
140 		synchronized(m_mtx) {
141 			if (!m_pool)
142 				return false;
143 			
144 			if (!ptr_in_pool(m_pool, mem.ptr, mem.length))
145 				return false;
146 		}
147 		return true;
148 	}
149 
150 	bool free(void[] mem)
151 	{
152 		if (!has(mem)) return false;
153 
154 		synchronized(m_mtx) {
155 			import std.range : front, empty;
156 
157 
158 			bool is_merged;
159 			void[] combined;
160 			
161 			auto upper_range = m_freelist.upperBoundRange(mem);
162 			if (!upper_range.empty && upper_range.front().ptr > (mem.ptr + mem.length) && (upper_range.front().ptr - alignment) < (mem.ptr + mem.length))
163 			{
164 				//import std.stdio : writeln;
165 				logTrace("Pool item (>): ", upper_range.front().ptr, " .. ", upper_range.front().ptr + upper_range.front().length, " <==> ", mem.ptr, " .. ", mem.ptr + mem.length);
166 				
167 				// we can merge with the next block
168 				void[] upper_elem = upper_range.front();
169 				size_t alignment_padding = upper_elem.ptr - (mem.ptr + mem.length);
170 				assert(alignment_padding < alignment, "Alignment padding error on upper bound");
171 				combined = mem.ptr[0 .. mem.length + alignment_padding + upper_elem.length];
172 				
173 				m_freelist.remove(upper_elem);
174 				mem = combined;
175 			}
176 			
177 			auto lower_range = m_freelist.lowerBoundRange(mem);
178 			if (!lower_range.empty && (lower_range.back().ptr + lower_range.back().length) < mem.ptr && (lower_range.back().ptr + lower_range.back().length + alignment) > mem.ptr)
179 			{
180 				// import std.stdio : writeln;
181 				logTrace("Pool item (<): ", lower_range.back().ptr, " .. ", lower_range.back().ptr + lower_range.back().length, " <==> ", mem.ptr, " .. ", mem.ptr + mem.length);
182 				// we can merge with the next block
183 				void[] lower_elem = lower_range.back();
184 				size_t alignment_padding = mem.ptr - ( lower_range.back().ptr + lower_range.back().length );
185 				assert(alignment_padding < alignment, "Alignment padding error on lower bound " ~ mem.ptr.to!string ~ ": " ~ alignment_padding.to!string ~ "/" ~ alignment.to!string);
186 				combined = lower_elem.ptr[0 .. lower_elem.length + alignment_padding + mem.length];
187 				m_freelist.remove(lower_elem);
188 				mem = combined;
189 			}
190 			m_freelist.insert(mem);
191 			return true;
192 		}
193 	}
194 package:
195 	this()
196 	{
197 		logTrace("Loading SecurePool instance ...");
198 		m_mtx = new Mutex;
199 		
200 		auto pool_size = mlock_limit();
201 		
202 		if (pool_size)
203 		{
204 			void* pool_ptr;
205 			pool_ptr = mmap(null, pool_size,
206 				PROT_READ | PROT_WRITE,
207 				MAP_ANON | MAP_SHARED | MAP_LOCKED,
208 				-1, 0);
209 			
210 			if (pool_ptr == MAP_FAILED)
211 			{
212 				throw new Exception("Failed to mmap SecurePool pool");
213 			}
214 
215 			m_pool_unaligned = pool_ptr[0 .. pool_size];
216 
217 			import core.stdc.string : memset;
218 			memset(m_pool_unaligned.ptr, 0, m_pool_unaligned.length);
219 			
220 			if (mlock(m_pool_unaligned.ptr, m_pool_unaligned.length) != 0)
221 			{
222 				munmap(m_pool_unaligned.ptr, m_pool_unaligned.length);
223 				m_pool_unaligned = null;
224 				import core.stdc.errno;
225 				logError("Could not mlock " ~ to!string(pool_size) ~ " bytes: " ~ errno().to!string);
226 				return;
227 			}
228 			
229 			version(Posix) posix_madvise(m_pool_unaligned.ptr, m_pool_unaligned.length, MADV_DONTDUMP);
230 			
231 			m_pool = m_pool_unaligned.ptr[cast(size_t)m_pool_unaligned.ptr % alignment .. m_pool_unaligned.length - (m_pool_unaligned.length % alignment)];
232 			
233 			m_freelist.insert(m_pool);
234 		}
235 	}
236 	
237 	~this()
238 	{
239 		if (m_pool)
240 		{
241 			import core.stdc.string : memset;
242 			memset(m_pool_unaligned.ptr, 0, m_pool_unaligned.length);
243 			munlock(m_pool_unaligned.ptr, m_pool_unaligned.length);
244 			munmap(m_pool_unaligned.ptr, m_pool_unaligned.length);
245 			m_pool = null;
246 			m_pool_unaligned = null;
247 		}
248 	}
249 	
250 private:
251 	__gshared Mutex m_mtx;
252 	RBTree!(void[], "a.ptr < b.ptr", false, Malloc) m_freelist;
253 	void[] m_pool;
254 	void[] m_pool_unaligned;
255 }
256 
257 private:
258 
259 size_t mlock_limit()
260 {
261 	/*
262     * Linux defaults to only 64 KiB of mlockable memory per process
263     * (too small) but BSDs offer a small fraction of total RAM (more
264     * than we need). Bound the total mlock size to 512 KiB which is
265     * enough to run the entire test suite without spilling to non-mlock
266     * memory (and thus presumably also enough for many useful
267     * programs), but small enough that we should not cause problems
268     * even if many processes are mlocking on the same machine.
269     */
270 	__gshared immutable size_t MLOCK_UPPER_BOUND = 512*1024;
271 	
272 	version(Posix) {
273 		rlimit limits;
274 		getrlimit(RLIMIT_MEMLOCK, &limits);
275 		
276 		if (limits.rlim_cur < limits.rlim_max)
277 		{
278 			limits.rlim_cur = limits.rlim_max;
279 			setrlimit(RLIMIT_MEMLOCK, &limits);
280 			getrlimit(RLIMIT_MEMLOCK, &limits);
281 		}
282 		return std.algorithm.min(limits.rlim_cur, MLOCK_UPPER_BOUND);
283 	}
284 	version(Windows) {
285 		BOOL success = SetProcessWorkingSetSize(cast(void*)GetCurrentProcessId(), 512*1024, 315*4096);
286 		if (success == 0)
287 			return 0;
288 		return MLOCK_UPPER_BOUND;
289 	}
290 }
291 
292 bool ptr_in_pool(in void[] pool, in void* buf_ptr, size_t bufsize) pure
293 {
294 	if (buf_ptr < pool.ptr || buf_ptr >= pool.ptr + pool.length)
295 		return false;
296 	
297 	assert(buf_ptr + bufsize <= pool.ptr + pool.length, "Pointer does not partially overlap pool");
298 	
299 	return true;
300 }
301 
302 size_t padding_for_alignment(size_t offset, size_t desired_alignment) pure
303 {
304 	size_t mod = offset % desired_alignment;
305 	if (mod == 0)
306 		return 0; // already right on
307 	return desired_alignment - mod;
308 }