From 65833487dfc420fe0b09162230da367205ed9c9b Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 13:02:09 -0400 Subject: [PATCH] regamedll: 1 CWE-407 defect, MOAD 0002-0005 CLEAN regamedll-0001: BotProfileManager::GetRandomProfile calls UTIL_IsNameTaken O(C) per profile in loop over all profiles O(P), yielding O(P*C*2) string comparisons. Fix: build taken-name set once O(C), check O(1) per profile. 19.8x at P=100/C=32, 4/4 PASS. --- .../regamedll-0001/patch/regamedll-0001.patch | 44 ++++ .../test/test_regamedll_0001.py | 218 ++++++++++++++++++ .../xash3d-0001/test/test_consistency_lookup | Bin 0 -> 16552 bytes defects/xash3d-0002/test/test_precache_index | Bin 0 -> 16768 bytes defects/xash3d-0003/test/test_rcon_logging | Bin 0 -> 16328 bytes 5 files changed, 262 insertions(+) create mode 100644 defects/regamedll-0001/patch/regamedll-0001.patch create mode 100644 defects/regamedll-0001/test/test_regamedll_0001.py create mode 100755 defects/xash3d-0001/test/test_consistency_lookup create mode 100755 defects/xash3d-0002/test/test_precache_index create mode 100755 defects/xash3d-0003/test/test_rcon_logging diff --git a/defects/regamedll-0001/patch/regamedll-0001.patch b/defects/regamedll-0001/patch/regamedll-0001.patch new file mode 100644 index 000000000..41b77ee21 --- /dev/null +++ b/defects/regamedll-0001/patch/regamedll-0001.patch @@ -0,0 +1,44 @@ +--- a/regamedll/game_shared/bot/bot_profile.cpp ++++ b/regamedll/game_shared/bot/bot_profile.cpp +@@ -590,13 +590,24 @@ + } + + // Return random unused profile that matches the given difficulty level ++// DEFECT: UTIL_IsNameTaken called per-profile O(P*C) where P=profiles, C=clients ++// FIX: build taken-name set once O(C), then check O(1) per profile + const BotProfile *BotProfileManager::GetRandomProfile(BotDifficultyType difficulty, BotProfileTeamType team) const + { + #ifdef RANDOM_LONG + BotProfileList::const_iterator iter; + ++ // Build set of taken names once, instead of scanning players per profile ++ std::set takenNames; ++ for (int i = 1; i <= gpGlobals->maxClients; ++i) ++ { ++ CBasePlayer *player = static_cast(UTIL_PlayerByIndex(i)); ++ if (player && player->IsBot()) ++ takenNames.insert(static_cast(player)->GetProfile()->GetName()); ++ else if (player && !FNullEnt(player->pev) && STRING(player->pev->netname)[0]) ++ takenNames.insert(STRING(player->pev->netname)); ++ } ++ + // count up valid profiles + int validCount = 0; + for (iter = m_profileList.begin(); iter != m_profileList.end(); ++iter) +@@ -604,7 +615,7 @@ + const BotProfile *profile = (*iter); + +- if (profile->IsDifficulty(difficulty) && !UTIL_IsNameTaken(profile->GetName()) && profile->IsValidForTeam(team)) ++ if (profile->IsDifficulty(difficulty) && takenNames.find(profile->GetName()) == takenNames.end() && profile->IsValidForTeam(team)) + ++validCount; + } + +@@ -616,7 +627,7 @@ + { + const BotProfile *profile = (*iter); + +- if (profile->IsDifficulty(difficulty) && !UTIL_IsNameTaken(profile->GetName()) && profile->IsValidForTeam(team)) ++ if (profile->IsDifficulty(difficulty) && takenNames.find(profile->GetName()) == takenNames.end() && profile->IsValidForTeam(team)) + { + if (which-- == 0) + return profile; diff --git a/defects/regamedll-0001/test/test_regamedll_0001.py b/defects/regamedll-0001/test/test_regamedll_0001.py new file mode 100644 index 000000000..16ecc8fcb --- /dev/null +++ b/defects/regamedll-0001/test/test_regamedll_0001.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python3 +""" +Unit test for regamedll-0001: BotProfileManager::GetRandomProfile +UTIL_IsNameTaken O(P*C) linear scan inside profile loop. + +Defect: GetRandomProfile loops over all bot profiles (P) and calls +UTIL_IsNameTaken per profile, which scans all 32 player slots (C) +doing string comparison. Total: O(P * C * 2) for the double pass. + +Fix: Build a set of taken names once O(C), then check set membership +O(1) per profile, reducing to O(P + C). + +This test simulates both the defective and patched patterns and +measures operation counts to verify the fix. +""" + +import time + + +def util_is_name_taken_linear(name, players): + """Simulates UTIL_IsNameTaken: O(C) linear scan of all player slots.""" + for player_name in players: + if player_name == name: + return True + return False + + +def get_random_profile_defective(profiles, difficulty, team, players): + """ + Defective pattern: calls UTIL_IsNameTaken per profile (O(P * C)). + Returns (selected_profile, operation_count). + """ + ops = 0 + + # First pass: count valid profiles + valid_count = 0 + for profile in profiles: + if profile["difficulty"] == difficulty and profile["team"] in (team, "any"): + # Linear scan of all players per profile + taken = False + for player_name in players: + ops += 1 + if player_name == profile["name"]: + taken = True + break + if not taken: + valid_count += 1 + + if valid_count == 0: + return None, ops + + # Second pass: select nth valid profile (simulate random selection of index 0) + which = 0 + for profile in profiles: + if profile["difficulty"] == difficulty and profile["team"] in (team, "any"): + taken = False + for player_name in players: + ops += 1 + if player_name == profile["name"]: + taken = True + break + if not taken: + if which == 0: + return profile, ops + which -= 1 + + return None, ops + + +def get_random_profile_patched(profiles, difficulty, team, players): + """ + Patched pattern: build taken-name set once O(C), then O(1) lookups. + Returns (selected_profile, operation_count). + """ + ops = 0 + + # Build set of taken names once + taken_names = set() + for player_name in players: + ops += 1 + taken_names.add(player_name) + + # First pass: count valid profiles + valid_count = 0 + for profile in profiles: + if profile["difficulty"] == difficulty and profile["team"] in (team, "any"): + ops += 1 # set lookup + if profile["name"] not in taken_names: + valid_count += 1 + + if valid_count == 0: + return None, ops + + # Second pass: select nth valid + which = 0 + for profile in profiles: + if profile["difficulty"] == difficulty and profile["team"] in (team, "any"): + ops += 1 # set lookup + if profile["name"] not in taken_names: + if which == 0: + return profile, ops + which -= 1 + + return None, ops + + +def test_correctness(): + """Verify both implementations return the same result.""" + players = ["Bot_Alpha", "Bot_Bravo", "Bot_Charlie"] + profiles = [ + {"name": "Bot_Alpha", "difficulty": "hard", "team": "any"}, + {"name": "Bot_Bravo", "difficulty": "hard", "team": "any"}, + {"name": "Bot_Delta", "difficulty": "hard", "team": "any"}, + {"name": "Bot_Echo", "difficulty": "hard", "team": "any"}, + {"name": "Bot_Foxtrot", "difficulty": "easy", "team": "any"}, + ] + + result_defective, _ = get_random_profile_defective(profiles, "hard", "any", players) + result_patched, _ = get_random_profile_patched(profiles, "hard", "any", players) + + assert result_defective is not None, "Defective should find a profile" + assert result_patched is not None, "Patched should find a profile" + assert result_defective["name"] == result_patched["name"], ( + f"Both should return same profile: {result_defective['name']} vs {result_patched['name']}" + ) + assert result_defective["name"] == "Bot_Delta", ( + f"Should select first untaken profile: got {result_defective['name']}" + ) + print("PASS: correctness") + + +def test_operation_count(): + """Verify patched version does fewer operations.""" + # Simulate 100 bot profiles and 32 player slots (max CS 1.6 server) + num_profiles = 100 + num_players = 32 + + players = [f"Bot_{i}" for i in range(num_players)] + profiles = [ + {"name": f"Bot_{i}", "difficulty": "hard", "team": "any"} + for i in range(num_profiles) + ] + + _, ops_defective = get_random_profile_defective(profiles, "hard", "any", players) + _, ops_patched = get_random_profile_patched(profiles, "hard", "any", players) + + # Defective: ~P*C*2 = 100*32*2 = 6400 operations + # Patched: C + P*2 = 32 + 200 = 232 operations + ratio = ops_defective / max(ops_patched, 1) + + print(f" Defective ops: {ops_defective}") + print(f" Patched ops: {ops_patched}") + print(f" Ratio: {ratio:.1f}x") + + assert ratio > 5.0, f"Expected significant reduction, got {ratio:.1f}x" + print("PASS: operation count") + + +def test_scaling(): + """Test that patched scales better as profiles increase.""" + num_players = 32 + players = [f"Bot_{i}" for i in range(num_players)] + + ratios = [] + for num_profiles in [50, 100, 200, 500]: + profiles = [ + {"name": f"Bot_{i}", "difficulty": "hard", "team": "any"} + for i in range(num_profiles) + ] + + _, ops_defective = get_random_profile_defective(profiles, "hard", "any", players) + _, ops_patched = get_random_profile_patched(profiles, "hard", "any", players) + ratio = ops_defective / max(ops_patched, 1) + ratios.append(ratio) + print(f" P={num_profiles:4d}: defective={ops_defective:6d} patched={ops_patched:4d} ratio={ratio:.1f}x") + + # Ratio should increase with more profiles (defective grows as P*C, patched as P+C) + assert ratios[-1] > ratios[0], "Ratio should increase with more profiles" + print("PASS: scaling") + + +def test_timing(): + """Wall-clock timing comparison.""" + num_profiles = 500 + num_players = 32 + iterations = 100 + + players = [f"Bot_{i}" for i in range(num_players)] + profiles = [ + {"name": f"Bot_{i}", "difficulty": "hard", "team": "any"} + for i in range(num_profiles) + ] + + start = time.perf_counter() + for _ in range(iterations): + get_random_profile_defective(profiles, "hard", "any", players) + time_defective = time.perf_counter() - start + + start = time.perf_counter() + for _ in range(iterations): + get_random_profile_patched(profiles, "hard", "any", players) + time_patched = time.perf_counter() - start + + speedup = time_defective / max(time_patched, 1e-9) + print(f" Defective: {time_defective*1000:.1f}ms") + print(f" Patched: {time_patched*1000:.1f}ms") + print(f" Speedup: {speedup:.1f}x") + + assert speedup > 2.0, f"Expected speedup > 2x, got {speedup:.1f}x" + print("PASS: timing") + + +if __name__ == "__main__": + test_correctness() + test_operation_count() + test_scaling() + test_timing() + print("\nAll tests PASSED") diff --git a/defects/xash3d-0001/test/test_consistency_lookup b/defects/xash3d-0001/test/test_consistency_lookup new file mode 100755 index 0000000000000000000000000000000000000000..47fb74ad81c35207de26b46e5bdc09bb1a52f3c0 GIT binary patch literal 16552 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 literal 0 HcmV?d00001 diff --git a/defects/xash3d-0002/test/test_precache_index b/defects/xash3d-0002/test/test_precache_index new file mode 100755 index 0000000000000000000000000000000000000000..cab782eeae28e62276726075e883c20273996d1e GIT binary patch literal 16768 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 SVp8FvC$|WVwI%}-%l->a*Lr^d literal 0 HcmV?d00001 diff --git a/defects/xash3d-0003/test/test_rcon_logging b/defects/xash3d-0003/test/test_rcon_logging new file mode 100755 index 0000000000000000000000000000000000000000..77a64bcd19a9af46293b852acfef7440f78a4677 GIT binary patch literal 16328 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