From a663290e44700ace033133f7a30545bc7ec2ff2e Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 18:46:59 -0400 Subject: [PATCH] lime3ds: 1 CWE-407 defect, MOAD 0002-0005 CLEAN --- defects/lime3ds-0001/patch/lime3ds-0001.patch | 125 ++++++++++++++++ .../lime3ds-0001/test/test_lime3ds_0001.cpp | 140 ++++++++++++++++++ 2 files changed, 265 insertions(+) create mode 100644 defects/lime3ds-0001/patch/lime3ds-0001.patch create mode 100644 defects/lime3ds-0001/test/test_lime3ds_0001.cpp diff --git a/defects/lime3ds-0001/patch/lime3ds-0001.patch b/defects/lime3ds-0001/patch/lime3ds-0001.patch new file mode 100644 index 000000000..da2119ec3 --- /dev/null +++ b/defects/lime3ds-0001/patch/lime3ds-0001.patch @@ -0,0 +1,125 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/src/network/room.cpp ++++ b/src/network/room.cpp +@@ -1,8 +1,8 @@ + // Copyright Citra Emulator Project / Azahar Emulator Project + // Licensed under GPLv2 or any later version + // Refer to the license.txt file included. + +-#include + #include + #include + #include + #include + #include + #include ++#include + #include "common/logging/log.h" + #include "enet/enet.h" + #include "network/packet.h" +@@ -48,8 +48,8 @@ class Room::RoomImpl { + public: + // ... +- UsernameBanList username_ban_list; ///< List of banned usernames +- IPBanList ip_ban_list; ///< List of banned IP addresses ++ std::unordered_set username_ban_list; ///< Set of banned usernames (O(1) lookup) ++ std::unordered_set ip_ban_list; ///< Set of banned IP addresses (O(1) lookup) + mutable std::mutex ban_list_mutex; ///< Mutex for the ban lists + +@@ -383,14 +383,10 @@ void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) { + { + std::lock_guard lock(ban_list_mutex); + + // Check username ban + if (!member.user_data.username.empty() && +- std::find(username_ban_list.begin(), username_ban_list.end(), +- member.user_data.username) != username_ban_list.end()) { ++ username_ban_list.count(member.user_data.username) != 0) { + SendUserBanned(event->peer); + return; + } + + // Check IP ban + char ip_raw[256]; + enet_address_get_host_ip(&event->peer->address, ip_raw, sizeof(ip_raw) - 1); + ip = ip_raw; + +- if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) != ip_ban_list.end()) { ++ if (ip_ban_list.count(ip) != 0) { + SendUserBanned(event->peer); + return; + } + } + +@@ -503,15 +499,10 @@ void Room::RoomImpl::HandleModBanPacket(const ENetEvent* event) { + if (!username.empty()) { + // Ban the forum username +- if (std::find(username_ban_list.begin(), username_ban_list.end(), username) == +- username_ban_list.end()) { +- username_ban_list.emplace_back(username); +- } ++ username_ban_list.insert(username); + } + + // Ban the member's IP as well +- if (std::find(ip_ban_list.begin(), ip_ban_list.end(), ip) == ip_ban_list.end()) { +- ip_ban_list.emplace_back(ip); +- } ++ ip_ban_list.insert(ip); + } + +@@ -535,14 +526,10 @@ void Room::RoomImpl::HandleModUnbanPacket(const ENetEvent* event) { + bool unbanned = false; + { + std::lock_guard lock(ban_list_mutex); + +- auto it = std::find(username_ban_list.begin(), username_ban_list.end(), address); +- if (it != username_ban_list.end()) { ++ if (username_ban_list.erase(address) > 0) { + unbanned = true; +- username_ban_list.erase(it); + } + +- it = std::find(ip_ban_list.begin(), ip_ban_list.end(), address); +- if (it != ip_ban_list.end()) { ++ if (ip_ban_list.erase(address) > 0) { + unbanned = true; +- ip_ban_list.erase(it); + } + } + +@@ -739,8 +726,10 @@ void Room::RoomImpl::SendModBanListResponse(ENetPeer* client) { + Packet packet; + packet << static_cast(IdModBanListResponse); + { + std::lock_guard lock(ban_list_mutex); +- packet << username_ban_list; +- packet << ip_ban_list; ++ std::vector unames(username_ban_list.begin(), username_ban_list.end()); ++ std::vector ips(ip_ban_list.begin(), ip_ban_list.end()); ++ packet << unames; ++ packet << ips; + } + ENetPacket* enet_packet = + enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE); + +@@ -1043,8 +1032,10 @@ bool Room::Create(const std::string& name, const std::string& description, + room_impl->password = password; + room_impl->verify_backend = std::move(verify_backend); +- room_impl->username_ban_list = ban_list.first; +- room_impl->ip_ban_list = ban_list.second; ++ room_impl->username_ban_list = ++ std::unordered_set(ban_list.first.begin(), ban_list.first.end()); ++ room_impl->ip_ban_list = ++ std::unordered_set(ban_list.second.begin(), ban_list.second.end()); + room_impl->StartLoop(); + return true; + +@@ -1065,7 +1056,9 @@ Room::BanList Room::GetBanList() const { + std::lock_guard lock(room_impl->ban_list_mutex); +- return {room_impl->username_ban_list, room_impl->ip_ban_list}; ++ return { ++ UsernameBanList(room_impl->username_ban_list.begin(), room_impl->username_ban_list.end()), ++ IPBanList(room_impl->ip_ban_list.begin(), room_impl->ip_ban_list.end()), ++ }; + } diff --git a/defects/lime3ds-0001/test/test_lime3ds_0001.cpp b/defects/lime3ds-0001/test/test_lime3ds_0001.cpp new file mode 100644 index 000000000..f967e11d2 --- /dev/null +++ b/defects/lime3ds-0001/test/test_lime3ds_0001.cpp @@ -0,0 +1,140 @@ +// UNDF: UNDF-2026-XXXXXXXXX +// Test: lime3ds-0001 — Room ban list O(N) std::find -> O(1) unordered_set +// +// Defect: HandleJoinRequest checks username and IP bans via std::find over +// std::vector. With B bans and C connection attempts the +// total work is O(C * B). On a large, heavily-moderated room (B=1000, +// C=100 simultaneous joins) this is 100,000 string comparisons instead +// of 200 (100 username + 100 IP hash lookups). +// +// Fix: change internal storage to std::unordered_set for O(1) +// average-case membership checks; convert to/from std::vector only at +// serialization boundaries (GetBanList / Create / SendModBanListResponse). +// +// Compile: g++ -std=c++17 -O2 -o test_lime3ds_0001 test_lime3ds_0001.cpp && ./test_lime3ds_0001 + +#include +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Simulate the BEFORE (defect) path: std::vector + std::find +// --------------------------------------------------------------------------- +static bool ban_check_vector(const std::vector& ban_list, + const std::string& candidate) { + return std::find(ban_list.begin(), ban_list.end(), candidate) != ban_list.end(); +} + +// --------------------------------------------------------------------------- +// Simulate the AFTER (fixed) path: std::unordered_set +// --------------------------------------------------------------------------- +static bool ban_check_set(const std::unordered_set& ban_set, + const std::string& candidate) { + return ban_set.count(candidate) != 0; +} + +// --------------------------------------------------------------------------- +// Correctness test +// --------------------------------------------------------------------------- +static void test_correctness() { + std::vector vec_bans; + std::unordered_set set_bans; + + for (int i = 0; i < 200; ++i) { + std::string entry = "banned_user_" + std::to_string(i); + vec_bans.push_back(entry); + set_bans.insert(entry); + } + + // Banned entries must be detected by both implementations + for (int i = 0; i < 200; ++i) { + std::string entry = "banned_user_" + std::to_string(i); + assert(ban_check_vector(vec_bans, entry) == true); + assert(ban_check_set(set_bans, entry) == true); + } + + // Non-banned entries must be rejected by both + for (int i = 200; i < 400; ++i) { + std::string entry = "allowed_user_" + std::to_string(i); + assert(ban_check_vector(vec_bans, entry) == false); + assert(ban_check_set(set_bans, entry) == false); + } + + // insert is idempotent in the set (no duplicate bans) + set_bans.insert("banned_user_0"); + assert(set_bans.size() == 200); + + // erase by value works on the set + assert(set_bans.erase("banned_user_0") > 0); + assert(ban_check_set(set_bans, "banned_user_0") == false); + + printf("Correctness: PASS\n"); +} + +// --------------------------------------------------------------------------- +// Benchmark: O(N) vs O(1) at B=1000 bans, 100 join checks +// --------------------------------------------------------------------------- +static void test_performance() { + const int NUM_BANS = 1000; + const int NUM_CHECKS = 100; + const int REPEAT = 200; + + std::vector vec_bans; + std::unordered_set set_bans; + + for (int i = 0; i < NUM_BANS; ++i) { + std::string entry = "banned_" + std::to_string(i); + vec_bans.push_back(entry); + set_bans.insert(entry); + } + + // Use non-banned candidates so every search goes the full distance + std::vector candidates; + for (int i = 0; i < NUM_CHECKS; ++i) { + candidates.push_back("user_" + std::to_string(i + NUM_BANS)); + } + + // --- defect path (vector + std::find) --- + auto t0 = std::chrono::high_resolution_clock::now(); + volatile bool sink = false; + for (int r = 0; r < REPEAT; ++r) { + for (const auto& c : candidates) { + sink ^= ban_check_vector(vec_bans, c); + } + } + auto t1 = std::chrono::high_resolution_clock::now(); + double ms_vector = + std::chrono::duration(t1 - t0).count(); + + // --- fixed path (unordered_set) --- + t0 = std::chrono::high_resolution_clock::now(); + for (int r = 0; r < REPEAT; ++r) { + for (const auto& c : candidates) { + sink ^= ban_check_set(set_bans, c); + } + } + t1 = std::chrono::high_resolution_clock::now(); + double ms_set = std::chrono::duration(t1 - t0).count(); + + double ratio = (ms_set > 0.0) ? (ms_vector / ms_set) : 9999.0; + + printf("Performance (B=%d, C=%d, repeat=%d):\n", NUM_BANS, NUM_CHECKS, REPEAT); + printf(" vector+find : %.3f ms\n", ms_vector); + printf(" unordered_set: %.3f ms\n", ms_set); + printf(" speedup : %.1fx\n", ratio); + + assert(ratio >= 3.0 && "Expected at least 3x speedup from unordered_set at B=1000"); + printf("Performance: PASS\n"); + (void)sink; +} + +int main() { + test_correctness(); + test_performance(); + printf("ALL TESTS PASSED\n"); + return 0; +}