Bug 1329499 - Part 1: Detect the jemalloc freed memory poison pattern during append calls. r=jandem
authorEmanuel Hoogeveen <emanuel.hoogeveen@gmail.com>
Mon, 09 Jan 2017 08:28:00 -0500
changeset 457727 e9e9e07a5afbae19a8470f2a8dacacfc4a2184fd
parent 457726 1940969b24fbe8186b4aa8f628fa2d9f1eddf110
child 457728 c89eb360f419e9e688aa08604e7a2c088aae62b3
push id40874
push userbmo:kmckinley@mozilla.com
push dateMon, 09 Jan 2017 19:01:26 +0000
reviewersjandem
bugs1329499
milestone53.0a1
Bug 1329499 - Part 1: Detect the jemalloc freed memory poison pattern during append calls. r=jandem
js/src/ds/PageProtectingVector.h
js/src/jit/x86-shared/AssemblerBuffer-x86-shared.h
--- a/js/src/ds/PageProtectingVector.h
+++ b/js/src/ds/PageProtectingVector.h
@@ -30,17 +30,19 @@ namespace js {
  * up to two pages of memory may not be protected.
  */
 template<typename T,
          size_t MinInlineCapacity = 0,
          class AllocPolicy = mozilla::MallocAllocPolicy,
          bool ProtectUsed = true,
          bool ProtectUnused = true,
          bool GuardAgainstReentrancy = true,
-         size_t InitialLowerBound = 0>
+         bool DetectPoison = false,
+         size_t InitialLowerBound = 0,
+         uint8_t PoisonPattern = 0xe5>
 class PageProtectingVector final
 {
     mozilla::Vector<T, MinInlineCapacity, AllocPolicy> vector;
 
     static constexpr size_t toShift(size_t v) { return v <= 1 ? 0 : 1 + toShift(v >> 1); }
 
     static_assert((sizeof(T) & (sizeof(T) - 1)) == 0, "For performance reasons, "
                   "PageProtectingVector only works with power-of-2 sized elements!");
@@ -82,28 +84,36 @@ class PageProtectingVector final
 
     /*
      * The size in elems that a buffer needs to be before its pages will be
      * protected. This is intended to reduce churn for small vectors while
      * still offering protection when they grow large enough.
      */
     size_t lowerBound;
 
+    /*
+     * The number of subsequent bytes containing the poison pattern detected
+     * thus far. This detection may span several append calls.
+     */
+    size_t poisonBytes;
+
 #ifdef DEBUG
     bool regionUnprotected;
 #endif
 
     bool usable;
     bool enabled;
     bool protectUsedEnabled;
     bool protectUnusedEnabled;
 
     bool reentrancyGuardEnabled;
     mutable mozilla::Atomic<bool, mozilla::ReleaseAcquire> reentrancyGuard;
 
+    bool detectPoisonEnabled;
+
     MOZ_ALWAYS_INLINE void resetTest() {
         MOZ_ASSERT(protectUsedEnabled || protectUnusedEnabled);
         size_t nextPage = (pageSize - (uintptr_t(begin() + length()) & pageMask)) >> elemShift;
         size_t nextResize = capacity() - length();
         if (MOZ_LIKELY(nextPage <= nextResize))
             elemsUntilTest = intptr_t(nextPage);
         else
             elemsUntilTest = intptr_t(nextResize);
@@ -119,18 +129,18 @@ class PageProtectingVector final
     MOZ_ALWAYS_INLINE void resetForNewBuffer() {
         initPage = (uintptr_t(begin() - 1) >> pageShift) + 1;
         currPage = (uintptr_t(begin() + length()) >> pageShift);
         lastPage = (uintptr_t(begin() + capacity()) >> pageShift) - 1;
         protectUsedEnabled = ProtectUsed && usable && enabled && initPage <= lastPage &&
                              (uintptr_t(begin()) & elemMask) == 0 && capacity() >= lowerBound;
         protectUnusedEnabled = ProtectUnused && usable && enabled && initPage <= lastPage &&
                                (uintptr_t(begin()) & elemMask) == 0 && capacity() >= lowerBound;
-        reentrancyGuardEnabled = GuardAgainstReentrancy && enabled && initPage <= lastPage &&
-                                 capacity() >= lowerBound;
+        reentrancyGuardEnabled = GuardAgainstReentrancy && enabled && capacity() >= lowerBound;
+        detectPoisonEnabled = DetectPoison && enabled && capacity() >= lowerBound;
         setTestInitial();
     }
 
     MOZ_ALWAYS_INLINE void addExceptionHandler() {
         if (MOZ_UNLIKELY(protectUsedEnabled || protectUnusedEnabled))
             MemoryProtectionExceptionHandler::addRegion(begin(), capacity() << elemShift);
     }
 
@@ -309,36 +319,55 @@ class PageProtectingVector final
             vector.lock();
         }
 
         MOZ_ALWAYS_INLINE ~AutoGuardAgainstReentrancy() {
             vector.unlock();
         }
     };
 
+    template<typename U>
+    MOZ_ALWAYS_INLINE void checkForPoison(const U* values, size_t size) {
+        if (MOZ_LIKELY(!DetectPoison || !detectPoisonEnabled))
+            return;
+        const uint8_t* addr = reinterpret_cast<const uint8_t*>(values);
+        size_t bytes = size * sizeof(U);
+        for (size_t i = 0; i < bytes; ++i) {
+            if (MOZ_LIKELY(addr[i] != PoisonPattern)) {
+                poisonBytes = 0;
+            } else {
+                ++poisonBytes;
+                if (MOZ_UNLIKELY(poisonBytes >= 16))
+                    MOZ_CRASH("Caller is writing the poison pattern into this buffer!");
+            }
+        }
+    }
+
     MOZ_ALWAYS_INLINE T* begin() { return vector.begin(); }
     MOZ_ALWAYS_INLINE const T* begin() const { return vector.begin(); }
 
   public:
     explicit PageProtectingVector(AllocPolicy policy = AllocPolicy())
       : vector(policy),
         elemsUntilTest(0),
         currPage(0),
         initPage(0),
         lastPage(0),
         lowerBound(InitialLowerBound),
+        poisonBytes(0),
 #ifdef DEBUG
         regionUnprotected(false),
 #endif
         usable(true),
         enabled(true),
         protectUsedEnabled(false),
         protectUnusedEnabled(false),
         reentrancyGuardEnabled(false),
-        reentrancyGuard(false)
+        reentrancyGuard(false),
+        detectPoisonEnabled(false)
     {
         if (gc::SystemPageSize() != pageSize)
             usable = false;
         protectNewBuffer();
     }
 
     ~PageProtectingVector() { unprotectOldBuffer(); }
 
@@ -422,89 +451,91 @@ class PageProtectingVector final
         if (MOZ_LIKELY(size <= capacity()))
             return vector.reserve(size);
         return reserveSlow(size);
     }
 
     template<typename U>
     MOZ_ALWAYS_INLINE void infallibleAppend(const U* values, size_t size) {
         AutoGuardAgainstReentrancy guard(*this);
+        checkForPoison(values, size);
         elemsUntilTest -= size;
         if (MOZ_LIKELY(elemsUntilTest >= 0))
             return vector.infallibleAppend(values, size);
         infallibleAppendSlow(values, size);
     }
 
     template<typename U>
     MOZ_ALWAYS_INLINE MOZ_MUST_USE bool append(const U* values, size_t size) {
         AutoGuardAgainstReentrancy guard(*this);
+        checkForPoison(values, size);
         elemsUntilTest -= size;
         if (MOZ_LIKELY(elemsUntilTest >= 0))
             return vector.append(values, size);
         return appendSlow(values, size);
     }
 };
 
