Run clang-format (the repo's .clang-format: LLVM base, 120 cols, tabs, Allman functions) over every source file so the tree is formatter-clean. Whitespace only -- no behavior change; full x64 + x86 suites pass. Also set SortIncludes: false in .clang-format. Windows include order is load-bearing (windows.h must precede tlhelp32.h / mmreg.h / xinput.h / dinput.h; winsock2.h must precede windows.h), and the default alphabetical sort reorders tlhelp32.h ahead of windows.h -- a build break. Leaving order alone keeps the manual, correct grouping.
50 lines
2.5 KiB
C++
50 lines
2.5 KiB
C++
// 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 <safetyhook.hpp>
|
|
|
|
#include <windows.h>
|
|
|
|
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 <class T, class D>
|
|
void install_inline(safetyhook::InlineHook& dst, T target, D detour)
|
|
{
|
|
install_inline(dst, reinterpret_cast<void*>(target), reinterpret_cast<void*>(detour));
|
|
}
|
|
|
|
} // namespace coop::hook
|