diff --git a/defects/fbneo-0001/patch/fbneo-0001.patch b/defects/fbneo-0001/patch/fbneo-0001.patch new file mode 100644 index 000000000..cb3a7216a --- /dev/null +++ b/defects/fbneo-0001/patch/fbneo-0001.patch @@ -0,0 +1,69 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/src/burn/burn.cpp ++++ b/src/burn/burn.cpp +@@ -1,6 +1,7 @@ + // Burn - Drivers module + + #include "version.h" ++#include + #include "burnint.h" + #include "timer.h" + //#include "burn_sound.h" // included in burnint.h +@@ -88,6 +89,9 @@ static char** pszShortName = NULL, ** pszFullNameA = NULL; + static wchar_t** pszFullNameW = NULL; + static char szBackupNameW[MAX_PATH * sizeof(wchar_t)]; + ++// Hash index from short name to driver array index for O(1) BurnDrvGetIndex. ++static std::unordered_map g_drvIndexMap; ++ + static INT32 BurnCheckMMXSupport() + { + #if defined (_X86_) && defined (_MSC_VER) +@@ -108,6 +112,8 @@ static void BurnGameListInit() + if (0 == nBurnDrvCount) return; + + pszShortName = (char** )malloc(nBurnDrvCount * sizeof(char*)); ++ g_drvIndexMap.clear(); ++ g_drvIndexMap.reserve(nBurnDrvCount); + pszFullNameA = (char** )malloc(nBurnDrvCount * sizeof(char*)); + pszFullNameW = (wchar_t**)malloc(nBurnDrvCount * sizeof(wchar_t*)); + +@@ -122,6 +128,7 @@ static void BurnGameListInit() + if (NULL != pszShortName[i]) { + strcpy(pszShortName[i], pDriver[i]->szShortName); + pDriver[i]->szShortName = pszShortName[i]; ++ g_drvIndexMap[pDriver[i]->szShortName] = (INT32)i; + } + if (NULL != pszFullNameA[i]) { + strcpy(pszFullNameA[i], pDriver[i]->szFullNameA); +@@ -149,6 +156,7 @@ static void BurnGameListExit() + if ((NULL != pszFullNameW) && (NULL != pszFullNameW[i])) free(pszFullNameW[i]); + } + ++ g_drvIndexMap.clear(); + if (NULL != pszShortName) free(pszShortName); + if (NULL != pszFullNameA) free(pszFullNameA); + if (NULL != pszFullNameW) free(pszFullNameW); +@@ -584,12 +592,13 @@ extern "C" wchar_t* BurnDrvGetFullNameW(UINT32 i) + return pDriver[i]->szFullNameW; + } + +-extern "C" INT32 BurnDrvGetIndex(char* szName) ++// BurnDrvGetIndex -- O(1) hash lookup, was O(N) linear scan over all drivers. ++extern "C" INT32 BurnDrvGetIndex(char* szName) // CWE-407 fix: unordered_map + { + if (NULL == szName) return -1; + +- for (UINT32 i = 0; i < nBurnDrvCount; i++) { +- if (0 == strcmp(szName, pDriver[i]->szShortName)) { +-// nBurnDrvActive = i; +- return i; +- } +- } +- +- return -1; ++ auto it = g_drvIndexMap.find(szName); ++ if (it != g_drvIndexMap.end()) ++ return it->second; ++ return -1; + } diff --git a/defects/fbneo-0001/test/test b/defects/fbneo-0001/test/test new file mode 100755 index 000000000..0fe3254ad Binary files /dev/null and b/defects/fbneo-0001/test/test differ diff --git a/defects/fbneo-0001/test/test_burn_drv_get_index.cpp b/defects/fbneo-0001/test/test_burn_drv_get_index.cpp new file mode 100644 index 000000000..ac31df530 --- /dev/null +++ b/defects/fbneo-0001/test/test_burn_drv_get_index.cpp @@ -0,0 +1,139 @@ +// Unit test for fbneo-0001: BurnDrvGetIndex O(N) linear scan -> O(1) hash lookup. +// Models the defect: with 5000+ game drivers, every call to BurnDrvGetIndex +// (game load, state load, Kaillera/netplay) scanned the full driver array. +// Fix: build std::unordered_map at BurnLibInit time, O(1) lookup. + +#include +#include +#include +#include +#include +#include +#include + +// Minimal driver struct matching FBNeo's BurnDriver +struct BurnDriver { + const char* szShortName; +}; + +// Original: O(N) linear scan +static int drv_get_index_original(const std::vector& pDriver, + const char* szName) +{ + if (!szName) return -1; + for (int i = 0; i < (int)pDriver.size(); i++) { + if (strcmp(szName, pDriver[i]->szShortName) == 0) + return i; + } + return -1; +} + +// Patched: O(1) hash lookup +struct PatchedIndex { + std::unordered_map map; + + void build(const std::vector& pDriver) { + map.clear(); + map.reserve(pDriver.size()); + for (int i = 0; i < (int)pDriver.size(); i++) + map[pDriver[i]->szShortName] = i; + } + + int get(const char* szName) const { + if (!szName) return -1; + auto it = map.find(szName); + return (it != map.end()) ? it->second : -1; + } +}; + +int main() +{ + // Build driver pool modeling FBNeo's ~5000 game drivers + const int N_DRIVERS = 5000; + std::vector names; + names.reserve(N_DRIVERS); + for (int i = 0; i < N_DRIVERS; i++) + names.push_back("drv_" + std::to_string(i)); + + std::vector drivers(N_DRIVERS); + std::vector pDriver(N_DRIVERS); + for (int i = 0; i < N_DRIVERS; i++) { + drivers[i].szShortName = names[i].c_str(); + pDriver[i] = &drivers[i]; + } + + PatchedIndex idx; + idx.build(pDriver); + + // Test 1: found at index 0 (best case original, same patched) + { + int orig = drv_get_index_original(pDriver, "drv_0"); + int patched = idx.get("drv_0"); + assert(orig == 0); + assert(patched == 0); + printf("PASS: lookup first driver\n"); + } + + // Test 2: found at last index (worst case original) + { + int orig = drv_get_index_original(pDriver, "drv_4999"); + int patched = idx.get("drv_4999"); + assert(orig == 4999); + assert(patched == 4999); + printf("PASS: lookup last driver\n"); + } + + // Test 3: not found returns -1 + { + int orig = drv_get_index_original(pDriver, "no_such_game"); + int patched = idx.get("no_such_game"); + assert(orig == -1); + assert(patched == -1); + printf("PASS: missing driver returns -1\n"); + } + + // Test 4: null returns -1 + { + int orig = drv_get_index_original(pDriver, nullptr); + int patched = idx.get(nullptr); + assert(orig == -1); + assert(patched == -1); + printf("PASS: null name returns -1\n"); + } + + // Test 5: correctness across all drivers + { + for (int i = 0; i < N_DRIVERS; i += 100) { + assert(drv_get_index_original(pDriver, names[i].c_str()) == i); + assert(idx.get(names[i].c_str()) == i); + } + printf("PASS: correctness across all %d drivers\n", N_DRIVERS); + } + + // Test 6: performance -- lookup all N drivers once (covers entire range, no elision) + { + volatile int sink = 0; + + auto t0 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < N_DRIVERS; i++) + sink += drv_get_index_original(pDriver, names[i].c_str()); + auto t1 = std::chrono::high_resolution_clock::now(); + for (int i = 0; i < N_DRIVERS; i++) + sink += idx.get(names[i].c_str()); + auto t2 = std::chrono::high_resolution_clock::now(); + (void)sink; + + double orig_us = std::chrono::duration_cast(t1 - t0).count(); + double patch_us = std::chrono::duration_cast(t2 - t1).count(); + double ratio = orig_us / (patch_us > 0.0 ? patch_us : 1.0); + + printf("PASS: performance N=%d all-driver scan: original=%.0fus patched=%.0fus ratio=%.1fx\n", + N_DRIVERS, orig_us, patch_us, ratio); + // Scanning all N=5000 drivers takes O(N^2/2) comparisons vs O(N) hash lookups. + // Even with vectorization the ratio must exceed 5x at N=5000. + assert(ratio > 5.0); + } + + printf("ALL TESTS PASSED\n"); + return 0; +} diff --git a/defects/fbneo-scan/MOAD-2-5-CLEAN.md b/defects/fbneo-scan/MOAD-2-5-CLEAN.md new file mode 100644 index 000000000..2282d93c7 --- /dev/null +++ b/defects/fbneo-scan/MOAD-2-5-CLEAN.md @@ -0,0 +1,27 @@ +# FinalBurn Neo — MOAD 0002-0005 Scan: CLEAN + +**Date:** 2026-03-31 +**Scanner:** agent blackops +**Scope:** src/burn/, src/burner/, src/cpu/tms34010/ + +## MOAD-0002 (Intertangle): CLEAN (by design) + +`nBurnDrvActive` is a global index used both as "active driver cursor" and as a +temporary scan index. This is a known architectural pattern in FBNeo, not a runtime +coupling defect -- all driver enumeration uses guard/restore patterns. + +## MOAD-0003 (Leaked Context): CLEAN + +No `thread_local`, `__thread`, or ThreadLocal constructs found in src/. FBNeo is +single-threaded (emulation + UI on one thread). + +## MOAD-0004 (CWE-312 Logged Secret): CLEAN + +No credential, token, session password, or API key logging found. `bprintf` calls +log emulation state. Netplay (Kaillera) session handling does not log passwords. +Spectrum game descriptions include in-game passwords as ROM metadata -- not credentials. + +## MOAD-0005 (Thundering Herd): CLEAN + +Our cx4.cpp program cache (SNES coprocessor) is a 2-slot LRU. FBNeo is +single-threaded; no concurrent cache races are possible. diff --git a/defects/mame/scan/CLEAN.md b/defects/mame/scan/CLEAN.md index 6a3861329..a287c719a 100644 --- a/defects/mame/scan/CLEAN.md +++ b/defects/mame/scan/CLEAN.md @@ -1,25 +1,42 @@ -# MAME — CWE-407 Scan Result: CLEAN +# MAME — 5-MOAD Scan Result: CLEAN -**Date:** 2026-03-30 +**Date:** 2026-03-31 **Scanner:** agent blackops -**Scope:** src/emu, src/frontend, src/lib/util (sparse checkout) +**Scope:** src/emu/, src/frontend/mame/, src/devices/ (targeted grep) -## Methodology +## MOAD-0001 (CWE-407): CLEAN Searched for `std::find(` on vector/array containers inside loops, dedup patterns with `push_back`, visited-list membership, and linear container scans in hot paths. -## Findings +All `std::find` usages fall into safe categories: -All `std::find` usages in MAME fall into safe categories: - -1. **Constant-bounded containers:** Input type arrays (ioport.cpp), page mappings - (SCREEN_PAGE_NUM=16), parent chains (1-3 deep) +1. **Constant-bounded containers:** Input type arrays (ioport.cpp), page mappings, + parent chains (1-3 deep) 2. **Cycle detection on tiny chains:** Layout group nesting (rendlay.cpp), - software parent chains (romload.cpp) — depth < 5 -3. **Cold paths only:** Validation code (validity.cpp), info XML generation - (infoxml.cpp), CLI options (submenu.cpp) + software parent chains (romload.cpp) -- depth < 5 +3. **Cold paths only:** Validation code, info XML generation, CLI options 4. **Character/string searches:** Not container membership tests +5. **Driver lookup:** Uses a sorted `s_drivers_sorted[]` with binary search in + drivenum.cpp -- O(log N), no defect -MAME uses proper data structures (maps, sets, enumerators) for hot-path lookups. -No CWE-407 defects found. +## MOAD-0002 (Intertangle): CLEAN + +MAME uses a `running_machine` object that owns all subsystems. Device state is +encapsulated per-device. No shared mutable god-object coupling unrelated subsystems. +`g_profiler` is a profiling-only singleton with no behavioral coupling. + +## MOAD-0003 (Leaked Context): CLEAN + +No `thread_local` or `__thread` declarations found in src/emu/ or src/frontend/. +MAME is architecturally single-threaded (one machine per process). + +## MOAD-0004 (CWE-312 Logged Secret): CLEAN + +No credential, token, password, or API key logging found. `osd_printf_*` calls +log game state and configuration only. + +## MOAD-0005 (Thundering Herd): CLEAN + +Our only significant cache is `driver_enumerator::m_config` (machine_config LRU). +MAME is single-threaded; no concurrent cache population races are possible.