-template<typename T, size_t N, class AP, bool P, bool Q, bool G, size_t I>
+template<typename T, size_t N, class A, bool P, bool Q, bool G, bool D, size_t I, uint8_t X>
 MOZ_NEVER_INLINE void
-PageProtectingVector<T, N, AP, P, Q, G, I>::unprotectRegionSlow(uintptr_t l, uintptr_t r)
+PageProtectingVector<T, N, A, P, Q, G, D, I, X>::unprotectRegionSlow(uintptr_t l, uintptr_t r)
 {
     if (l < initPage)
         l = initPage;
     if (r >= currPage)
         r = currPage - 1;
     T* addr = reinterpret_cast<T*>(l << pageShift);
     size_t size = (r - l + 1) << pageShift;
     gc::UnprotectPages(addr, size);
 }
 
-template<typename T, size_t N, class AP, bool P, bool Q, bool G, size_t I>
+template<typename T, size_t N, class A, bool P, bool Q, bool G, bool D, size_t I, uint8_t X>
 MOZ_NEVER_INLINE void
-PageProtectingVector<T, N, AP, P, Q, G, I>::reprotectRegionSlow(uintptr_t l, uintptr_t r)
+PageProtectingVector<T, N, A, P, Q, G, D, I, X>::reprotectRegionSlow(uintptr_t l, uintptr_t r)
 {
     if (l < initPage)
         l = initPage;
     if (r >= currPage)
         r = currPage - 1;
     T* addr = reinterpret_cast<T*>(l << pageShift);
     size_t size = (r - l + 1) << pageShift;
     gc::MakePagesReadOnly(addr, size);
 }
 
