Skip to content

RsEventsService: make unregisterEventsHandler() a barrier (fix shutdown UAF)#330

Open
jolavillette wants to merge 1 commit into
RetroShare:masterfrom
jolavillette:fix/rsevents-unregister-inflight-uaf
Open

RsEventsService: make unregisterEventsHandler() a barrier (fix shutdown UAF)#330
jolavillette wants to merge 1 commit into
RetroShare:masterfrom
jolavillette:fix/rsevents-unregister-inflight-uaf

Conversation

@jolavillette

Copy link
Copy Markdown
Contributor

RsEventsService: make unregisterEventsHandler() a barrier (fix shutdown UAF)

handleEvent() copies the registered callbacks under mHandlerMapMtx and then runs them outside the lock (on purpose, so a callback may re-enter to send events or unregister itself). As a side effect unregisterEventsHandler() was not a barrier: it removed the handler from the map, but an in-flight dispatch still held a private copy of it. If the handler's owner (a GUI widget) was destroyed on another thread in that window, the copy fired against a dangling object -> use-after-free, typically SIGSEGV in qobject_cast<QThread*> inside RsQThreadUtils::postToObject. This is what tears the process down at shutdown, when many widgets are destroyed while the events thread keeps ticking a flood of peer-disconnect events (the rest of the crash dump is just exit()-in-the- signal-handler unwinding RsServer while other threads are still running).

Add a recursive mDispatchMtx held by handleEvent() across the whole dispatch loop. unregisterEventsHandler() erases the handler under mHandlerMapMtx (no future dispatch can pick it up) and then fences on mDispatchMtx (no ongoing dispatch is still running it), so once it returns the handler is guaranteed neither running nor about to start. Widgets that unregister from their destructor are then safe to destroy. Recursive so a callback re-entering on the dispatching thread (self-unregister / synchronous sendEvent) does not deadlock; safe because handlers post asynchronously (Qt QueuedConnection) and never block on the unregistering thread, so there is no lock-order cycle.

…wn UAF)

handleEvent() copies the registered callbacks under mHandlerMapMtx and then
runs them *outside* the lock (on purpose, so a callback may re-enter to send
events or unregister itself). As a side effect unregisterEventsHandler() was
not a barrier: it removed the handler from the map, but an in-flight dispatch
still held a private copy of it. If the handler's owner (a GUI widget) was
destroyed on another thread in that window, the copy fired against a dangling
object -> use-after-free, typically SIGSEGV in qobject_cast<QThread*> inside
RsQThreadUtils::postToObject. This is what tears the process down at shutdown,
when many widgets are destroyed while the events thread keeps ticking a flood
of peer-disconnect events (the rest of the crash dump is just exit()-in-the-
signal-handler unwinding RsServer while other threads are still running).

Add a recursive mDispatchMtx held by handleEvent() across the whole dispatch
loop. unregisterEventsHandler() erases the handler under mHandlerMapMtx (no
future dispatch can pick it up) and then fences on mDispatchMtx (no ongoing
dispatch is still running it), so once it returns the handler is guaranteed
neither running nor about to start. Widgets that unregister from their
destructor are then safe to destroy. Recursive so a callback re-entering on
the dispatching thread (self-unregister / synchronous sendEvent) does not
deadlock; safe because handlers post asynchronously (Qt QueuedConnection) and
never block on the unregistering thread, so there is no lock-order cycle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant