Make it safe to store a ThreadSafeRefCounted object in Ref & RefPtr safe inside its destructor
https://bugs.webkit.org/show_bug.cgi?id=201576
Source/JavaScriptCore:
<rdar://problem/56001847>
Reviewed by Geoffrey Garen and Mark Lam.
Made DropAllLocks::DropAllLocks check Heap::isShuttingDown instead of VM's refCount being 0 to detect
when VM is getting destroyed.
* runtime/JSLock.cpp:
(JSC::JSLock::DropAllLocks::DropAllLocks):
Source/WTF:
Reviewed by Geoffrey Garen and Mark Lam.
This patch leaves m_refCount 1 inside the last deref call to ThreadSafeRefCounted
so that storing an instance of this object in Ref or RefPtr would not trigger a recursive delete.
Note: this patch does not try to fix a race condition by which another thread tries to ref()
this object after the "last" call to deref had happened since such a code would ref() this object
long after it had been removed (UAF), nor some code calling deref() before calling ref() inside
a destructor since there is no way to defend against unpaired calls to ref() & deref() like that.
Also added m_deletionHasBegun like RefCounted.
* wtf/ThreadSafeRefCounted.h:
(WTF::ThreadSafeRefCountedBase::ref const):
(WTF::ThreadSafeRefCountedBase::hasOneRef const):
(WTF::ThreadSafeRefCountedBase::derefBase const):
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251034 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 13c6cd3..b6fcc65 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,17 @@
+2019-10-10 Ryosuke Niwa <rniwa@webkit.org>
+
+ Make it safe to store a ThreadSafeRefCounted object in Ref & RefPtr safe inside its destructor
+ https://bugs.webkit.org/show_bug.cgi?id=201576
+ <rdar://problem/56001847>
+
+ Reviewed by Geoffrey Garen and Mark Lam.
+
+ Made DropAllLocks::DropAllLocks check Heap::isShuttingDown instead of VM's refCount being 0 to detect
+ when VM is getting destroyed.
+
+ * runtime/JSLock.cpp:
+ (JSC::JSLock::DropAllLocks::DropAllLocks):
+
2019-10-11 Keith Miller <keith_miller@apple.com>
Wasm B3IRGenerator should use arguments for control data.
diff --git a/Source/JavaScriptCore/runtime/JSLock.cpp b/Source/JavaScriptCore/runtime/JSLock.cpp
index daf0f77..0427fdd 100644
--- a/Source/JavaScriptCore/runtime/JSLock.cpp
+++ b/Source/JavaScriptCore/runtime/JSLock.cpp
@@ -279,7 +279,7 @@
// If the VM is in the middle of being destroyed then we don't want to resurrect it
// by allowing DropAllLocks to ref it. By this point the JSLock has already been
// released anyways, so it doesn't matter that DropAllLocks is a no-op.
- , m_vm(vm->refCount() ? vm : nullptr)
+ , m_vm(vm->heap.isShuttingDown() ? nullptr : vm)
{
if (!m_vm)
return;
diff --git a/Source/WTF/ChangeLog b/Source/WTF/ChangeLog
index 07f2469..ef97fb1 100644
--- a/Source/WTF/ChangeLog
+++ b/Source/WTF/ChangeLog
@@ -1,3 +1,25 @@
+2019-10-10 Ryosuke Niwa <rniwa@webkit.org>
+
+ Make it safe to store a ThreadSafeRefCounted object in Ref & RefPtr safe inside its destructor
+ https://bugs.webkit.org/show_bug.cgi?id=201576
+
+ Reviewed by Geoffrey Garen and Mark Lam.
+
+ This patch leaves m_refCount 1 inside the last deref call to ThreadSafeRefCounted
+ so that storing an instance of this object in Ref or RefPtr would not trigger a recursive delete.
+
+ Note: this patch does not try to fix a race condition by which another thread tries to ref()
+ this object after the "last" call to deref had happened since such a code would ref() this object
+ long after it had been removed (UAF), nor some code calling deref() before calling ref() inside
+ a destructor since there is no way to defend against unpaired calls to ref() & deref() like that.
+
+ Also added m_deletionHasBegun like RefCounted.
+
+ * wtf/ThreadSafeRefCounted.h:
+ (WTF::ThreadSafeRefCountedBase::ref const):
+ (WTF::ThreadSafeRefCountedBase::hasOneRef const):
+ (WTF::ThreadSafeRefCountedBase::derefBase const):
+
2019-10-11 Alex Christensen <achristensen@webkit.org>
Only use CFNetwork SPI for metrics where needed
diff --git a/Source/WTF/wtf/ThreadSafeRefCounted.h b/Source/WTF/wtf/ThreadSafeRefCounted.h
index 8a624b2..0561f20 100644
--- a/Source/WTF/wtf/ThreadSafeRefCounted.h
+++ b/Source/WTF/wtf/ThreadSafeRefCounted.h
@@ -32,19 +32,39 @@
namespace WTF {
+#if defined(NDEBUG) && !ENABLE(SECURITY_ASSERTIONS)
+#define CHECK_THREAD_SAFE_REF_COUNTED_LIFECYCLE 0
+#else
+#define CHECK_THREAD_SAFE_REF_COUNTED_LIFECYCLE 1
+#endif
+
class ThreadSafeRefCountedBase {
WTF_MAKE_NONCOPYABLE(ThreadSafeRefCountedBase);
WTF_MAKE_FAST_ALLOCATED;
public:
ThreadSafeRefCountedBase() = default;
+#if CHECK_THREAD_SAFE_REF_COUNTED_LIFECYCLE
+ ~ThreadSafeRefCountedBase()
+ {
+ // When this ThreadSafeRefCounted object is a part of another object, derefBase() is never called on this object.
+ m_deletionHasBegun = true;
+ }
+#endif
+
void ref() const
{
+#if CHECK_THREAD_SAFE_REF_COUNTED_LIFECYCLE
+ ASSERT_WITH_SECURITY_IMPLICATION(!m_deletionHasBegun);
+#endif
++m_refCount;
}
bool hasOneRef() const
{
+#if CHECK_THREAD_SAFE_REF_COUNTED_LIFECYCLE
+ ASSERT(!m_deletionHasBegun);
+#endif
return refCount() == 1;
}
@@ -57,11 +77,31 @@
// Returns whether the pointer should be freed or not.
bool derefBase() const
{
- return !--m_refCount;
+ ASSERT(m_refCount);
+
+#if CHECK_THREAD_SAFE_REF_COUNTED_LIFECYCLE
+ ASSERT_WITH_SECURITY_IMPLICATION(!m_deletionHasBegun);
+#endif
+
+ if (UNLIKELY(!--m_refCount)) {
+ // Setting m_refCount to 1 here prevents double delete within the destructor but not from another thread
+ // since such a thread could have ref'ed this object long after it had been deleted. See webkit.org/b/201576.
+ m_refCount = 1;
+#if CHECK_THREAD_SAFE_REF_COUNTED_LIFECYCLE
+ m_deletionHasBegun = true;
+#endif
+ return true;
+ }
+
+ return false;
}
private:
mutable std::atomic<unsigned> m_refCount { 1 };
+
+#if CHECK_THREAD_SAFE_REF_COUNTED_LIFECYCLE
+ mutable std::atomic<bool> m_deletionHasBegun { false };
+#endif
};
enum class DestructionThread { Any, Main, MainRunLoop };