From 33f5ed9ef58f26c6b3b52cf605dd4311d658a60d Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 6 Nov 2026 16:09:55 +0100 Subject: Fix audit findings: crash safety, correctness, rendering, performance Crash/UB: - gate every hook behind an init-stage flag until config/surface/input are ready - publish module maps as immutable atomic snapshots instead of reallocating shared vectors under unlocked readers - validate the %ebp walk in the RandomSeed/RandomFloat hooks against the stack - bounds-check every RecvTable/RecvProp/string dereference with module_range() - preserve the Itanium vtable header when cloning (client, CInput, model render, studio render) - reject negative usercmd sequence numbers; guard config_load against a null section target; cap the bone parent walk; check mprotect; drop PROT_EXEC on the GOT patch; absorb server.so .bss tail Correctness: - filter dormant entities across ESP, aimbot, triggerbot, melee, items and projectiles (slot self-tested against the local player) - reset the server-health latch per map and only latch on valid model indices - bone cache sized for the 128-target scan; set valid=false on every failure; head_position keeps searching past an invalid bone - vischeck steps past glass by probing until clear, and ignores entities owned by the target (held weapons, bone followers) - aimbot: maxs no longer disables it, AABB-aware FOV prefilter, lock hysteresis, lead point re-checked for FOV and visibility - spread: seed hook arms on the no-spread toggle alone; engine toggle only controls the RandomFloat zeroing - resolve m_hOwnerEntity/m_hOwner; NPC lead velocity from the server entity - vector_angles pitch range, point_ground normal test, underwater jump+duck no longer skips combat, semi-auto press/release edge for synthetic fire - item/projectile markers and blast rings refresh origin per frame while discovery stays at 10 Hz; trajectory uses the real m_DmgRadius and honours the blast-radius toggle Rendering/GUI: - one overlay draw per engine frame (FRAME_RENDER_START boundary), fixing alpha stacking and the speed-graph flicker - clipped lines/outlines and ellipsised text instead of drawing past the content pane or shifting over neighbours - dropdown dismissal consumes the click; hover ignores hidden controls - chams: reset AlphaModulate, reference shared materials once, restore prop tint colour; fonts created once per handle - projection uses Source's 4:3 aspect scaling and roll; screen size refreshed each frame; FOV circle gated on the aim toggles, not the bind Performance: - TTL caches for NPC relation, friendly/neutral and damage class - model names memoised per model index - entity pointer -> index cache for the triggerbot (was a 4096-slot walk/tick) Build: - real Makefile with -MMD dependency tracking, debug/release objects and an always-relink rule; build.sh kept as a thin wrapper --- src/modules.cpp | 47 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 9 deletions(-) (limited to 'src/modules.cpp') diff --git a/src/modules.cpp b/src/modules.cpp index 7a82028..a671d82 100644 --- a/src/modules.cpp +++ b/src/modules.cpp @@ -1,10 +1,27 @@ // modules.cpp — /proc/self/maps parsing and module discovery #include "hl2h.h" +#include #include -#include #include -static std::mutex g_modules_mutex; +// Immutable snapshots. Readers load a pointer with acquire semantics and get a +// module that never changes underneath them. find_modules() publishes a fresh +// copy instead of mutating the current one; retired snapshots are deliberately +// leaked so a reference already handed to another thread stays valid. +static const Module g_no_module; +static std::atomic g_client_snap{&g_no_module}; +static std::atomic g_engine_snap{&g_no_module}; +static std::atomic g_server_snap{&g_no_module}; + +const Module &mod_client() { + return *g_client_snap.load(std::memory_order_acquire); +} +const Module &mod_engine() { + return *g_engine_snap.load(std::memory_order_acquire); +} +const Module &mod_server() { + return *g_server_snap.load(std::memory_order_acquire); +} static bool basename_is(const char *path, const char *base) { size_t pl = strlen(path), bl = strlen(base); @@ -55,6 +72,9 @@ void find_modules() { else if (engine.valid() && engine.ranges.back().file && lo == engine.ranges.back().hi && hi - lo <= 0x1000000) m = &engine; + else if (server.valid() && server.ranges.back().file && + lo == server.ranges.back().hi && hi - lo <= 0x1000000) + m = &server; } if (!m) continue; @@ -72,16 +92,25 @@ void find_modules() { } } fclose(f); - // setup_thread and resolve_server_netvars can both call this; the old code - // reset the shared Module globals and reallocated their vectors live, which - // crashed under the startup thread. Publish complete snapshots instead. - std::lock_guard lock(g_modules_mutex); + // setup_thread and resolve_server_netvars can both call this. Publish whole + // snapshots atomically so a reader never iterates a vector being reallocated. if (client.valid()) - g_client = std::move(client); + g_client_snap.store(new Module(std::move(client)), std::memory_order_release); if (engine.valid()) - g_engine = std::move(engine); + g_engine_snap.store(new Module(std::move(engine)), std::memory_order_release); if (server.valid()) - g_server = std::move(server); + g_server_snap.store(new Module(std::move(server)), std::memory_order_release); +} + +bool module_range(uintptr_t p, size_t n) { + if (n == 0) + return true; + uintptr_t last = p + (n - 1); + if (last < p) + return false; // address-space wrap + return (mod_client().contains(p) && mod_client().contains(last)) || + (mod_engine().contains(p) && mod_engine().contains(last)) || + (mod_server().contains(p) && mod_server().contains(last)); } // --------------------------------------------------------------------------- -- cgit v1.2.3