Skip to content

Commit 87adcd9

Browse files
authored
Merge pull request #330 from jolavillette/fix/rsevents-unregister-inflight-uaf
RsEventsService: make unregisterEventsHandler() a barrier (fix shutdown UAF)
2 parents f655c6d + dddaf26 commit 87adcd9

2 files changed

Lines changed: 49 additions & 10 deletions

File tree

src/services/rseventsservice.cc

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -166,18 +166,33 @@ std::error_condition RsEventsService::registerEventsHandler(
166166
std::error_condition RsEventsService::unregisterEventsHandler(
167167
RsEventsHandlerId_t hId )
168168
{
169-
RS_STACK_MUTEX(mHandlerMapMtx);
169+
std::error_condition retval = RsEventsErrorNum::INVALID_HANDLER_ID;
170170

171-
for(uint32_t i=0; i<mHandlerMaps.size(); ++i)
172171
{
173-
auto it = mHandlerMaps[i].find(hId);
174-
if(it != mHandlerMaps[i].end())
172+
RS_STACK_MUTEX(mHandlerMapMtx);
173+
174+
for(uint32_t i=0; i<mHandlerMaps.size(); ++i)
175175
{
176-
mHandlerMaps[i].erase(it);
177-
return std::error_condition();
176+
auto it = mHandlerMaps[i].find(hId);
177+
if(it != mHandlerMaps[i].end())
178+
{
179+
mHandlerMaps[i].erase(it);
180+
retval = std::error_condition();
181+
break;
182+
}
178183
}
179184
}
180-
return RsEventsErrorNum::INVALID_HANDLER_ID;
185+
186+
/* At this point no *future* dispatch can pick up this handler. But an
187+
* ongoing handleEvent() may still hold a copy of it in flight (callbacks are
188+
* run outside mHandlerMapMtx). Fence on mDispatchMtx so that, once we return,
189+
* the handler is guaranteed not to be executing either: callers that
190+
* unregister from their destructor (most GUI widgets) can then be destroyed
191+
* safely. Recursive mutex => when called from within a callback on the
192+
* dispatching thread this is a cheap no-op instead of a self-deadlock. */
193+
{ std::lock_guard<std::recursive_mutex> dispatchFence(mDispatchMtx); }
194+
195+
return retval;
181196
}
182197

183198
void RsEventsService::threadTick()
@@ -223,12 +238,21 @@ void RsEventsService::handleEvent(std::shared_ptr<const RsEvent> event)
223238
return;
224239
}
225240

241+
/* Hold mDispatchMtx across the whole dispatch so unregisterEventsHandler()
242+
* can fence on it and guarantee a handler is not running once it returns
243+
* (see mDispatchMtx doc). Recursive: a callback re-entering on this same
244+
* thread (self-unregister or synchronous sendEvent) does not deadlock.
245+
* Safe against GUI teardown because handlers only post asynchronously (Qt
246+
* QueuedConnection) and never block waiting on the thread that unregisters,
247+
* so there is no lock-order cycle. */
248+
std::lock_guard<std::recursive_mutex> dispatchLock(mDispatchMtx);
249+
226250
std::list<std::function<void(std::shared_ptr<const RsEvent>)> > callbacks;
227251
{
228252
RS_STACK_MUTEX(mHandlerMapMtx);
229-
/* It is important to NOT call the callback under mutex protection to
230-
* allow callbacks to send other events or unregister themselves,
231-
* which would otherwise deadlock. */
253+
/* It is important to NOT call the callback under mHandlerMapMtx
254+
* protection to allow callbacks to send other events or unregister
255+
* themselves, which would otherwise deadlock. */
232256

233257
// Call all clients that registered a callback for this event type
234258
for(auto& cbit: mHandlerMaps[static_cast<uint32_t>(event->mType)])

src/services/rseventsservice.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <cstdint>
2626
#include <deque>
2727
#include <array>
28+
#include <mutex>
2829

2930
#include "retroshare/rsevents.h"
3031
#include "util/rsthreads.h"
@@ -69,6 +70,20 @@ class RsEventsService :
6970
std::error_condition isEventInvalid(std::shared_ptr<const RsEvent> event);
7071

7172
RsMutex mHandlerMapMtx;
73+
74+
/** Held by handleEvent() for the whole duration of the callbacks dispatch
75+
* loop, so that unregisterEventsHandler() can act as a barrier: after it
76+
* returns, the removed handler is guaranteed to be neither running nor about
77+
* to start. Without this, unregister only removes the handler from the map,
78+
* but handleEvent() runs callbacks on a *copy* taken outside mHandlerMapMtx
79+
* (on purpose, to let callbacks re-enter), so a callback whose owner is
80+
* being destroyed on another thread could still fire against a dangling
81+
* object -> use-after-free (typically a SIGSEGV in qobject_cast<QThread*>
82+
* inside RsQThreadUtils::postToObject at shutdown). Recursive so that a
83+
* callback re-entering (self-unregister or synchronous sendEvent) on the
84+
* dispatching thread does not deadlock. */
85+
std::recursive_mutex mDispatchMtx;
86+
7287
RsEventsHandlerId_t mLastHandlerId;
7388

7489
/** Storage for event handlers, keep 10 extra types for plugins that might

0 commit comments

Comments
 (0)