Skip to content

Commit 3e72a15

Browse files
committed
Tentative fix for use-after-free in thread exit notification linked list, and for race between thread exit and thread exit unsubscription (see cameron314#288)
1 parent d3a1e75 commit 3e72a15

File tree

2 files changed

+23
-7
lines changed

2 files changed

+23
-7
lines changed

concurrentqueue.h

+22-6
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
#include <climits> // for CHAR_BIT
7878
#include <array>
7979
#include <thread> // partly for __WINPTHREADS_VERSION if on MinGW-w64 w/ POSIX threading
80+
#include <mutex> // used for thread exit synchronization
8081

8182
// Platform-specific definitions of a numeric thread ID type and an invalid value
8283
namespace moodycamel { namespace details {
@@ -562,29 +563,38 @@ namespace details
562563
typedef RelacyThreadExitListener ThreadExitListener;
563564
typedef RelacyThreadExitNotifier ThreadExitNotifier;
564565
#else
566+
class ThreadExitNotifier;
567+
565568
struct ThreadExitListener
566569
{
567570
typedef void (*callback_t)(void*);
568571
callback_t callback;
569572
void* userData;
570573

571574
ThreadExitListener* next; // reserved for use by the ThreadExitNotifier
575+
ThreadExitNotifier* chain; // reserved for use by the ThreadExitNotifier
572576
};
573-
574-
577+
575578
class ThreadExitNotifier
576579
{
577580
public:
578581
static void subscribe(ThreadExitListener* listener)
579582
{
580583
auto& tlsInst = instance();
584+
std::lock_guard<std::mutex> guard(mutex());
581585
listener->next = tlsInst.tail;
586+
listener->chain = &tlsInst;
582587
tlsInst.tail = listener;
583588
}
584589

585590
static void unsubscribe(ThreadExitListener* listener)
586591
{
587-
auto& tlsInst = instance();
592+
std::lock_guard<std::mutex> guard(mutex());
593+
if (!listener->chain) {
594+
return; // race with ~ThreadExitNotifier
595+
}
596+
auto& tlsInst = *listener->chain;
597+
listener->chain = nullptr;
588598
ThreadExitListener** prev = &tlsInst.tail;
589599
for (auto ptr = tlsInst.tail; ptr != nullptr; ptr = ptr->next) {
590600
if (ptr == listener) {
@@ -604,7 +614,9 @@ namespace details
604614
{
605615
// This thread is about to exit, let everyone know!
606616
assert(this == &instance() && "If this assert fails, you likely have a buggy compiler! Change the preprocessor conditions such that MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED is no longer defined.");
617+
std::lock_guard<std::mutex> guard(mutex());
607618
for (auto ptr = tail; ptr != nullptr; ptr = ptr->next) {
619+
ptr->chain = nullptr;
608620
ptr->callback(ptr->userData);
609621
}
610622
}
@@ -615,6 +627,13 @@ namespace details
615627
static thread_local ThreadExitNotifier notifier;
616628
return notifier;
617629
}
630+
631+
static inline std::mutex& mutex()
632+
{
633+
// Must be static because the ThreadExitNotifier could be destroyed while unsubscribe is called
634+
static std::mutex mutex;
635+
return mutex;
636+
}
618637

619638
private:
620639
ThreadExitListener* tail;
@@ -3521,9 +3540,6 @@ class ConcurrentQueue
35213540
#ifdef MOODYCAMEL_CPP11_THREAD_LOCAL_SUPPORTED
35223541
void implicit_producer_thread_exited(ImplicitProducer* producer)
35233542
{
3524-
// Remove from thread exit listeners
3525-
details::ThreadExitNotifier::unsubscribe(&producer->threadExitListener);
3526-
35273543
// Remove from hash
35283544
#ifdef MCDBGQ_NOLOCKFREE_IMPLICITPRODHASH
35293545
debug::DebugLock lock(implicitProdMutex);

tests/unittests/unittests.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -3089,7 +3089,7 @@ class ConcurrentQueueTests : public TestClass<ConcurrentQueueTests>
30893089
std::vector<SimpleThread> threads(32);
30903090
ConcurrentQueue<int, Traits> q;
30913091
for (auto& thread : threads) {
3092-
SimpleThread t([&] {
3092+
thread = SimpleThread([&] {
30933093
int x;
30943094
for (int j = 0; j != 16; ++j) {
30953095
q.enqueue(0);

0 commit comments

Comments
 (0)