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 }