-template<typename T, size_t N, class AP, bool P, bool Q, bool G, size_t I>
+template<typename T, size_t N, class A, bool P, bool Q, bool G, bool D, size_t I, uint8_t X>
 MOZ_NEVER_INLINE MOZ_MUST_USE bool
-PageProtectingVector<T, N, AP, P, Q, G, I>::reserveSlow(size_t size)
+PageProtectingVector<T, N, A, P, Q, G, D, I, X>::reserveSlow(size_t size)
 {
     return reserveNewBuffer(size);
 }
 
-template<typename T, size_t N, class AP, bool P, bool Q, bool G, size_t I>
+template<typename T, size_t N, class A, bool P, bool Q, bool G, bool D, size_t I, uint8_t X>
 template<typename U>
 MOZ_NEVER_INLINE void
-PageProtectingVector<T, N, AP, P, Q, G, I>::infallibleAppendSlow(const U* values, size_t size)
+PageProtectingVector<T, N, A, P, Q, G, D, I, X>::infallibleAppendSlow(const U* values, size_t size)
 {
     // Ensure that we're here because we reached a page
     // boundary and not because of a buffer overflow.
     MOZ_RELEASE_ASSERT(MOZ_LIKELY(length() + size <= capacity()),
                        "About to overflow our AssemblerBuffer using infallibleAppend!");
     infallibleAppendNewPage(values, size);
 }
 
-template<typename T, size_t N, class AP, bool P, bool Q, bool G, size_t I>
+template<typename T, size_t N, class A, bool P, bool Q, bool G, bool D, size_t I, uint8_t X>
 template<typename U>
 MOZ_NEVER_INLINE MOZ_MUST_USE bool
-PageProtectingVector<T, N, AP, P, Q, G, I>::appendSlow(const U* values, size_t size)
+PageProtectingVector<T, N, A, P, Q, G, D, I, X>::appendSlow(const U* values, size_t size)
 {
     if (MOZ_LIKELY(length() + size <= capacity()))
         return appendNewPage(values, size);
     return appendNewBuffer(values, size);
 }
 
-template<typename T, size_t N, class AP, bool P, bool Q, bool G, size_t I>
+template<typename T, size_t N, class A, bool P, bool Q, bool G, bool D, size_t I, uint8_t X>
 MOZ_NEVER_INLINE void
-PageProtectingVector<T, N, AP, P, Q, G, I>::lockSlow() const
+PageProtectingVector<T, N, A, P, Q, G, D, I, X>::lockSlow() const
 {
     MOZ_CRASH("Cannot access PageProtectingVector from more than one thread at a time!");
 }
 
 } /* namespace js */
 
 #endif /* ds_PageProtectingVector_h */
--- a/js/src/jit/x86-shared/AssemblerBuffer-x86-shared.h
+++ b/js/src/jit/x86-shared/AssemblerBuffer-x86-shared.h
@@ -183,17 +183,17 @@ namespace jit {
         void oomDetected() {
             m_oom = true;
             m_buffer.clear();
         }
 
 #ifndef RELEASE_OR_BETA
         PageProtectingVector<unsigned char, 256, SystemAllocPolicy,
                              /* ProtectUsed = */ false, /* ProtectUnused = */ false,
-                             /* GuardAgainstReentrancy = */ true,
+                             /* GuardAgainstReentrancy = */ true, /* DetectPoison = */ true,
                              /* InitialLowerBound = */ 32 * 1024> m_buffer;
 #else
         mozilla::Vector<unsigned char, 256, SystemAllocPolicy> m_buffer;
 #endif
         bool m_oom;
     };
 
     class GenericAssembler