1 : /*
2 : * Copyright (c) 2012 The Native Client Authors. All rights reserved.
3 : * Use of this source code is governed by a BSD-style license that can be
4 : * found in the LICENSE file.
5 : */
6 :
7 : /*
8 : * NaCl Service Runtime memory allocation code
9 : */
10 : #include "native_client/src/include/portability.h"
11 : #include "native_client/src/include/win/mman.h"
12 :
13 : #include <errno.h>
14 : #include <windows.h>
15 : #include <string.h>
16 :
17 : #include "native_client/src/shared/platform/nacl_check.h"
18 : #include "native_client/src/shared/platform/nacl_global_secure_random.h"
19 : #include "native_client/src/shared/platform/nacl_log.h"
20 : #include "native_client/src/shared/platform/win/xlate_system_error.h"
21 :
22 : #include "native_client/src/trusted/service_runtime/nacl_config.h"
23 : #include "native_client/src/trusted/service_runtime/sel_memory.h"
24 : #include "native_client/src/trusted/service_runtime/sel_util.h"
25 :
26 : /*
27 : * NaClPageFree: free pages allocated with NaClPageAlloc.
28 : * Must start at allocation granularity (NACL_MAP_PAGESIZE) and
29 : * number of bytes must be a multiple of allocation granularity.
30 : */
31 1 : void NaClPageFree(void *p, size_t num_bytes) {
32 : void *end_addr;
33 :
34 1 : end_addr = (void *) (((char *) p) + num_bytes);
35 1 : while (p < end_addr) {
36 1 : if (!VirtualFree(p, 0, MEM_RELEASE)) {
37 0 : DWORD err = GetLastError();
38 : NaClLog(LOG_FATAL,
39 : "NaClPageFree: VirtualFree(0x%016"NACL_PRIxPTR
40 : ", 0, MEM_RELEASE) failed "
41 : "with error 0x%X\n",
42 0 : (uintptr_t) p, err);
43 : }
44 1 : p = (void *) (((char *) p) + NACL_MAP_PAGESIZE);
45 1 : }
46 1 : }
47 :
48 :
49 1 : int NaClPageAllocAtAddr(void **p, size_t num_bytes) {
50 : SYSTEM_INFO sys_info;
51 :
52 : int attempt_count;
53 :
54 1 : void *hint = *p;
55 : void *addr;
56 : void *end_addr;
57 : void *chunk;
58 : void *unroll;
59 :
60 : /*
61 : * We have to allocate every 64KB -- the windows allocation
62 : * granularity -- because VirtualFree will only accept an address
63 : * that was returned by a call to VirtualAlloc. NB: memory pages
64 : * put into the address space via MapViewOfFile{,Ex} must be
65 : * released by UnmapViewOfFile. Thus, in order for us to open up a
66 : * hole in the NaCl application's address space to map in a file, we
67 : * must allocate the entire address space in 64KB chunks, so we can
68 : * later pick an arbitrary range of addresses (in 64KB chunks) to
69 : * free up and map in a file later.
70 : *
71 : * First, we verify via GetSystemInfo that the allocation
72 : * granularity matches NACL_MAP_PAGESIZE.
73 : *
74 : * Next, we VirtualAlloc the entire chunk desired. This essentially
75 : * asks the kernel where there is space in the virtual address
76 : * space. Then, we free this back, and repeat the allocation
77 : * starting at the returned address, but in 64KB chunks. If any of
78 : * these smaller allocations fail, we roll back and try again.
79 : */
80 :
81 1 : NaClLog(3, "NaClPageAlloc(*, 0x%"NACL_PRIxS")\n", num_bytes);
82 1 : GetSystemInfo(&sys_info);
83 1 : if (NACL_PAGESIZE != sys_info.dwPageSize) {
84 : NaClLog(2, "page size is 0x%x; expected 0x%x\n",
85 : sys_info.dwPageSize,
86 0 : NACL_PAGESIZE);
87 : }
88 1 : if (NACL_MAP_PAGESIZE != sys_info.dwAllocationGranularity) {
89 : NaClLog(LOG_ERROR, "allocation granularity is 0x%x; expected 0x%x\n",
90 : sys_info.dwAllocationGranularity,
91 0 : NACL_MAP_PAGESIZE);
92 : }
93 :
94 : /*
95 : * Round allocation request up to next NACL_MAP_PAGESIZE. This is
96 : * assumed to have taken place in NaClPageFree.
97 : */
98 1 : num_bytes = NaClRoundAllocPage(num_bytes);
99 :
100 : for (attempt_count = 0;
101 : attempt_count < NACL_MEMORY_ALLOC_RETRY_MAX;
102 1 : ++attempt_count) {
103 :
104 1 : addr = VirtualAlloc(hint, num_bytes, MEM_RESERVE, PAGE_NOACCESS);
105 1 : if (addr == NULL) {
106 : NaClLog(LOG_ERROR,
107 : "NaClPageAlloc: VirtualAlloc(*,0x%"NACL_PRIxS") failed\n",
108 0 : num_bytes);
109 0 : return -ENOMEM;
110 : }
111 : NaClLog(3,
112 : ("NaClPageAlloc:"
113 : " VirtualAlloc(*,0x%"NACL_PRIxS")"
114 : " succeeded, 0x%016"NACL_PRIxPTR","
115 : " releasing and re-allocating in 64K chunks....\n"),
116 1 : num_bytes, (uintptr_t) addr);
117 1 : (void) VirtualFree(addr, 0, MEM_RELEASE);
118 : /*
119 : * We now know [addr, addr + num_bytes) is available in our addr space.
120 : * Get that memory again, but in 64KB chunks.
121 : */
122 1 : end_addr = (void *) (((char *) addr) + num_bytes);
123 : for (chunk = addr;
124 : chunk < end_addr;
125 1 : chunk = (void *) (((char *) chunk) + NACL_MAP_PAGESIZE)) {
126 : if (NULL == VirtualAlloc(chunk,
127 : NACL_MAP_PAGESIZE,
128 : MEM_RESERVE,
129 1 : PAGE_NOACCESS)) {
130 : NaClLog(LOG_ERROR,
131 : ("NaClPageAlloc: re-allocation failed at "
132 : "0x%016"NACL_PRIxPTR","
133 : " error %d\n"),
134 0 : (uintptr_t) chunk, GetLastError());
135 : for (unroll = addr;
136 : unroll < chunk;
137 0 : unroll = (void *) (((char *) unroll) + NACL_MAP_PAGESIZE)) {
138 0 : (void) VirtualFree(unroll, 0, MEM_RELEASE);
139 0 : }
140 0 : goto retry;
141 : /* double break */
142 : }
143 1 : }
144 : NaClLog(2,
145 : ("NaClPageAlloc: *p = 0x%016"NACL_PRIxPTR","
146 : " returning success.\n"),
147 1 : (uintptr_t) addr);
148 1 : *p = addr;
149 1 : return 0;
150 : retry:
151 0 : NaClLog(2, "NaClPageAllocAtAddr: retrying w/o hint\n");
152 0 : hint = NULL;
153 0 : }
154 :
155 0 : return -ENOMEM;
156 1 : }
157 :
158 1 : int NaClPageAlloc(void **p, size_t num_bytes) {
159 1 : *p = NULL;
160 1 : return NaClPageAllocAtAddr(p, num_bytes);
161 1 : }
162 :
163 : /*
164 : * Pick a "hint" address that is random.
165 : */
166 0 : int NaClPageAllocRandomized(void **p, size_t num_bytes) {
167 : uintptr_t addr;
168 0 : int neg_errno = -ENOMEM; /* in case we change kNumTries to 0 */
169 : int tries;
170 0 : const int kNumTries = 4;
171 :
172 0 : for (tries = 0; tries < kNumTries; ++tries) {
173 : #if NACL_HOST_WORDSIZE == 32
174 0 : addr = NaClGlobalSecureRngUint32();
175 0 : NaClLog(2, "NaClPageAllocRandomized: 0x%"NACL_PRIxPTR"\n", addr);
176 : /*
177 : * Windows permits 2-3 GB of user address space, depending on
178 : * 4-gigabyte tuning (4GT) parameter. We ask for somewhere in the
179 : * lower 2G.
180 : */
181 : *p = (void *) (addr & ~((uintptr_t) NACL_MAP_PAGESIZE - 1)
182 0 : & ((~(uintptr_t) 0) >> 1));
183 : #elif NACL_HOST_WORDSIZE == 64
184 : addr = NaClGlobalSecureRngUint32();
185 : NaClLog(2, "NaClPageAllocRandomized: 0x%"NACL_PRIxPTR"\n", addr);
186 : /*
187 : * 64-bit windows permits 8 TB of user address space, and we keep
188 : * the low 16 bits free (64K alignment), so we just have 43-16=27
189 : * bits of entropy.
190 : */
191 : *p = (void *) ((addr << NACL_MAP_PAGESHIFT) /* bits [47:16] are random */
192 : & ((((uintptr_t) 1) << 43) - 1)); /* now bits [42:16] */
193 : #else
194 : # error "where am i?"
195 : #endif
196 :
197 : NaClLog(2, "NaClPageAllocRandomized: hint 0x%"NACL_PRIxPTR"\n",
198 0 : (uintptr_t) *p);
199 0 : neg_errno = NaClPageAllocAtAddr(p, num_bytes);
200 0 : if (0 == neg_errno) {
201 0 : break;
202 : }
203 0 : }
204 0 : if (0 != neg_errno) {
205 : NaClLog(LOG_INFO,
206 : "NaClPageAllocRandomized: failed (%d), dropping hints\n",
207 0 : -neg_errno);
208 0 : *p = 0;
209 0 : neg_errno = NaClPageAllocAtAddr(p, num_bytes);
210 : }
211 0 : return neg_errno;
212 0 : }
213 :
214 : static uintptr_t NaClProtectChunkSize(uintptr_t start,
215 0 : uintptr_t end) {
216 : uintptr_t chunk_end;
217 :
218 0 : chunk_end = NaClRoundAllocPage(start + 1);
219 0 : if (chunk_end > end) {
220 0 : chunk_end = end;
221 : }
222 0 : return chunk_end - start;
223 0 : }
224 :
225 :
226 : /*
227 : * Change memory protection.
228 : *
229 : * This is critical to make the text region non-writable, and the data
230 : * region read/write but no exec. Of course, some kernels do not
231 : * respect the lack of PROT_EXEC.
232 : *
233 : * NB: this function signature is likely to need to change. In
234 : * particular, when on Windows, if the memory originated from a memory
235 : * mapping, whether the file was MAP_SHARED or MAP_PRIVATE and thus
236 : * whether the dwDesiredAccess used in the MapViewOfFileEx contained
237 : * FILE_MAP_WRITE or FILE_MAP_COPY determines whether the right
238 : * newProtection below should be PAGE_READWRITE or PAGE_WRITECOPY.
239 : * So, we need to have either extend prot to include a PROT_CoW, or
240 : * otherwise some way to look up whether the region had what level of
241 : * access when MapViewOfFileEx was invoked.
242 : *
243 : * For now, we implement a "fallback" mechanism on windows, where if
244 : * PAGE_READWRITE fails, we retry with PAGE_WRITECOPY. Fortunately,
245 : * this is only needed when setting up memory protection for the BSS
246 : * segment during program loading, when the executable was deemed to
247 : * be safe-for-mapping.
248 : */
249 0 : int NaClMprotect(void *addr, size_t len, int prot) {
250 : uintptr_t start_addr;
251 : uintptr_t end_addr;
252 : uintptr_t cur_addr;
253 : uintptr_t cur_chunk_size;
254 : DWORD newProtection, oldProtection;
255 : BOOL res;
256 : int xlated_res;
257 :
258 : NaClLog(2, "NaClMprotect(0x%016"NACL_PRIxPTR", 0x%"NACL_PRIxS", 0x%x)\n",
259 0 : (uintptr_t) addr, len, prot);
260 :
261 0 : if (len == 0) {
262 0 : return 0;
263 : }
264 :
265 0 : start_addr = (uintptr_t) addr;
266 0 : if (!NaClIsPageMultiple(start_addr)) {
267 0 : NaClLog(2, "NaClMprotect: start address not a multiple of page size\n");
268 0 : return -EINVAL;
269 : }
270 0 : if (!NaClIsPageMultiple(len)) {
271 0 : NaClLog(2, "NaClMprotect: length not a multiple of page size\n");
272 0 : return -EINVAL;
273 : }
274 0 : end_addr = start_addr + len;
275 :
276 : #define M(P) \
277 : do { \
278 : NaClLog(2, "NaClMprotect: newProtection %s, 0x%x\n", #P, P); \
279 : newProtection = P; \
280 : } while (0)
281 :
282 0 : switch (prot) {
283 : case PROT_EXEC: {
284 0 : M(PAGE_EXECUTE);
285 0 : break;
286 : }
287 : case PROT_EXEC | PROT_READ: {
288 0 : M(PAGE_EXECUTE_READ);
289 0 : break;
290 : }
291 : case PROT_EXEC | PROT_READ | PROT_WRITE: {
292 0 : M(PAGE_EXECUTE_READWRITE);
293 0 : break;
294 : }
295 : case PROT_READ: {
296 0 : M(PAGE_READONLY);
297 0 : break;
298 : }
299 : case PROT_READ | PROT_WRITE: {
300 0 : M(PAGE_READWRITE);
301 0 : break;
302 : }
303 : case PROT_NONE: {
304 0 : M(PAGE_NOACCESS);
305 0 : break;
306 : }
307 : default: {
308 0 : NaClLog(2, "NaClMprotect: invalid protection mode\n");
309 0 : return -EINVAL;
310 : }
311 : }
312 : #undef M
313 : /*
314 : * VirtualProtect region cannot span allocations: all addresses from
315 : * [lpAddress, lpAddress+dwSize) must be in one region of memory
316 : * returned from VirtualAlloc or VirtualAllocEx
317 : */
318 : for (cur_addr = start_addr,
319 : cur_chunk_size = NaClProtectChunkSize(cur_addr, end_addr);
320 : cur_addr < end_addr;
321 : cur_addr += cur_chunk_size,
322 0 : cur_chunk_size = NaClProtectChunkSize(cur_addr, end_addr)) {
323 : NaClLog(7,
324 : "NaClMprotect: VirtualProtect(0x%016"NACL_PRIxPTR","
325 : " 0x%"NACL_PRIxPTR", 0x%x, *)\n",
326 0 : cur_addr, cur_chunk_size, newProtection);
327 :
328 0 : if (newProtection != PAGE_NOACCESS) {
329 : res = VirtualProtect((void *) cur_addr,
330 : cur_chunk_size,
331 : newProtection,
332 0 : &oldProtection);
333 :
334 0 : if (!res && newProtection == PAGE_READWRITE) {
335 : NaClLog(4,
336 : "NaClMprotect: VirtualProtect(0x%016"NACL_PRIxPTR","
337 : " 0x%"NACL_PRIxPTR", 0x%x, *) failed,"
338 : " trying CoW fallback\n",
339 0 : cur_addr, cur_chunk_size, newProtection);
340 : res = VirtualProtect((void *) cur_addr,
341 : cur_chunk_size,
342 : PAGE_WRITECOPY,
343 0 : &oldProtection);
344 0 : if (!res) {
345 : NaClLog(4,
346 : "NaClMprotect: VirtualProtect with PAGE_WRITECOPY failed,"
347 0 : " trying VirtualAlloc\n");
348 : }
349 : }
350 :
351 0 : if (!res) {
352 : void *p;
353 : p = VirtualAlloc((void*) cur_addr,
354 : cur_chunk_size,
355 : MEM_COMMIT,
356 0 : newProtection);
357 0 : if (p != (void*) cur_addr) {
358 : NaClLog(2,
359 : "NaClMprotect: VirtualAlloc(0x%016"NACL_PRIxPTR","
360 : " 0x%"NACL_PRIxPTR", MEM_COMMIT, 0x%x) failed\n",
361 0 : cur_addr, cur_chunk_size, newProtection);
362 0 : return -NaClXlateSystemError(GetLastError());
363 : }
364 : }
365 0 : } else {
366 : /*
367 : * decommit the memory--this has the same effect as setting the protection
368 : * level to PAGE_NOACCESS, with the added benefit of not taking up any
369 : * swap space.
370 : */
371 0 : if (!VirtualFree((void *) cur_addr, cur_chunk_size, MEM_DECOMMIT)) {
372 0 : xlated_res = NaClXlateSystemError(GetLastError());
373 0 : NaClLog(2, "NaClMprotect: VirtualFree failed: 0x%x\n", xlated_res);
374 0 : return -xlated_res;
375 : }
376 : }
377 0 : }
378 0 : NaClLog(2, "NaClMprotect: done\n");
379 0 : return 0;
380 0 : }
381 :
382 0 : int NaClMadvise(void *start, size_t length, int advice) {
383 : int err;
384 : uintptr_t start_addr;
385 : uintptr_t end_addr;
386 : uintptr_t cur_addr;
387 : uintptr_t cur_chunk_size;
388 :
389 : /*
390 : * MADV_DONTNEED and MADV_NORMAL are needed
391 : */
392 : NaClLog(5, "NaClMadvise(0x%016"NACL_PRIxPTR", 0x%"NACL_PRIxS", 0x%x)\n",
393 0 : (uintptr_t) start, length, advice);
394 0 : switch (advice) {
395 : case MADV_DONTNEED:
396 0 : start_addr = (uintptr_t) start;
397 0 : if (!NaClIsPageMultiple(start_addr)) {
398 : NaClLog(2,
399 0 : "NaClMadvise: start address not a multiple of page size\n");
400 0 : return -EINVAL;
401 : }
402 0 : if (!NaClIsPageMultiple(length)) {
403 0 : NaClLog(2, "NaClMadvise: length not a multiple of page size\n");
404 0 : return -EINVAL;
405 : }
406 0 : end_addr = start_addr + length;
407 : for (cur_addr = start_addr,
408 : cur_chunk_size = NaClProtectChunkSize(cur_addr, end_addr);
409 : cur_addr < end_addr;
410 : cur_addr += cur_chunk_size,
411 0 : cur_chunk_size = NaClProtectChunkSize(cur_addr, end_addr)) {
412 : NaClLog(7,
413 : ("NaClMadvise: MADV_DONTNEED"
414 : " -> VirtualAlloc(0x%016"NACL_PRIxPTR","
415 : " 0x%"NACL_PRIxPTR", MEM_RESET, PAGE_NOACCESS)\n"),
416 0 : cur_addr, cur_chunk_size);
417 :
418 : /*
419 : * Decommit (but do not release) the page. This allows the kernel to
420 : * release backing store, but does not release the VA space. Should be
421 : * fairly close to the behavior we'd get from the Linux madvise()
422 : * function.
423 : */
424 : if (NULL == VirtualAlloc((void *) cur_addr,
425 0 : cur_chunk_size, MEM_RESET, PAGE_READONLY)) {
426 0 : err = NaClXlateSystemError(GetLastError());
427 0 : NaClLog(2, "NaClMadvise: VirtualFree failed: 0x%x\n", err);
428 0 : return -err;
429 : }
430 0 : }
431 0 : break;
432 : case MADV_NORMAL:
433 0 : memset(start, 0, length);
434 0 : break;
435 : default:
436 0 : return -EINVAL;
437 : }
438 0 : NaClLog(5, "NaClMadvise: done\n");
439 0 : return 0;
440 0 : }
|