1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=8 sts=4 et sw=4 tw=99:
3  * This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "jit/ProcessExecutableMemory.h"
8 
9 #include "mozilla/Array.h"
10 #include "mozilla/Atomics.h"
11 #include "mozilla/DebugOnly.h"
12 #include "mozilla/Maybe.h"
13 #include "mozilla/TaggedAnonymousMemory.h"
14 #include "mozilla/XorShift128PlusRNG.h"
15 
16 #include "jsfriendapi.h"
17 #include "jslock.h"
18 #include "jsmath.h"
19 #include "jsutil.h"
20 #include "jswin.h"
21 
22 #include <errno.h>
23 
24 #include "gc/Memory.h"
25 
26 #ifdef XP_WIN
27 # include "mozilla/WindowsVersion.h"
28 #else
29 # include <sys/mman.h>
30 # include <unistd.h>
31 #endif
32 
33 using namespace js;
34 using namespace js::jit;
35 
36 #ifdef XP_WIN
37 static void*
ComputeRandomAllocationAddress()38 ComputeRandomAllocationAddress()
39 {
40     /*
41      * Inspiration is V8's OS::Allocate in platform-win32.cc.
42      *
43      * VirtualAlloc takes 64K chunks out of the virtual address space, so we
44      * keep 16b alignment.
45      *
46      * x86: V8 comments say that keeping addresses in the [64MiB, 1GiB) range
47      * tries to avoid system default DLL mapping space. In the end, we get 13
48      * bits of randomness in our selection.
49      * x64: [2GiB, 4TiB), with 25 bits of randomness.
50      */
51 # ifdef HAVE_64BIT_BUILD
52     static const uintptr_t base = 0x0000000080000000;
53     static const uintptr_t mask = 0x000003ffffff0000;
54 # elif defined(_M_IX86) || defined(__i386__)
55     static const uintptr_t base = 0x04000000;
56     static const uintptr_t mask = 0x3fff0000;
57 # else
58 #  error "Unsupported architecture"
59 # endif
60 
61     uint64_t rand = js::GenerateRandomSeed();
62     return (void*) (base | (rand & mask));
63 }
64 
65 # ifdef HAVE_64BIT_BUILD
66 static js::JitExceptionHandler sJitExceptionHandler;
67 
JS_FRIEND_API(void)68 JS_FRIEND_API(void)
69 js::SetJitExceptionHandler(JitExceptionHandler handler)
70 {
71     MOZ_ASSERT(!sJitExceptionHandler);
72     sJitExceptionHandler = handler;
73 }
74 
75 // From documentation for UNWIND_INFO on
76 // http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx
77 struct UnwindInfo
78 {
79     uint8_t version : 3;
80     uint8_t flags : 5;
81     uint8_t sizeOfPrologue;
82     uint8_t countOfUnwindCodes;
83     uint8_t frameRegister : 4;
84     uint8_t frameOffset : 4;
85     ULONG exceptionHandler;
86 };
87 
88 static const unsigned ThunkLength = 12;
89 
90 struct ExceptionHandlerRecord
91 {
92     RUNTIME_FUNCTION runtimeFunction;
93     UnwindInfo unwindInfo;
94     uint8_t thunk[ThunkLength];
95 };
96 
97 // This function must match the function pointer type PEXCEPTION_HANDLER
98 // mentioned in:
99 //   http://msdn.microsoft.com/en-us/library/ssa62fwe.aspx.
100 // This type is rather elusive in documentation; Wine is the best I've found:
101 //   http://source.winehq.org/source/include/winnt.h
102 static DWORD
ExceptionHandler(PEXCEPTION_RECORD exceptionRecord,_EXCEPTION_REGISTRATION_RECORD *,PCONTEXT context,_EXCEPTION_REGISTRATION_RECORD **)103 ExceptionHandler(PEXCEPTION_RECORD exceptionRecord, _EXCEPTION_REGISTRATION_RECORD*,
104                  PCONTEXT context, _EXCEPTION_REGISTRATION_RECORD**)
105 {
106     return sJitExceptionHandler(exceptionRecord, context);
107 }
108 
109 // For an explanation of the problem being solved here, see
110 // SetJitExceptionFilter in jsfriendapi.h.
111 static bool
RegisterExecutableMemory(void * p,size_t bytes,size_t pageSize)112 RegisterExecutableMemory(void* p, size_t bytes, size_t pageSize)
113 {
114     if (!VirtualAlloc(p, pageSize, MEM_COMMIT, PAGE_READWRITE))
115         MOZ_CRASH();
116 
117     ExceptionHandlerRecord* r = reinterpret_cast<ExceptionHandlerRecord*>(p);
118 
119     // All these fields are specified to be offsets from the base of the
120     // executable code (which is 'p'), even if they have 'Address' in their
121     // names. In particular, exceptionHandler is a ULONG offset which is a
122     // 32-bit integer. Since 'p' can be farther than INT32_MAX away from
123     // sJitExceptionHandler, we must generate a little thunk inside the
124     // record. The record is put on its own page so that we can take away write
125     // access to protect against accidental clobbering.
126 
127     r->runtimeFunction.BeginAddress = pageSize;
128     r->runtimeFunction.EndAddress = (DWORD)bytes;
129     r->runtimeFunction.UnwindData = offsetof(ExceptionHandlerRecord, unwindInfo);
130 
131     r->unwindInfo.version = 1;
132     r->unwindInfo.flags = UNW_FLAG_EHANDLER;
133     r->unwindInfo.sizeOfPrologue = 0;
134     r->unwindInfo.countOfUnwindCodes = 0;
135     r->unwindInfo.frameRegister = 0;
136     r->unwindInfo.frameOffset = 0;
137     r->unwindInfo.exceptionHandler = offsetof(ExceptionHandlerRecord, thunk);
138 
139     // mov imm64, rax
140     r->thunk[0]  = 0x48;
141     r->thunk[1]  = 0xb8;
142     void* handler = JS_FUNC_TO_DATA_PTR(void*, ExceptionHandler);
143     memcpy(&r->thunk[2], &handler, 8);
144 
145     // jmp rax
146     r->thunk[10] = 0xff;
147     r->thunk[11] = 0xe0;
148 
149     DWORD oldProtect;
150     if (!VirtualProtect(p, pageSize, PAGE_EXECUTE_READ, &oldProtect))
151         MOZ_CRASH();
152 
153     bool success = RtlAddFunctionTable(&r->runtimeFunction, 1, reinterpret_cast<DWORD64>(p));
154 
155     return success;
156 }
157 
158 static void
UnregisterExecutableMemory(void * p,size_t bytes,size_t pageSize)159 UnregisterExecutableMemory(void* p, size_t bytes, size_t pageSize)
160 {
161     ExceptionHandlerRecord* r = reinterpret_cast<ExceptionHandlerRecord*>(p);
162 
163     RtlDeleteFunctionTable(&r->runtimeFunction);
164 }
165 # endif
166 
167 static void*
ReserveProcessExecutableMemory(size_t bytes)168 ReserveProcessExecutableMemory(size_t bytes)
169 {
170 # ifdef HAVE_64BIT_BUILD
171     size_t pageSize = gc::SystemPageSize();
172     if (sJitExceptionHandler)
173         bytes += pageSize;
174 # endif
175 
176     void* p = nullptr;
177     for (size_t i = 0; i < 10; i++) {
178         void* randomAddr = ComputeRandomAllocationAddress();
179         p = VirtualAlloc(randomAddr, bytes, MEM_RESERVE, PAGE_NOACCESS);
180         if (p)
181             break;
182     }
183 
184     if (!p) {
185         // Try again without randomization.
186         p = VirtualAlloc(nullptr, bytes, MEM_RESERVE, PAGE_NOACCESS);
187         if (!p)
188             return nullptr;
189     }
190 
191 # ifdef HAVE_64BIT_BUILD
192     if (sJitExceptionHandler) {
193         if (!RegisterExecutableMemory(p, bytes, pageSize)) {
194             VirtualFree(p, 0, MEM_RELEASE);
195             return nullptr;
196         }
197 
198         p = (uint8_t*)p + pageSize;
199     }
200 # endif
201 
202     return p;
203 }
204 
205 static void
DeallocateProcessExecutableMemory(void * addr,size_t bytes)206 DeallocateProcessExecutableMemory(void* addr, size_t bytes)
207 {
208 # ifdef HAVE_64BIT_BUILD
209     if (sJitExceptionHandler) {
210         size_t pageSize = gc::SystemPageSize();
211         addr = (uint8_t*)addr - pageSize;
212         UnregisterExecutableMemory(addr, bytes, pageSize);
213     }
214 # endif
215 
216     VirtualFree(addr, 0, MEM_RELEASE);
217 }
218 
219 static DWORD
ProtectionSettingToFlags(ProtectionSetting protection)220 ProtectionSettingToFlags(ProtectionSetting protection)
221 {
222     return PAGE_EXECUTE_READWRITE;
223 }
224 
225 static void
CommitPages(void * addr,size_t bytes,ProtectionSetting protection)226 CommitPages(void* addr, size_t bytes, ProtectionSetting protection)
227 {
228     if (!VirtualAlloc(addr, bytes, MEM_COMMIT, ProtectionSettingToFlags(protection)))
229         MOZ_CRASH("CommitPages failed");
230 }
231 
232 static void
DecommitPages(void * addr,size_t bytes)233 DecommitPages(void* addr, size_t bytes)
234 {
235     if (!VirtualFree(addr, bytes, MEM_DECOMMIT))
236         MOZ_CRASH("DecommitPages failed");
237 }
238 #else // !XP_WIN
239 static void*
ComputeRandomAllocationAddress()240 ComputeRandomAllocationAddress()
241 {
242     uint64_t rand = js::GenerateRandomSeed();
243 
244 # ifdef HAVE_64BIT_BUILD
245     // x64 CPUs have a 48-bit address space and on some platforms the OS will
246     // give us access to 47 bits, so to be safe we right shift by 18 to leave
247     // 46 bits.
248     rand >>= 18;
249 # else
250     // On 32-bit, right shift by 34 to leave 30 bits, range [0, 1GiB). Then add
251     // 512MiB to get range [512MiB, 1.5GiB), or [0x20000000, 0x60000000). This
252     // is based on V8 comments in platform-posix.cc saying this range is
253     // relatively unpopulated across a variety of kernels.
254     rand >>= 34;
255     rand += 512 * 1024 * 1024;
256 # endif
257 
258     // Ensure page alignment.
259     uintptr_t mask = ~uintptr_t(gc::SystemPageSize() - 1);
260     return (void*) uintptr_t(rand & mask);
261 }
262 
263 static void*
ReserveProcessExecutableMemory(size_t bytes)264 ReserveProcessExecutableMemory(size_t bytes)
265 {
266     // Note that randomAddr is just a hint: if the address is not available
267     // mmap will pick a different address.
268     void* randomAddr = ComputeRandomAllocationAddress();
269 #ifdef SOLARIS
270     // Do not reserve swap space for the entire allocation. Regardless of what mmap(2) says,
271     // Solaris reserves swap space even though this allocation is not marked as writable.
272     void* p = MozTaggedAnonymousMmap(randomAddr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE,
273                                      -1, 0, "js-executable-memory");
274 #else
275     void* p = MozTaggedAnonymousMmap(randomAddr, bytes, PROT_NONE, MAP_PRIVATE | MAP_ANON,
276                                      -1, 0, "js-executable-memory");
277 #endif
278     if (p == MAP_FAILED)
279         return nullptr;
280     return p;
281 }
282 
283 static void
DeallocateProcessExecutableMemory(void * addr,size_t bytes)284 DeallocateProcessExecutableMemory(void* addr, size_t bytes)
285 {
286     mozilla::DebugOnly<int> result = munmap(addr, bytes);
287     MOZ_ASSERT(!result || errno == ENOMEM);
288 }
289 
290 static unsigned
ProtectionSettingToFlags(ProtectionSetting protection)291 ProtectionSettingToFlags(ProtectionSetting protection)
292 {
293     return PROT_READ | PROT_WRITE | PROT_EXEC;
294 }
295 
296 static void
CommitPages(void * addr,size_t bytes,ProtectionSetting protection)297 CommitPages(void* addr, size_t bytes, ProtectionSetting protection)
298 {
299     void* p = MozTaggedAnonymousMmap(addr, bytes, ProtectionSettingToFlags(protection),
300                                      MAP_FIXED | MAP_PRIVATE | MAP_ANON,
301                                      -1, 0, "js-executable-memory");
302     MOZ_RELEASE_ASSERT(addr == p);
303 }
304 
305 static void
DecommitPages(void * addr,size_t bytes)306 DecommitPages(void* addr, size_t bytes)
307 {
308     // Use mmap with MAP_FIXED and PROT_NONE. Inspired by jemalloc's
309     // pages_decommit.
310     void* p = MozTaggedAnonymousMmap(addr, bytes, PROT_NONE,
311                                      MAP_FIXED | MAP_PRIVATE | MAP_ANON,
312                                      -1, 0, "js-executable-memory");
313     MOZ_RELEASE_ASSERT(addr == p);
314 }
315 #endif
316 
317 template <size_t NumBits>
318 class PageBitSet
319 {
320     using WordType = uint32_t;
321     static const size_t BitsPerWord = sizeof(WordType) * 8;
322 
323     static_assert((NumBits % BitsPerWord) == 0,
324                   "NumBits must be a multiple of BitsPerWord");
325     static const size_t NumWords = NumBits / BitsPerWord;
326 
327     mozilla::Array<WordType, NumWords> words_;
328 
indexToWord(uint32_t index) const329     uint32_t indexToWord(uint32_t index) const {
330         MOZ_ASSERT(index < NumBits);
331         return index / BitsPerWord;
332     }
indexToBit(uint32_t index) const333     WordType indexToBit(uint32_t index) const {
334         MOZ_ASSERT(index < NumBits);
335         return WordType(1) << (index % BitsPerWord);
336     }
337 
338   public:
init()339     void init() {
340         mozilla::PodArrayZero(words_);
341     }
contains(size_t index) const342     bool contains(size_t index) const {
343         uint32_t word = indexToWord(index);
344         return words_[word] & indexToBit(index);
345     }
insert(size_t index)346     void insert(size_t index) {
347         MOZ_ASSERT(!contains(index));
348         uint32_t word = indexToWord(index);
349         words_[word] |= indexToBit(index);
350     }
remove(size_t index)351     void remove(size_t index) {
352         MOZ_ASSERT(contains(index));
353         uint32_t word = indexToWord(index);
354         words_[word] &= ~indexToBit(index);
355     }
356 
357 #ifdef DEBUG
empty() const358     bool empty() const {
359         for (size_t i = 0; i < NumWords; i++) {
360             if (words_[i] != 0)
361                 return false;
362         }
363         return true;
364     }
365 #endif
366 };
367 
368 // Limit on the number of bytes of executable memory to prevent JIT spraying
369 // attacks.
370 #if JS_BITS_PER_WORD == 32
371 static const size_t MaxCodeBytesPerProcess = 128 * 1024 * 1024;
372 #else
373 static const size_t MaxCodeBytesPerProcess = 640 * 1024 * 1024;
374 #endif
375 
376 // Per-process executable memory allocator. It reserves a block of memory of
377 // MaxCodeBytesPerProcess bytes, then allocates/deallocates pages from that.
378 //
379 // This has a number of benefits compared to raw mmap/VirtualAlloc:
380 //
381 // * More resillient against certain attacks.
382 //
383 // * Behaves more consistently across platforms: it avoids the 64K granularity
384 //   issues on Windows, for instance.
385 //
386 // * On x64, near jumps can be used for jumps to other JIT pages.
387 //
388 // * On Win64, we have to register the exception handler only once (at process
389 //   startup). This saves some memory and avoids RtlAddFunctionTable profiler
390 //   deadlocks.
391 class ProcessExecutableMemory
392 {
393     static_assert((MaxCodeBytesPerProcess % ExecutableCodePageSize) == 0,
394                   "MaxCodeBytesPerProcess must be a multiple of ExecutableCodePageSize");
395     static const size_t MaxCodePages = MaxCodeBytesPerProcess / ExecutableCodePageSize;
396 
397     // Start of the MaxCodeBytesPerProcess memory block or nullptr if
398     // uninitialized. Note that this is NOT guaranteed to be aligned to
399     // ExecutableCodePageSize.
400     uint8_t* base_;
401 
402     // The fields below should only be accessed while we hold the lock.
403     PRLock* lock_;
404 
405     // pagesAllocated_ is an Atomic so that bytesAllocated does not have to
406     // take the lock.
407     mozilla::Atomic<size_t, mozilla::ReleaseAcquire> pagesAllocated_;
408 
409     // Page where we should try to allocate next.
410     size_t cursor_;
411 
412     mozilla::Maybe<mozilla::non_crypto::XorShift128PlusRNG> rng_;
413     PageBitSet<MaxCodePages> pages_;
414 
415   public:
ProcessExecutableMemory()416     ProcessExecutableMemory()
417       : base_(nullptr),
418         lock_(nullptr),
419         pagesAllocated_(0),
420         cursor_(0),
421         rng_(),
422         pages_()
423     {}
424 
init()425     MOZ_MUST_USE bool init() {
426         pages_.init();
427 
428         MOZ_RELEASE_ASSERT(!initialized());
429         MOZ_RELEASE_ASSERT(gc::SystemPageSize() <= ExecutableCodePageSize);
430 
431         lock_ = PR_NewLock();
432         if (!lock_)
433             return false;
434 
435         void* p = ReserveProcessExecutableMemory(MaxCodeBytesPerProcess);
436         if (!p)
437             return false;
438 
439         base_ = static_cast<uint8_t*>(p);
440 
441         mozilla::Array<uint64_t, 2> seed;
442         GenerateXorShift128PlusSeed(seed);
443         rng_.emplace(seed[0], seed[1]);
444         return true;
445     }
446 
initialized() const447     bool initialized() const {
448         return base_ != nullptr;
449     }
450 
bytesAllocated() const451     size_t bytesAllocated() const {
452         MOZ_ASSERT(pagesAllocated_ <= MaxCodePages);
453         return pagesAllocated_ * ExecutableCodePageSize;
454     }
455 
release()456     void release() {
457         MOZ_ASSERT(initialized());
458         MOZ_ASSERT(pages_.empty());
459         MOZ_ASSERT(pagesAllocated_ == 0);
460         DeallocateProcessExecutableMemory(base_, MaxCodeBytesPerProcess);
461         base_ = nullptr;
462         rng_.reset();
463         MOZ_ASSERT(lock_);
464         PR_DestroyLock(lock_);
465         lock_ = nullptr;
466         MOZ_ASSERT(!initialized());
467     }
468 
assertValidAddress(void * p,size_t bytes) const469     void assertValidAddress(void* p, size_t bytes) const {
470         MOZ_RELEASE_ASSERT(p >= base_ &&
471                            uintptr_t(p) + bytes <= uintptr_t(base_) + MaxCodeBytesPerProcess);
472     }
473 
474     void* allocate(size_t bytes, ProtectionSetting protection);
475     void deallocate(void* addr, size_t bytes);
476 };
477 
478 void*
allocate(size_t bytes,ProtectionSetting protection)479 ProcessExecutableMemory::allocate(size_t bytes, ProtectionSetting protection)
480 {
481     MOZ_ASSERT(initialized());
482     MOZ_ASSERT(bytes > 0);
483     MOZ_ASSERT((bytes % ExecutableCodePageSize) == 0);
484 
485     size_t numPages = bytes / ExecutableCodePageSize;
486 
487     // Take the lock and try to allocate.
488     void* p = nullptr;
489     {
490         PR_Lock(lock_);
491         MOZ_ASSERT(pagesAllocated_ <= MaxCodePages);
492 
493         // Check if we have enough pages available.
494         if (pagesAllocated_ + numPages >= MaxCodePages) {
495             PR_Unlock(lock_);
496             return nullptr;
497         }
498 
499         MOZ_ASSERT(bytes <= MaxCodeBytesPerProcess);
500 
501         // Maybe skip a page to make allocations less predictable.
502         size_t page = cursor_ + (rng_.ref().next() % 2);
503 
504         for (size_t i = 0; i < MaxCodePages; i++) {
505             // Make sure page + numPages - 1 is a valid index.
506             if (page + numPages > MaxCodePages)
507                 page = 0;
508 
509             bool available = true;
510             for (size_t j = 0; j < numPages; j++) {
511                 if (pages_.contains(page + j)) {
512                     available = false;
513                     break;
514                 }
515             }
516             if (!available) {
517                 page++;
518                 continue;
519             }
520 
521             // Mark the pages as unavailable.
522             for (size_t j = 0; j < numPages; j++)
523                 pages_.insert(page + j);
524 
525             pagesAllocated_ += numPages;
526             MOZ_ASSERT(pagesAllocated_ <= MaxCodePages);
527 
528             // If we allocated a small number of pages, move cursor_ to the
529             // next page. We don't do this for larger allocations to avoid
530             // skipping a large number of small holes.
531             if (numPages <= 2)
532                 cursor_ = page + numPages;
533 
534             p = base_ + page * ExecutableCodePageSize;
535             break;
536         }
537         PR_Unlock(lock_);
538         if (!p)
539             return nullptr;
540     }
541 
542     // Commit the pages after releasing the lock.
543     CommitPages(p, bytes, protection);
544     return p;
545 }
546 
547 void
deallocate(void * addr,size_t bytes)548 ProcessExecutableMemory::deallocate(void* addr, size_t bytes)
549 {
550     MOZ_ASSERT(initialized());
551     MOZ_ASSERT(addr);
552     MOZ_ASSERT((uintptr_t(addr) % gc::SystemPageSize()) == 0);
553     MOZ_ASSERT(bytes > 0);
554     MOZ_ASSERT((bytes % ExecutableCodePageSize) == 0);
555 
556     assertValidAddress(addr, bytes);
557 
558     size_t firstPage = (static_cast<uint8_t*>(addr) - base_) / ExecutableCodePageSize;
559     size_t numPages = bytes / ExecutableCodePageSize;
560 
561     // Decommit before taking the lock.
562     DecommitPages(addr, bytes);
563 
564     PR_Lock(lock_);
565     MOZ_ASSERT(numPages <= pagesAllocated_);
566     pagesAllocated_ -= numPages;
567 
568     for (size_t i = 0; i < numPages; i++)
569         pages_.remove(firstPage + i);
570 
571     // Move the cursor back so we can reuse pages instead of fragmenting the
572     // whole region.
573     if (firstPage < cursor_)
574         cursor_ = firstPage;
575 
576     PR_Unlock(lock_);
577 }
578 
579 static ProcessExecutableMemory execMemory;
580 
581 void*
AllocateExecutableMemory(size_t bytes,ProtectionSetting protection)582 js::jit::AllocateExecutableMemory(size_t bytes, ProtectionSetting protection)
583 {
584     return execMemory.allocate(bytes, protection);
585 }
586 
587 void
DeallocateExecutableMemory(void * addr,size_t bytes)588 js::jit::DeallocateExecutableMemory(void* addr, size_t bytes)
589 {
590     execMemory.deallocate(addr, bytes);
591 }
592 
593 bool
InitProcessExecutableMemory()594 js::jit::InitProcessExecutableMemory()
595 {
596     return execMemory.init();
597 }
598 
599 void
ReleaseProcessExecutableMemory()600 js::jit::ReleaseProcessExecutableMemory()
601 {
602     execMemory.release();
603 }
604 
605 bool
CanLikelyAllocateMoreExecutableMemory()606 js::jit::CanLikelyAllocateMoreExecutableMemory()
607 {
608     // Use a 16 MB buffer.
609     static const size_t BufferSize = 16 * 1024 * 1024;
610 
611     MOZ_ASSERT(execMemory.bytesAllocated() <= MaxCodeBytesPerProcess);
612 
613     return execMemory.bytesAllocated() + BufferSize <= MaxCodeBytesPerProcess;
614 }
615