diff --git a/defects/xenia-0001/patch/xenia-0001.patch b/defects/xenia-0001/patch/xenia-0001.patch new file mode 100644 index 000000000..85fd5bdd0 --- /dev/null +++ b/defects/xenia-0001/patch/xenia-0001.patch @@ -0,0 +1,29 @@ +--- a/src/xenia/kernel/util/object_table.cc ++++ b/src/xenia/kernel/util/object_table.cc +@@ -9,6 +9,7 @@ + + #include "xenia/kernel/util/object_table.h" + ++#include + #include + #include + +@@ -219,13 +220,15 @@ std::vector> ObjectTable::GetAllObjects() { + std::vector> ObjectTable::GetAllObjects() { + auto lock = global_critical_region_.Acquire(); + std::vector> results; ++ std::unordered_set seen; + + for (uint32_t slot = 0; slot < table_capacity_; slot++) { + auto& entry = table_[slot]; +- if (entry.object && std::find(results.begin(), results.end(), +- entry.object) == results.end()) { ++ if (entry.object && seen.find(entry.object) == seen.end()) { ++ seen.insert(entry.object); + entry.object->Retain(); + results.push_back(object_ref(entry.object)); + } + } + + return results; + } diff --git a/defects/xenia-0001/test/XeniaTest$XObject.class b/defects/xenia-0001/test/XeniaTest$XObject.class new file mode 100644 index 000000000..0219636e0 Binary files /dev/null and b/defects/xenia-0001/test/XeniaTest$XObject.class differ diff --git a/defects/xenia-0001/test/XeniaTest.class b/defects/xenia-0001/test/XeniaTest.class new file mode 100644 index 000000000..180d1fe95 Binary files /dev/null and b/defects/xenia-0001/test/XeniaTest.class differ diff --git a/defects/xenia-0001/test/XeniaTest.java b/defects/xenia-0001/test/XeniaTest.java new file mode 100644 index 000000000..94d4c8814 --- /dev/null +++ b/defects/xenia-0001/test/XeniaTest.java @@ -0,0 +1,144 @@ +import java.util.*; + +/** + * XeniaTest: unit test for xenia-0001 + * + * Defect: ObjectTable::GetAllObjects() in + * src/xenia/kernel/util/object_table.cc uses std::find on a growing + * std::vector> to deduplicate objects while iterating + * over all table slots. Each slot check is O(results.size()), making the + * full scan O(S * R) where S = table slot count (starting at 16,384) and R + * = unique object count (up to S/2 in the worst case). At S=16384 with a + * full table of unique objects this yields ~134 million pointer comparisons. + * + * Fix: replace the results vector linear scan with an unordered_set + * seen-pointer set, reducing each membership test from O(R) to O(1) and the + * full scan from O(S*R) to O(S). + * + * This test simulates both implementations and confirms: + * 1. Functional equivalence: same objects returned in same order. + * 2. Performance ratio: patched version is faster by the expected factor. + */ +public class XeniaTest { + + // Simulates an XObject pointer (just an Integer ID here). + static class XObject { + final int id; + XObject(int id) { this.id = id; } + } + + // --- DEFECTIVE IMPLEMENTATION --- + // Mirrors ObjectTable::GetAllObjects() before the patch. + // table[slot] may hold duplicate pointers (multiple handles to same object). + // Dedup is done via List.contains() -- O(results.size()) per slot. + static List getAllObjectsDefective(XObject[] table) { + List results = new ArrayList<>(); + for (XObject entry : table) { + if (entry != null && !results.contains(entry)) { // O(R) scan + results.add(entry); + } + } + return results; + } + + // --- PATCHED IMPLEMENTATION --- + // Mirrors ObjectTable::GetAllObjects() after the patch. + // Dedup via HashSet seen -- O(1) per slot. + static List getAllObjectsPatched(XObject[] table) { + List results = new ArrayList<>(); + Set seen = new HashSet<>(); + for (XObject entry : table) { + if (entry != null && seen.add(entry)) { // O(1) hash test+insert + results.add(entry); + } + } + return results; + } + + // Build a table where each object is referenced by DUPLICATE_FACTOR slots. + // This matches xenia's handle table: one object can have multiple handles. + static XObject[] buildTable(int uniqueObjects, int duplicateFactor) { + XObject[] objs = new XObject[uniqueObjects]; + for (int i = 0; i < uniqueObjects; i++) { + objs[i] = new XObject(i); + } + XObject[] table = new XObject[uniqueObjects * duplicateFactor]; + for (int slot = 0; slot < table.length; slot++) { + table[slot] = objs[slot % uniqueObjects]; + } + return table; + } + + public static void main(String[] args) { + // --- Correctness test --- + // Small table: 8 unique objects, each referenced 3 times -> 24 slots + XObject[] smallTable = buildTable(8, 3); + List defResult = getAllObjectsDefective(smallTable); + List patResult = getAllObjectsPatched(smallTable); + + assert defResult.size() == 8 : + "defective: expected 8 unique objects, got " + defResult.size(); + assert patResult.size() == 8 : + "patched: expected 8 unique objects, got " + patResult.size(); + for (int i = 0; i < defResult.size(); i++) { + assert defResult.get(i) == patResult.get(i) : + "result mismatch at index " + i; + } + System.out.println("PASS correctness: 8 unique objects, 24 slots, results match"); + + // --- Empty table --- + XObject[] emptyTable = new XObject[0]; + assert getAllObjectsDefective(emptyTable).isEmpty() : "defective: empty table non-empty"; + assert getAllObjectsPatched(emptyTable).isEmpty() : "patched: empty table non-empty"; + System.out.println("PASS correctness: empty table"); + + // --- All-null table --- + XObject[] nullTable = new XObject[100]; + assert getAllObjectsDefective(nullTable).isEmpty() : "defective: all-null table non-empty"; + assert getAllObjectsPatched(nullTable).isEmpty() : "patched: all-null table non-empty"; + System.out.println("PASS correctness: all-null table"); + + // --- Performance benchmark --- + // xenia initial table_capacity = 16 * 1024 = 16384 slots. + // Simulate a loaded game: 1000 unique objects, each with 4 handle slots -> 4000 slots. + // Then scale to 4000 unique objects in 16384 slots for O(N^2) stress. + int UNIQUE = 4000; + int SLOTS = 16384; + XObject[] bigTable = new XObject[SLOTS]; + XObject[] objs = new XObject[UNIQUE]; + for (int i = 0; i < UNIQUE; i++) objs[i] = new XObject(i); + for (int s = 0; s < SLOTS; s++) bigTable[s] = objs[s % UNIQUE]; + + int REPS = 20; + + long tDef = 0; + for (int r = 0; r < REPS; r++) { + long t0 = System.nanoTime(); + getAllObjectsDefective(bigTable); + tDef += System.nanoTime() - t0; + } + + long tPat = 0; + for (int r = 0; r < REPS; r++) { + long t0 = System.nanoTime(); + getAllObjectsPatched(bigTable); + tPat += System.nanoTime() - t0; + } + + double defMs = tDef / 1e6 / REPS; + double patMs = tPat / 1e6 / REPS; + double ratio = defMs / patMs; + + System.out.printf( + "BENCH defective=%.3f ms patched=%.3f ms ratio=%.1fx%n", + defMs, patMs, ratio); + + assert ratio >= 2.0 : + "expected speedup >= 2x, got " + String.format("%.2f", ratio) + "x"; + + System.out.println("PASS performance: patched is " + + String.format("%.1f", ratio) + "x faster"); + + System.out.println("ALL TESTS PASSED"); + } +} diff --git a/defects/xenia/scan/MOAD-0002.txt b/defects/xenia/scan/MOAD-0002.txt new file mode 100644 index 000000000..9c41bfe1e --- /dev/null +++ b/defects/xenia/scan/MOAD-0002.txt @@ -0,0 +1,7 @@ +MOAD-0002 (Intertangle): CLEAN + +KernelState is a large central object but there is no evidence of independent +subsystems coupled through shared mutable global state in a way that causes +race conditions or incorrect phase mixing. Subsystems (GPU, CPU, kernel, APU) +communicate through clean interfaces and the global_critical_region_ pattern +provides explicit synchronization. No god-object coupling defect found. diff --git a/defects/xenia/scan/MOAD-0003.txt b/defects/xenia/scan/MOAD-0003.txt new file mode 100644 index 000000000..c3f4419d8 --- /dev/null +++ b/defects/xenia/scan/MOAD-0003.txt @@ -0,0 +1,11 @@ +MOAD-0003 (Leaked Context): CLEAN + +TLS variables in xenia: + - current_xthread_tls_ (xthread.cc): set at thread start, cleared at exit. 1:1 with thread lifetime. + - thread_state_ (thread_state.cc): set via ThreadState::Bind(), cleared in destructor. 1:1 with thread. + - current_thread_ (thread.cc/h): same pattern as above. + - thread_log_buffer_ (logging.cc): logging scratch buffer, no identity leakage. + - string_buffer_ (shim_utils.cc): scratch buffer for HLE shims, no identity. + +All TLS vars are thread-scoped, not request-scoped. No guest-task identity +leaks across OS-thread reuse found. diff --git a/defects/xenia/scan/MOAD-0004.txt b/defects/xenia/scan/MOAD-0004.txt new file mode 100644 index 000000000..904d83840 --- /dev/null +++ b/defects/xenia/scan/MOAD-0004.txt @@ -0,0 +1,7 @@ +MOAD-0004 (CWE-312 Logged Secret): CLEAN + +XEX decryption key constants (xe_xex2_retail_key, xe_xex2_devkit_key) are +never logged. Encryption errors log only status codes, not key bytes. +Session keys (session_key_) are used in AES decrypt calls but never passed +to any XELOG macro. No credentials, tokens, or secret key material logged +verbatim. diff --git a/defects/xenia/scan/MOAD-0005.txt b/defects/xenia/scan/MOAD-0005.txt new file mode 100644 index 000000000..166c7f2f6 --- /dev/null +++ b/defects/xenia/scan/MOAD-0005.txt @@ -0,0 +1,10 @@ +MOAD-0005 (Thundering Herd): CLEAN + +All caches with multi-threaded access use proper synchronization: +- Module symbol cache (module.cc): global_critical_region_ Acquire() wraps + all map_.find() + map_[] insert operations. +- GPU pipeline/shader cache (pipeline_cache.cc): accessed from single GPU + command thread; no concurrent writers. +- Object table (object_table.cc): global_critical_region_ wraps all slot + accesses. +No unsynchronized check-then-act cache patterns found.