Title: [179753] trunk/Source/_javascript_Core
Revision
179753
Author
[email protected]
Date
2015-02-06 12:57:45 -0800 (Fri, 06 Feb 2015)

Log Message

MachineThreads should be ref counted.
<https://webkit.org/b/141317>

Reviewed by Filip Pizlo.

The VM's MachineThreads registry object is being referenced from other
threads as a raw pointer.  In a scenario where the VM is destructed on
the main thread, there is no guarantee that another thread isn't still
holding a reference to the registry and will eventually invoke
removeThread() on it on thread exit.  Hence, there's a possible use
after free scenario here.

The fix is to make MachineThreads ThreadSafeRefCounted, and have all
threads that references keep a RefPtr to it to ensure that it stays
alive until the very last thread is done with it.

* API/tests/testapi.mm:
(useVMFromOtherThread): - Renamed to be more descriptive.
(useVMFromOtherThreadAndOutliveVM):
- Added a test that has another thread which uses the VM outlive the
  VM to confirm that there is no crash.

  However, I was not actually able to get the VM to crash without this
  patch because I wasn't always able to the thread destructor to be
  called.  With this patch applied, I did verify with some logging that
  the MachineThreads registry is only destructed after all threads
  have removed themselves from it.

(threadMain): Deleted.

* heap/Heap.cpp:
(JSC::Heap::Heap):
(JSC::Heap::~Heap):
(JSC::Heap::gatherStackRoots):
* heap/Heap.h:
(JSC::Heap::machineThreads):
* heap/MachineStackMarker.cpp:
(JSC::MachineThreads::Thread::Thread):
(JSC::MachineThreads::addCurrentThread):
(JSC::MachineThreads::removeCurrentThread):
* heap/MachineStackMarker.h:

Modified Paths

Diff

Modified: trunk/Source/_javascript_Core/API/tests/testapi.mm (179752 => 179753)


--- trunk/Source/_javascript_Core/API/tests/testapi.mm	2015-02-06 20:46:19 UTC (rev 179752)
+++ trunk/Source/_javascript_Core/API/tests/testapi.mm	2015-02-06 20:57:45 UTC (rev 179753)
@@ -472,7 +472,7 @@
     return containsClass;
 }
 
-static void* threadMain(void* contextPtr)
+static void* useVMFromOtherThread(void* contextPtr)
 {
     JSContext *context = (__bridge JSContext*)contextPtr;
 
@@ -482,6 +482,32 @@
     pthread_exit(nullptr);
 }
 
+struct ThreadArgs {
+    JSContext* context;
+    volatile bool* mainThreadIsReadyToJoin;
+    volatile bool* otherThreadIsDoneWithJSWork;
+};
+
+static void* useVMFromOtherThreadAndOutliveVM(void* data)
+{
+    ThreadArgs* args = reinterpret_cast<ThreadArgs*>(data);
+    volatile bool& mainThreadIsReadyToJoin = *args->mainThreadIsReadyToJoin;
+    volatile bool& otherThreadIsDoneWithJSWork = *args->otherThreadIsDoneWithJSWork;
+
+    @autoreleasepool {
+        JSContext *context = args->context;
+
+        // Do something to enter the VM.
+        TestObject *testObject = [TestObject testObject];
+        context[@"testObject"] = testObject;
+    }
+    otherThreadIsDoneWithJSWork = true;
+
+    while (!mainThreadIsReadyToJoin)
+        usleep(10000);
+    pthread_exit(nullptr);
+}
+
 void testObjectiveCAPI()
 {
     NSLog(@"Testing Objective-C API");
@@ -1375,13 +1401,33 @@
         JSContext *context = [[JSContext alloc] init];
         
         pthread_t threadID;
-        pthread_create(&threadID, NULL, &threadMain, (__bridge void*)context);
+        pthread_create(&threadID, NULL, &useVMFromOtherThread, (__bridge void*)context);
         pthread_join(threadID, nullptr);
         JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
 
         checkResult(@"Did not crash after entering the VM from another thread", true);
     }
