// Safe INSTALL of a SafetyHook inline hook -- the symmetric partner of hook_guard.hpp's safe removal. // // Two problems this avoids, both hit when a game polls a hooked API at thousands of calls/s while // the subsystem is toggled on/off (mock_game_test's hook/unhook storm exercises exactly that): // // 1. Enable-before-assign. create_inline() builds the hook AND enables it (patches the target's bytes // to jump to the detour), THEN the result is move-assigned into the global the detour reads to // reach the trampoline. A call landing in the detour during that assign reads a torn global -> AV. // So we create StartDisabled, let dst be populated, and only THEN enable(). // // 2. Trampoline use-after-free on re-install. Recreating the hook on every install and destroying // it on every remove (= {}) frees the trampoline. A detour that has entered but not yet reached // its DetourGate::Guard (the unguarded prologue) can then call a freed trampoline -> AV at a // garbage address. drain() narrows that window but can't fully close it under heavy // preemption. So instead we treat hooks as PERSISTENT: create each once and thereafter only // enable()/disable() it across install/remove cycles. The trampoline is allocated once and never // freed during the session, so a stale detour always calls a live trampoline (which, when // disabled, simply runs the original). No churn, no leak (it's reused), no UAF. Removal therefore // disable()s the hook (and drains) but does NOT destroy it (see hook_guard.hpp). #pragma once #include #include namespace coop::hook { // Arm `detour` over `target` in `dst`: create it once (StartDisabled) if empty, then enable. Calling // this again after a remove just re-enables the SAME hook (no recreate -> the trampoline is never // freed). enable()'s [[nodiscard]] result is surfaced, not discarded; enabling an already-enabled // hook is a no-op success. inline void install_inline(safetyhook::InlineHook& dst, void* target, void* detour) { if (!dst) // create only the first time; reuse across enable/disable cycles { dst = safetyhook::create_inline(target, detour, safetyhook::InlineHook::StartDisabled); } if (dst && !dst.enable()) { OutputDebugStringA("coop: SafetyHook InlineHook::enable() failed during install\n"); } } template void install_inline(safetyhook::InlineHook& dst, T target, D detour) { install_inline(dst, reinterpret_cast(target), reinterpret_cast(detour)); } } // namespace coop::hook