From b1d4c9926c5f8996bb7d6932b007c679288b3597 Mon Sep 17 00:00:00 2001 From: "seer-by-sentry[bot]" <157164994+seer-by-sentry[bot]@users.noreply.github.com> Date: Thu, 7 May 2026 21:16:35 +0000 Subject: [PATCH] bugfix(w3dmouse): Prevent race conditions and use-after-free during shutdown --- .../Source/W3DDevice/GameClient/W3DMouse.cpp | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp index 5b71653c8d8..11d913b8252 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/W3DMouse.cpp @@ -75,8 +75,15 @@ void MouseThreadClass::Thread_Function() while (running) { isThread = TRUE; - if (TheMouse) - TheMouse->draw(); + // Hold the mutex for the TheMouse check AND the draw() call together so + // that TheMouse cannot be destroyed between the null-check and the actual + // dereference. CriticalSectionClass uses a Windows CRITICAL_SECTION which + // is re-entrant per-thread, so the nested acquisition inside draw() is safe. + { + CriticalSectionClass::LockClass m(mutex); + if (TheMouse) + TheMouse->draw(); + } isThread = FALSE; Switch_Thread(); } @@ -108,6 +115,12 @@ W3DMouse::W3DMouse() W3DMouse::~W3DMouse() { + // Stop the mouse update thread FIRST, before freeing any assets it may be + // actively using. If the thread is mid-draw(), Stop() will wait for it to + // finish before returning, preventing a use-after-free when we release + // D3D/W3D resources below. + thread.Stop(); + LPDIRECT3DDEVICE8 m_pDev = DX8Wrapper::_Get_D3D_Device8(); if (m_pDev) @@ -119,8 +132,6 @@ W3DMouse::~W3DMouse() freeD3DAssets(); freeW3DAssets(); - thread.Stop(); - } void W3DMouse::initPolygonAssets()