-    
+
+    @autoreleasepool {
+        pthread_t threadID;
+        volatile bool mainThreadIsReadyToJoin = false;
+        volatile bool otherThreadIsDoneWithJSWork = false;
+        @autoreleasepool {
+            JSContext *context = [[JSContext alloc] init];
+            ThreadArgs args = { context, &mainThreadIsReadyToJoin, &otherThreadIsDoneWithJSWork };
+
+            pthread_create(&threadID, NULL, &useVMFromOtherThreadAndOutliveVM, &args);
+            JSSynchronousGarbageCollectForDebugging([context JSGlobalContextRef]);
+
+            while (!otherThreadIsDoneWithJSWork)
+                usleep(10000);
+        }
+
+        mainThreadIsReadyToJoin = true;
+        pthread_join(threadID, nullptr);
+        checkResult(@"Did not crash if the VM is destructed before another thread using the VM ends", true);
+    }
+
     currentThisInsideBlockGetterTest();
     runDateTests();
     runJSExportTests();

Modified: trunk/Source/_javascript_Core/ChangeLog (179752 => 179753)


--- trunk/Source/_javascript_Core/ChangeLog	2015-02-06 20:46:19 UTC (rev 179752)
+++ trunk/Source/_javascript_Core/ChangeLog	2015-02-06 20:57:45 UTC (rev 179753)
@@ -1,3 +1,47 @@
+2015-02-06  Mark Lam  <[email protected]>
+
+        MachineThreads should be ref counted.
+        <https://webkit.org/b/141317>
+
+        Reviewed by Filip Pizlo.
+
+        The VM's MachineThreads registry object is being referenced from other
+        threads as a raw pointer.  In a scenario where the VM is destructed on
+        the main thread, there is no guarantee that another thread isn't still
+        holding a reference to the registry and will eventually invoke
+        removeThread() on it on thread exit.  Hence, there's a possible use
+        after free scenario here.
+
+        The fix is to make MachineThreads ThreadSafeRefCounted, and have all
+        threads that references keep a RefPtr to it to ensure that it stays
+        alive until the very last thread is done with it.
+
+        * API/tests/testapi.mm:
+        (useVMFromOtherThread): - Renamed to be more descriptive.
+        (useVMFromOtherThreadAndOutliveVM):
+        - Added a test that has another thread which uses the VM outlive the
+          VM to confirm that there is no crash.
+
+          However, I was not actually able to get the VM to crash without this
+          patch because I wasn't always able to the thread destructor to be
+          called.  With this patch applied, I did verify with some logging that
+          the MachineThreads registry is only destructed after all threads
+          have removed themselves from it.
+
+        (threadMain): Deleted.
+
+        * heap/Heap.cpp:
+        (JSC::Heap::Heap):
+        (JSC::Heap::~Heap):
+        (JSC::Heap::gatherStackRoots):
+        * heap/Heap.h:
+        (JSC::Heap::machineThreads):
+        * heap/MachineStackMarker.cpp:
+        (JSC::MachineThreads::Thread::Thread):
+        (JSC::MachineThreads::addCurrentThread):
+        (JSC::MachineThreads::removeCurrentThread):
+        * heap/MachineStackMarker.h:
+
 2015-02-06  Commit Queue  <[email protected]>
 
         Unreviewed, rolling out r179743.

Modified: trunk/Source/_javascript_Core/heap/Heap.cpp (179752 => 179753)


--- trunk/Source/_javascript_Core/heap/Heap.cpp	2015-02-06 20:46:19 UTC (rev 179752)
+++ trunk/Source/_javascript_Core/heap/Heap.cpp	2015-02-06 20:57:45 UTC (rev 179753)
@@ -311,7 +311,6 @@
     , m_objectSpace(this)
     , m_storageSpace(this)
     , m_extraMemoryUsage(0)
-    , m_machineThreads(this)
     , m_sharedData(vm)
     , m_slotVisitor(m_sharedData)
     , m_copyVisitor(m_sharedData)
@@ -340,6 +339,7 @@
     , m_delayedReleaseRecursionCount(0)
 #endif
 {
+    m_machineThreads = adoptRef(new MachineThreads(this));
     m_storageSpace.init();
     if (Options::verifyHeap())
         m_verifier = std::make_unique<HeapVerifier>(this, Options::numberOfGCCyclesToRecordForVerification());
@@ -347,6 +347,9 @@
 
 Heap::~Heap()
 {
+    // We need to remove the main thread explicitly here because the main thread
+    // may not terminate for a while though the Heap (and VM) is being shut down.
+    m_machineThreads->removeCurrentThread();
 }
 
 bool Heap::isPagedOut(double deadline)
@@ -587,7 +590,7 @@
 {
     GCPHASE(GatherStackRoots);
     m_jitStubRoutines.clearMarks();
-    m_machineThreads.gatherConservativeRoots(roots, m_jitStubRoutines, m_codeBlocks, dummy, registers);
+    m_machineThreads->gatherConservativeRoots(roots, m_jitStubRoutines, m_codeBlocks, dummy, registers);
 }
 
 void Heap::gatherJSStackRoots(ConservativeRoots& roots)

Modified: trunk/Source/_javascript_Core/heap/Heap.h (179752 => 179753)


--- trunk/Source/_javascript_Core/heap/Heap.h	2015-02-06 20:46:19 UTC (rev 179752)
+++ trunk/Source/_javascript_Core/heap/Heap.h	2015-02-06 20:57:45 UTC (rev 179753)
@@ -119,7 +119,7 @@
 
     VM* vm() const { return m_vm; }
     MarkedSpace& objectSpace() { return m_objectSpace; }
-    MachineThreads& machineThreads() { return m_machineThreads; }
+    MachineThreads& machineThreads() { return *m_machineThreads; }
 
     const SlotVisitor& slotVisitor() const { return m_slotVisitor; }
 
@@ -355,7 +355,7 @@
     Vector<Vector<ValueStringPair, 0, UnsafeVectorOverflow>*> m_tempSortingVectors;
     std::unique_ptr<HashSet<MarkedArgumentBuffer*>> m_markListSet;
 
-    MachineThreads m_machineThreads;
+    RefPtr<MachineThreads> m_machineThreads;
     
     GCThreadSharedData m_sharedData;
     SlotVisitor m_slotVisitor;

Modified: trunk/Source/_javascript_Core/heap/MachineStackMarker.cpp (179752 => 179753)


--- trunk/Source/_javascript_Core/heap/MachineStackMarker.cpp	2015-02-06 20:46:19 UTC (rev 179752)
+++ trunk/Source/_javascript_Core/heap/MachineStackMarker.cpp	2015-02-06 20:57:45 UTC (rev 179753)
@@ -91,9 +91,10 @@
 class MachineThreads::Thread {
     WTF_MAKE_FAST_ALLOCATED;
 public:
-    Thread(const PlatformThread& platThread, void* base)
+    Thread(PassRefPtr<MachineThreads> machineThreads, const PlatformThread& platThread, void* base)
         : platformThread(platThread)
         , stackBase(base)
+        , m_machineThreads(machineThreads)
     {
 #if USE(PTHREADS) && !OS(WINDOWS) && !OS(DARWIN) && defined(SA_RESTART)
         // if we have SA_RESTART, enable SIGUSR2 debugging mechanism
@@ -113,6 +114,7 @@
     Thread* next;
     PlatformThread platformThread;
     void* stackBase;
+    RefPtr<MachineThreads> m_machineThreads;
 };
 
 MachineThreads::MachineThreads(Heap* heap)
@@ -120,6 +122,7 @@
     , m_threadSpecific(0)
 #if !ASSERT_DISABLED
     , m_heap(heap)
+    , m_magicNumber(0x1234567890abcdef)
 #endif
 {
     UNUSED_PARAM(heap);
@@ -136,6 +139,9 @@
         delete t;
         t = next;
     }
+#if !ASSERT_DISABLED
+    m_magicNumber = 0xbaddbeefdeadbeef;
+#endif
 }
 
 static inline PlatformThread getCurrentPlatformThread()
@@ -170,7 +176,7 @@
     }
 
     threadSpecificSet(m_threadSpecific, this);
