Fix stale removal comments (persistent, not destroy) + add deterministic install test

- The remove_* comments still described the superseded "disable -> drain -> destroy"
  flow; the code keeps hooks alive (persistent) and re-enables on re-install. Updated
  the comments to match, and corrected the XInput note (its detours return synthesized
  state and never call the trampoline, so destroying its vector is safe -- unlike the
  trampoline-calling present/MKB/focus-cursor hooks).
- hook_install_test: a fast, single-threaded contract test for hook_install.hpp --
  install_inline creates the hook once and reuses the SAME trampoline across 50
  install/remove cycles (never freed -> no stale-detour UAF), toggling enable/disable
  cleanly. Fills the guard the removed (flaky, concurrency-bound) reproducer left, with
  no threads so it can't flake on SafetyHook's enable/disable atomicity.

x64 23/23, x86 3/3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 11:51:48 +02:00
parent 8a183902ad
commit f843c56f5b
8 changed files with 139 additions and 26 deletions

View File

@@ -367,11 +367,11 @@ bool install_d3d9_hooks(IpcClient& ipc)
void remove_d3d9_hooks()
{
// DISABLE (not destroy) first: restores Present's bytes under thread suspension (no new detour)
// but keeps the trampoline alive, so an in-flight detour about to call g_hk_present9.stdcall()
// (the 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 presents/s) hits
// reliably (0xC0000005). Disable -> drain -> only then destroy.
// DISABLE (persistent model -- never destroy during the session): restores Present's bytes under
// thread suspension (no new detour) but keeps the trampoline alive, so an in-flight detour about
// to call g_hk_present9.stdcall() (the trampoline) never has it freed under it. Destroying (= {})
// would free it -- a UAF the thousands/s storm hits reliably (0xC0000005). Disable -> drain ->
// leave alive (re-install re-enables; see hook_install.hpp).
disable_for_removal(g_hk_present9);
hook_set_installed(g_id_present9, false);
g_gate.drain();