Make inline-hook install AND remove safe to spam
The uncapped, input-polling mock_game_test storm (thousands of presents/s, now
also driving the input/focus/MKB hooks) drove out a family of install/remove races
the slow vsync'd mock had masked. Fixes (hook/src/hook_install.hpp + hook_guard.hpp):
- Persistent hooks. The old model created a hook on install and DESTROYED it on
remove (= {}), freeing the trampoline; a detour about to call it (.stdcall) then
hit freed memory -> 0xC0000005. drain() can't fully close that window (a thread
can be inside the detour but not past its Guard ctor). So hooks are now created
ONCE and only enable()/disable()d across install/remove cycles -- never destroyed
during the session -- so a stale detour always calls a live trampoline (disabled,
it just runs the original). Reused, so no churn and no leak. remove_* therefore
disable()s + drain()s but does not destroy; install guards check .enabled().
- Install race. create_inline() enables the hook before the result is move-assigned
into the global the detour reads; a call landing in the detour mid-assign reads a
torn hook -> AV. install_inline() creates StartDisabled, assigns, then enable()s.
- drain() Sleep(1)s BEFORE each zero-check, so a thread that entered the detour but
hasn't reached its Guard registers before we conclude zero.
- Focus: publish g_orig_proc before SetWindowLongPtr activates the subclass (and
subclass_proc falls back to DefWindowProc if null); and disable the focus-query
hooks in reverse install order, because GetForegroundWindow shares user32 code
with GetActiveWindow (keep GFW hooked until GAW is unhooked).
- disable()/enable() [[nodiscard]] results are handled (logged), not (void)-discarded.
Storm now survives on every backend across repeated runs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
#include "coop/shared_memory.hpp"
|
||||
#include "debug_log.hpp"
|
||||
#include "hook_guard.hpp"
|
||||
#include "hook_install.hpp"
|
||||
#include "hook_registry.hpp"
|
||||
|
||||
namespace coop::hook
|
||||
@@ -287,9 +288,9 @@ bool install_opengl_hooks(IpcClient& ipc)
|
||||
{
|
||||
g_ipc = &ipc;
|
||||
g_pid = GetCurrentProcessId();
|
||||
if (g_hk_swapbuffers || g_hk_wglswap)
|
||||
if (g_hk_swapbuffers.enabled() || g_hk_wglswap.enabled())
|
||||
{
|
||||
return true; // already installed
|
||||
return true; // already installed (persistent hooks; re-install below re-enables them)
|
||||
}
|
||||
|
||||
g_id_swapbuffers = hook_register("SwapBuffers", HookSubsys_Video);
|
||||
@@ -301,7 +302,7 @@ bool install_opengl_hooks(IpcClient& ipc)
|
||||
{
|
||||
if (void* fn = reinterpret_cast<void*>(GetProcAddress(gdi, "SwapBuffers")))
|
||||
{
|
||||
g_hk_swapbuffers = safetyhook::create_inline(fn, reinterpret_cast<void*>(&hk_SwapBuffers));
|
||||
install_inline(g_hk_swapbuffers, fn, &hk_SwapBuffers);
|
||||
}
|
||||
}
|
||||
// opengl32!wglSwapBuffers if OpenGL is already loaded.
|
||||
@@ -309,7 +310,7 @@ bool install_opengl_hooks(IpcClient& ipc)
|
||||
{
|
||||
if (void* fn = reinterpret_cast<void*>(GetProcAddress(gl, "wglSwapBuffers")))
|
||||
{
|
||||
g_hk_wglswap = safetyhook::create_inline(fn, reinterpret_cast<void*>(&hk_wglSwapBuffers));
|
||||
install_inline(g_hk_wglswap, fn, &hk_wglSwapBuffers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,13 +328,13 @@ void remove_opengl_hooks()
|
||||
// trampoline) doesn't have it freed under it. Destroying (= {}) before the drain frees the
|
||||
// trampoline immediately -- a UAF the uncapped mock-game storm (thousands of swaps/s) can hit.
|
||||
// Disable -> drain -> only then destroy.
|
||||
(void)g_hk_swapbuffers.disable();
|
||||
(void)g_hk_wglswap.disable();
|
||||
disable_for_removal(g_hk_swapbuffers);
|
||||
disable_for_removal(g_hk_wglswap);
|
||||
hook_set_installed(g_id_swapbuffers, false);
|
||||
hook_set_installed(g_id_wglswap, false);
|
||||
g_gate.drain();
|
||||
g_hk_swapbuffers = {}; // no detour in-flight or able to start now -> safe to free the trampolines
|
||||
g_hk_wglswap = {};
|
||||
// Persistent hooks: keep them ALIVE (disabled) so a stale detour's trampoline call is never freed
|
||||
// -- re-install re-enables them (see hook_install.hpp).
|
||||
release_shared();
|
||||
if (g_ctx != nullptr)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user