-    Thread* thread = new Thread(getCurrentPlatformThread(), wtfThreadData().stack().origin());
+    Thread* thread = new Thread(this, getCurrentPlatformThread(), wtfThreadData().stack().origin());
 
     MutexLocker lock(m_registeredThreadsMutex);
 
@@ -180,6 +186,7 @@
 
 void MachineThreads::removeThread(void* p)
 {
+    ASSERT(static_cast<MachineThreads*>(p)->m_magicNumber == 0x1234567890abcdef);
     static_cast<MachineThreads*>(p)->removeCurrentThread();
 }
 
@@ -187,6 +194,21 @@
 {
     PlatformThread currentPlatformThread = getCurrentPlatformThread();
 
+    // This thread could be the last entity that holds a RefPtr to the
+    // MachineThreads registry. We don't want the registry to be deleted while
+    // we're deleting the the Thread entry, because:
+    //
+    // 1. ~MachineThread() will attempt to lock its m_registeredThreadsMutex
+    //    and we already hold it here. m_registeredThreadsMutex is not
+    //    re-entrant.
+    // 2. The MutexLocker will unlock m_registeredThreadsMutex at the end of
+    //    this function. We can't let it be destructed until before then.
+    //
+    // Using this RefPtr here will defer destructing the registry till after
+    // MutexLocker releases its m_registeredThreadsMutex.
+    RefPtr<MachineThreads> retainMachineThreadsRegistryUntilDoneRemovingThread = this;
+
+    ASSERT(m_magicNumber == 0x1234567890abcdef);
     MutexLocker lock(m_registeredThreadsMutex);
     if (equalThread(currentPlatformThread, m_registeredThreads->platformThread)) {
         Thread* t = m_registeredThreads;

Modified: trunk/Source/_javascript_Core/heap/MachineStackMarker.h (179752 => 179753)


--- trunk/Source/_javascript_Core/heap/MachineStackMarker.h	2015-02-06 20:46:19 UTC (rev 179752)
+++ trunk/Source/_javascript_Core/heap/MachineStackMarker.h	2015-02-06 20:57:45 UTC (rev 179753)
@@ -24,6 +24,7 @@
 
 #include <setjmp.h>
 #include <wtf/Noncopyable.h>
+#include <wtf/ThreadSafeRefCounted.h>
 #include <wtf/ThreadSpecific.h>
 #include <wtf/ThreadingPrimitives.h>
 
@@ -34,7 +35,7 @@
     class Heap;
     class JITStubRoutineSet;
 
-    class MachineThreads {
+    class MachineThreads : public ThreadSafeRefCounted<MachineThreads> {
         WTF_MAKE_NONCOPYABLE(MachineThreads);
     public:
         typedef jmp_buf RegisterState;
@@ -46,6 +47,8 @@
 
         JS_EXPORT_PRIVATE void addCurrentThread(); // Only needs to be called by clients that can use the same heap from multiple threads.
 
+        void removeCurrentThread();
+
     private:
         class Thread;
 
@@ -55,13 +58,13 @@
         bool tryCopyOtherThreadStacks(MutexLocker&, void*, size_t capacity, size_t*);
 
         static void removeThread(void*);
-        void removeCurrentThread();
 
         Mutex m_registeredThreadsMutex;
         Thread* m_registeredThreads;
         WTF::ThreadSpecificKey m_threadSpecific;
 #if !ASSERT_DISABLED
         Heap* m_heap;
+        uint64_t m_magicNumber; // Only used for detecting use after free.
 #endif
     };
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to