lime3ds: 1 CWE-407 defect, MOAD 0002-0005 CLEAN
This commit is contained in:
parent
cb3ee91292
commit
a663290e44
2 changed files with 265 additions and 0 deletions
125
defects/lime3ds-0001/patch/lime3ds-0001.patch
Normal file
125
defects/lime3ds-0001/patch/lime3ds-0001.patch
Normal file
|
|
@ -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 <algorithm>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
#include <random>
|
||||
#include <regex>
|
||||
#include <sstream>
|
||||
#include <thread>
|
||||
+#include <unordered_set>
|
||||
#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<std::string> username_ban_list; ///< Set of banned usernames (O(1) lookup)
|
||||
+ std::unordered_set<std::string> 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<u8>(IdModBanListResponse);
|
||||
{
|
||||
std::lock_guard lock(ban_list_mutex);
|
||||
- packet << username_ban_list;
|
||||
- packet << ip_ban_list;
|
||||
+ std::vector<std::string> unames(username_ban_list.begin(), username_ban_list.end());
|
||||
+ std::vector<std::string> 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<std::string>(ban_list.first.begin(), ban_list.first.end());
|
||||
+ room_impl->ip_ban_list =
|
||||
+ std::unordered_set<std::string>(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()),
|
||||
+ };
|
||||
}
|
||||
140
defects/lime3ds-0001/test/test_lime3ds_0001.cpp
Normal file
140
defects/lime3ds-0001/test/test_lime3ds_0001.cpp
Normal file
|
|
@ -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<std::string>. 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<std::string> 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 <algorithm>
|
||||
#include <cassert>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Simulate the BEFORE (defect) path: std::vector + std::find
|
||||
// ---------------------------------------------------------------------------
|
||||
static bool ban_check_vector(const std::vector<std::string>& 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<std::string>& ban_set,
|
||||
const std::string& candidate) {
|
||||
return ban_set.count(candidate) != 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Correctness test
|
||||
// ---------------------------------------------------------------------------
|
||||
static void test_correctness() {
|
||||
std::vector<std::string> vec_bans;
|
||||
std::unordered_set<std::string> 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<std::string> vec_bans;
|
||||
std::unordered_set<std::string> 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<std::string> 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<double, std::milli>(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<double, std::milli>(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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue