java-topology/defects/bzflag-0002/test/test_ban_list_dedup.cpp
russell@unturf.com 4641c3c60f bzflag: 3 CWE-407 defects, MOAD 0002-0005 CLEAN
bzflag-0001: bz_EventHandler::HasEvent() std::find on HandledEvents vector
  called per-handler per-event-fire in callEvents hot path. O(E*H).
  Fix: std::bitset<bz_eLastEvent>. HIGH, 8.4x speedup.

bzflag-0002: AccessControlList ban/hostBan/idBan std::find on growing
  ban vector for dedup. O(B^2) during merge() of master ban list.
  Fix: parallel unordered_set index. MEDIUM, 17x speedup.

bzflag-0003: parsePermissionString customPerms std::find dedup O(W*C).
  Fix: std::set shadow for dedup. LOW-MEDIUM, 5.1x speedup.

MOAD-0004: bzfs.cxx:4732 logs auth token verbatim at debug level 1
  (logDebugMessage with player token). Noted, not patched (debug only).

MOAD-0002 (intertangle): global mutable state typical for 1993 C++ game
  server, not a clean god-object coupling defect.
MOAD-0003 (leaked context): no thread_local usage found. CLEAN.
MOAD-0005 (thundering herd): single-threaded server, no cache races. CLEAN.
2026-03-31 13:00:38 -04:00

143 lines
4 KiB
C++

// Unit test for bzflag-0002: AccessControlList ban/hostBan/idBan O(B^2) dedup
//
// Defect: ban(), hostBan(), and idBan() each use std::find() on a
// std::vector to detect duplicate bans before inserting. When called from
// merge() processing a master ban list, this makes ban list loading O(B^2).
//
// Fix: Maintain a parallel std::unordered_set index for O(1) duplicate
// detection. Only fall through to std::find for replacement when our
// index confirms a duplicate exists.
#include <vector>
#include <unordered_set>
#include <string>
#include <algorithm>
#include <cassert>
#include <chrono>
#include <cstdio>
#include <cstdint>
#include <cstring>
// Minimal reproduction of BZFlag ban structures
struct in_addr_sim {
uint32_t s_addr;
};
struct BanInfo {
in_addr_sim addr;
unsigned char cidr;
std::string bannedBy;
std::string reason;
BanInfo(in_addr_sim a, unsigned char c) : addr(a), cidr(c) {}
bool operator==(const BanInfo &rhs) const {
return addr.s_addr == rhs.addr.s_addr && cidr == rhs.cidr;
}
};
// BEFORE: original O(B^2) pattern
struct BanListBefore {
std::vector<BanInfo> banList;
void ban(in_addr_sim addr, unsigned char cidr) {
BanInfo toban(addr, cidr);
auto oldit = std::find(banList.begin(), banList.end(), toban);
if (oldit != banList.end())
*oldit = toban;
else
banList.push_back(toban);
}
};
// AFTER: O(1) dedup with unordered_set index
struct BanListAfter {
std::vector<BanInfo> banList;
std::unordered_set<uint64_t> banIndex;
static uint64_t banKey(in_addr_sim addr, unsigned char cidr) {
return ((uint64_t)addr.s_addr << 8) | cidr;
}
void ban(in_addr_sim addr, unsigned char cidr) {
BanInfo toban(addr, cidr);
uint64_t key = banKey(addr, cidr);
if (banIndex.count(key)) {
auto oldit = std::find(banList.begin(), banList.end(), toban);
if (oldit != banList.end())
*oldit = toban;
} else {
banIndex.insert(key);
banList.push_back(toban);
}
}
};
void test_correctness() {
BanListAfter acl;
// Add unique bans
for (int i = 0; i < 100; i++) {
in_addr_sim a;
a.s_addr = (uint32_t)i;
acl.ban(a, 32);
}
assert(acl.banList.size() == 100);
// Add duplicate: should replace, not add
in_addr_sim dup;
dup.s_addr = 50;
acl.ban(dup, 32);
assert(acl.banList.size() == 100);
// Different CIDR = different ban
dup.s_addr = 50;
acl.ban(dup, 24);
assert(acl.banList.size() == 101);
printf("PASS: correctness\n");
}
void test_performance() {
const int NUM_BANS = 5000;
// Benchmark BEFORE
BanListBefore before;
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < NUM_BANS; i++) {
in_addr_sim a;
a.s_addr = (uint32_t)i;
before.ban(a, 32);
}
auto t1 = std::chrono::high_resolution_clock::now();
double before_ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
// Benchmark AFTER
BanListAfter after;
auto t2 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < NUM_BANS; i++) {
in_addr_sim a;
a.s_addr = (uint32_t)i;
after.ban(a, 32);
}
auto t3 = std::chrono::high_resolution_clock::now();
double after_ms = std::chrono::duration<double, std::milli>(t3 - t2).count();
assert(before.banList.size() == (size_t)NUM_BANS);
assert(after.banList.size() == (size_t)NUM_BANS);
double ratio = before_ms / after_ms;
printf("BEFORE: %.1f ms (%d bans)\n", before_ms, NUM_BANS);
printf("AFTER: %.1f ms (%d bans)\n", after_ms, NUM_BANS);
printf("Ratio: %.1fx speedup\n", ratio);
assert(ratio > 5.0 && "Expected at least 5x speedup from hash index");
printf("PASS: performance (%.1fx)\n", ratio);
}
int main() {
test_correctness();
test_performance();
printf("ALL TESTS PASSED\n");
return 0;
}