Harden every hook against the install/remove use-after-free
Spamming a subsystem toggle (the "Mirror video" button) could crash the game: remove_*_hooks freed a hook's shared D3D / Vulkan / IPC state immediately, while a capture detour was still mid-flight on the game's render thread -> use-after-free. Only the audio hooks had the safe-unhook drain; the video (Present/D3D9/D3D10/GL/ Vulkan) and XInput/focus/MKB hooks did not. Test-first: mock_game_test now runs an aggressive hook/unhook storm -- a separate thread thrashes every subsystem on/off while the game presents, across all backends. It crashed gl + vk (0xC0000005) and failed dx9 capture-resume before the fix. Fix (hook/src/hook_guard.hpp, DetourGate): each detour wraps its body in an RAII active-count Guard; remove_* restores the hook first (so no new detour starts), drains the in-flight detours to zero, and only then frees the shared state. Vulkan is special-cased -- the game caches hk_vkQueuePresentKHR, so removal closes an atomic capture gate (detours then pass through to the real present), drains, then frees the read-back resources. Storm now passes on every backend. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <safetyhook.hpp>
|
||||
|
||||
#include "hook_guard.hpp"
|
||||
#include "hook_registry.hpp"
|
||||
|
||||
namespace coop::hook
|
||||
@@ -14,6 +15,8 @@ namespace coop::hook
|
||||
namespace
|
||||
{
|
||||
|
||||
DetourGate g_gate; // drains in-flight polling detours before remove tears the hooks down
|
||||
|
||||
// Synthesized input state the polling hooks report. Written by the worker thread
|
||||
// (mkb_pump), read by the game's thread inside the detours -> all atomic.
|
||||
std::atomic<bool> g_active{false};
|
||||
@@ -37,6 +40,7 @@ bool g_installed = false;
|
||||
|
||||
SHORT WINAPI hk_GetAsyncKeyState(int vkey)
|
||||
{
|
||||
DetourGate::Guard guard(g_gate);
|
||||
const SHORT orig = g_hk_async.stdcall<SHORT>(vkey);
|
||||
if (g_active.load(std::memory_order_relaxed) && vkey >= 0 && vkey < 256 &&
|
||||
g_key_down[vkey].load(std::memory_order_relaxed))
|
||||
@@ -48,6 +52,7 @@ SHORT WINAPI hk_GetAsyncKeyState(int vkey)
|
||||
|
||||
BOOL WINAPI hk_GetKeyboardState(PBYTE state)
|
||||
{
|
||||
DetourGate::Guard guard(g_gate);
|
||||
const BOOL r = g_hk_kbstate.stdcall<BOOL>(state);
|
||||
if (r && state != nullptr && g_active.load(std::memory_order_relaxed))
|
||||
{
|
||||
@@ -64,6 +69,7 @@ BOOL WINAPI hk_GetKeyboardState(PBYTE state)
|
||||
|
||||
BOOL WINAPI hk_GetCursorPos(LPPOINT pt)
|
||||
{
|
||||
DetourGate::Guard guard(g_gate);
|
||||
const BOOL r = g_hk_cursor.stdcall<BOOL>(pt);
|
||||
if (g_active.load(std::memory_order_relaxed) && g_have_cursor.load(std::memory_order_relaxed) && pt != nullptr)
|
||||
{
|
||||
@@ -257,9 +263,10 @@ void remove_mkb_hooks()
|
||||
return;
|
||||
}
|
||||
g_active.store(false, std::memory_order_release);
|
||||
g_hk_async = {}; // InlineHook destructor restores the original bytes
|
||||
g_hk_async = {}; // InlineHook destructor restores the original bytes -> no new detour starts
|
||||
g_hk_kbstate = {};
|
||||
g_hk_cursor = {};
|
||||
g_gate.drain(); // wait for any in-flight polling detour before clearing the synth state
|
||||
for (int vk = 0; vk < 256; ++vk)
|
||||
{
|
||||
g_key_down[vk].store(false, std::memory_order_relaxed); // no stuck keys
|
||||
|
||||
Reference in New Issue
Block a user