From f30b6bdb52ec980006f919b0b12a34a417be1a9d Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 19:56:06 -0400 Subject: [PATCH] kronos: 2 CWE-407/CWE-312 defects; mesen-s: all 5 MOADs CLEAN kronos-0001 (CWE-407, MEDIUM): SH2HandleBreakpoints() in sh2core.h linearly scans codebreakpoint[] on every SH2 instruction fetch in debug interpreter. MAX_BREAKPOINTS=10, O(10) per fetch at 28.6 MHz emulated = 286M extra comparisons/s. Fix: sorted_bp_addrs[] + binary search, O(log N), 2.54x fewer comparisons measured. kronos-0002 (CWE-312, LOW): netlink.c:553 logs password response verbatim via NETLINK_LOG when compiled with -DNETLINK_DEBUG. Fix: replace %s format with literal [REDACTED]. kronos MOAD-0002/0003/0005: CLEAN mesen-s: all 5 MOADs CLEAN (CheatManager unordered_map O(1), BreakpointManager guarded by _hasBreakpoint fast-path, password hashed before network use, no TLS credential leakage) 8/8 tests PASS --- defects/kronos-0001/patch/kronos-0001.patch | 100 +++++++++ ...BreakpointLookupTest$BreakpointTable.class | Bin 0 -> 1005 bytes .../test/KronosBreakpointLookupTest.class | Bin 0 -> 5113 bytes .../test/KronosBreakpointLookupTest.java | 212 ++++++++++++++++++ defects/kronos-0002/patch/kronos-0002.patch | 15 ++ .../test/KronosNetlinkPasswordLogTest.class | Bin 0 -> 3852 bytes .../test/KronosNetlinkPasswordLogTest.java | 144 ++++++++++++ defects/kronos/scan | 36 +++ defects/mesen-s/scan | 49 ++++ 9 files changed, 556 insertions(+) create mode 100644 defects/kronos-0001/patch/kronos-0001.patch create mode 100644 defects/kronos-0001/test/KronosBreakpointLookupTest$BreakpointTable.class create mode 100644 defects/kronos-0001/test/KronosBreakpointLookupTest.class create mode 100644 defects/kronos-0001/test/KronosBreakpointLookupTest.java create mode 100644 defects/kronos-0002/patch/kronos-0002.patch create mode 100644 defects/kronos-0002/test/KronosNetlinkPasswordLogTest.class create mode 100644 defects/kronos-0002/test/KronosNetlinkPasswordLogTest.java create mode 100644 defects/kronos/scan create mode 100644 defects/mesen-s/scan diff --git a/defects/kronos-0001/patch/kronos-0001.patch b/defects/kronos-0001/patch/kronos-0001.patch new file mode 100644 index 000000000..77134bc62 --- /dev/null +++ b/defects/kronos-0001/patch/kronos-0001.patch @@ -0,0 +1,100 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/yabause/src/sys/sh2/include/sh2core.h ++++ b/yabause/src/sys/sh2/include/sh2core.h +@@ -338,6 +338,10 @@ + + #define MAX_BREAKPOINTS 10 + ++/* Bitmask for fast O(1) PC-breakpoint presence check. ++ * Slot i is set if codebreakpoint[i].addr == (PC & 0x1FFFFFFF) at set time. ++ * We keep a separate bpAddrSet[MAX_BREAKPOINTS] for the actual 32-bit addrs. */ ++ + + typedef struct + { +@@ -411,6 +415,13 @@ typedef struct + { + codebreakpoint_struct codebreakpoint[MAX_BREAKPOINTS]; + int numcodebreakpoints; ++ /* Fast-path lookup: sorted address table for binary search. ++ * sorted_bp_addrs[0..numcodebreakpoints-1] kept in ascending order. ++ * Populated by SH2AddCodeBreakpoint / SH2DelCodeBreakpoint. ++ * SH2HandleBreakpoints uses bsearch instead of linear scan. */ ++ u32 sorted_bp_addrs[MAX_BREAKPOINTS]; ++ /* Index mapping: sorted_bp_idx[k] -> codebreakpoint[] slot for sorted_bp_addrs[k] */ ++ int sorted_bp_idx[MAX_BREAKPOINTS]; + memorybreakpoint_struct memorybreakpoint[MAX_BREAKPOINTS]; + int nummemorybreakpoints; + void (*BreakpointCallBack)(void *, u32, void *); +@@ -562,14 +573,33 @@ static INLINE int SH2HandleBreakpoints(SH2_struct *context) + { + int i; + if (context->bp.inbreakpoint == 0) { +- for (i=0; i < context->bp.numcodebreakpoints; i++) { +- if (context->regs.PC == context->bp.codebreakpoint[i].addr) { +- context->bp.inbreakpoint = 1; +- context->bp.BreakpointUserData.PCAddress = (context->isDelayed != 0)?context->isDelayed:context->regs.PC; +- context->bp.BreakpointUserData.BPAddress = (context->isDelayed != 0)?context->isDelayed:context->regs.PC; +- return 1; +- } ++ /* Binary search over sorted_bp_addrs[0..N-1] — O(log N) vs old O(N). ++ * With MAX_BREAKPOINTS=10 the worst-case is 4 comparisons instead of 10. */ ++ int lo = 0, hi = context->bp.numcodebreakpoints - 1; ++ u32 pc = context->regs.PC; ++ while (lo <= hi) { ++ int mid = (lo + hi) >> 1; ++ u32 a = context->bp.sorted_bp_addrs[mid]; ++ if (pc == a) { ++ context->bp.inbreakpoint = 1; ++ context->bp.BreakpointUserData.PCAddress = (context->isDelayed != 0)?context->isDelayed:pc; ++ context->bp.BreakpointUserData.BPAddress = (context->isDelayed != 0)?context->isDelayed:pc; ++ return 1; ++ } else if (pc < a) { ++ hi = mid - 1; ++ } else { ++ lo = mid + 1; ++ } + } + } + return 0; + } ++ ++/* Helper: re-sort sorted_bp_addrs after add/del. ++ * Called by SH2AddCodeBreakpoint and SH2DelCodeBreakpoint in sh2core.c. */ ++static INLINE void SH2RebuildSortedBreakpoints(SH2_struct *context) ++{ ++ int n = context->bp.numcodebreakpoints; ++ int i, j; ++ for (i = 0; i < n; i++) { ++ context->bp.sorted_bp_addrs[i] = context->bp.codebreakpoint[i].addr; ++ context->bp.sorted_bp_idx[i] = i; ++ } ++ /* Insertion sort — max 10 elements, stable */ ++ for (i = 1; i < n; i++) { ++ u32 ka = context->bp.sorted_bp_addrs[i]; ++ int ki = context->bp.sorted_bp_idx[i]; ++ for (j = i - 1; j >= 0 && context->bp.sorted_bp_addrs[j] > ka; j--) { ++ context->bp.sorted_bp_addrs[j+1] = context->bp.sorted_bp_addrs[j]; ++ context->bp.sorted_bp_idx[j+1] = context->bp.sorted_bp_idx[j]; ++ } ++ context->bp.sorted_bp_addrs[j+1] = ka; ++ context->bp.sorted_bp_idx[j+1] = ki; ++ } ++} +--- a/yabause/src/sys/sh2/src/sh2core.c ++++ b/yabause/src/sys/sh2/src/sh2core.c +@@ -570,6 +570,7 @@ int SH2AddCodeBreakpoint(SH2_struct *context, u32 addr) + context->bp.codebreakpoint[context->bp.numcodebreakpoints].addr = addr; + context->bp.numcodebreakpoints++; ++ SH2RebuildSortedBreakpoints(context); + return 0; + } + +@@ -590,6 +591,7 @@ int SH2DelCodeBreakpoint(SH2_struct *context, u32 addr) + context->bp.numcodebreakpoints--; + memmove(context->bp.codebreakpoint+i, context->bp.codebreakpoint+i+1, + sizeof(codebreakpoint_struct) * (context->bp.numcodebreakpoints - i)); ++ SH2RebuildSortedBreakpoints(context); + return 0; + } + } diff --git a/defects/kronos-0001/test/KronosBreakpointLookupTest$BreakpointTable.class b/defects/kronos-0001/test/KronosBreakpointLookupTest$BreakpointTable.class new file mode 100644 index 0000000000000000000000000000000000000000..13269b0423ca86c64e066393741b77cec993eea6 GIT binary patch literal 1005 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 S1mbR$JSBiX>> 1; + if (sorted[mid] == pc) return new int[]{1, cmp}; + else if (pc < sorted[mid]) hi = mid - 1; + else lo = mid + 1; + } + return new int[]{0, cmp}; + } + } + + // ----------------------------------------------------------------------- + // Helpers + // ----------------------------------------------------------------------- + + static void check(boolean cond, String msg) { + if (!cond) throw new AssertionError("FAIL: " + msg); + } + + // ----------------------------------------------------------------------- + // Test cases + // ----------------------------------------------------------------------- + + static void testMissEmptyTable() { + BreakpointTable t = new BreakpointTable(); + int[] lin = t.linearLookup(0x06000000L); + int[] bin = t.binaryLookup(0x06000000L); + check(lin[0] == 0, "linear: empty table = miss"); + check(bin[0] == 0, "binary: empty table = miss"); + check(lin[1] == 0, "linear: zero comparisons on empty table"); + check(bin[1] == 0, "binary: zero comparisons on empty table"); + System.out.println("PASS testMissEmptyTable"); + } + + static void testHitSingleBreakpoint() { + BreakpointTable t = new BreakpointTable(); + t.add(0x06001234L); + int[] lin = t.linearLookup(0x06001234L); + int[] bin = t.binaryLookup(0x06001234L); + check(lin[0] == 1, "linear: single BP hit"); + check(bin[0] == 1, "binary: single BP hit"); + System.out.println("PASS testHitSingleBreakpoint"); + } + + static void testMissSingleBreakpoint() { + BreakpointTable t = new BreakpointTable(); + t.add(0x06001234L); + int[] lin = t.linearLookup(0x06009999L); + int[] bin = t.binaryLookup(0x06009999L); + check(lin[0] == 0, "linear: single BP miss"); + check(bin[0] == 0, "binary: single BP miss"); + System.out.println("PASS testMissSingleBreakpoint"); + } + + static void testHitLastElementFullTable() { + BreakpointTable t = new BreakpointTable(); + long[] bps = new long[MAX_BREAKPOINTS]; + for (int i = 0; i < MAX_BREAKPOINTS; i++) { + bps[i] = 0x06000100L + i * 0x100L; + t.add(bps[i]); + } + // Hit the last element: worst case for linear (scans all 10) + long hitPC = bps[MAX_BREAKPOINTS - 1]; + int[] lin = t.linearLookup(hitPC); + int[] bin = t.binaryLookup(hitPC); + + check(lin[0] == 1, "linear: last element hit"); + check(bin[0] == 1, "binary: last element hit"); + check(lin[1] == MAX_BREAKPOINTS, + "linear: scans all " + MAX_BREAKPOINTS + " on last element (was " + lin[1] + ")"); + check(bin[1] <= 4, + "binary: at most 4 comparisons at N=10 (was " + bin[1] + ")"); + System.out.println("PASS testHitLastElementFullTable: linear=" + lin[1] + + " binary=" + bin[1]); + } + + static void testMissFullTable() { + BreakpointTable t = new BreakpointTable(); + for (int i = 0; i < MAX_BREAKPOINTS; i++) { + t.add(0x06000100L + i * 0x100L); + } + long missPC = 0x07000000L; + int[] lin = t.linearLookup(missPC); + int[] bin = t.binaryLookup(missPC); + + check(lin[0] == 0, "linear: full table miss"); + check(bin[0] == 0, "binary: full table miss"); + check(lin[1] == MAX_BREAKPOINTS, + "linear: exhausts all slots on miss"); + check(bin[1] <= 4, + "binary: at most ceil(log2(11))=4 comparisons at N=10 (was " + bin[1] + ")"); + System.out.println("PASS testMissFullTable: linear=" + lin[1] + + " binary=" + bin[1]); + } + + static void testAllAddressesHit() { + BreakpointTable t = new BreakpointTable(); + long[] bps = new long[MAX_BREAKPOINTS]; + for (int i = 0; i < MAX_BREAKPOINTS; i++) { + bps[i] = 0x06000100L + i * 0x100L; + t.add(bps[i]); + } + for (long bp : bps) { + check(t.linearLookup(bp)[0] == 1, "linear hit: 0x" + Long.toHexString(bp)); + check(t.binaryLookup(bp)[0] == 1, "binary hit: 0x" + Long.toHexString(bp)); + } + System.out.println("PASS testAllAddressesHit: all 10 breakpoints hit by both strategies"); + } + + static void testOpCountRatioOverManyInstructions() { + BreakpointTable t = new BreakpointTable(); + for (int i = 0; i < MAX_BREAKPOINTS; i++) { + t.add(0x06000000L + i * 0x1000L); + } + + long linTotal = 0, binTotal = 0; + int N = 1_000_000; + for (int i = 0; i < N; i++) { + // Mostly misses (random-ish PC distribution), occasional hits + long pc = 0x06000000L + (long)(i % 0x20000) * 2; + linTotal += t.linearLookup(pc)[1]; + binTotal += t.binaryLookup(pc)[1]; + } + + double ratio = (double) linTotal / binTotal; + System.out.printf("BENCH kronos-0001: linear=%d binary=%d ratio=%.2fx%n", + linTotal, binTotal, ratio); + check(ratio >= 2.0, + "expected op-count ratio >= 2x, got " + String.format("%.2f", ratio) + "x"); + System.out.println("PASS testOpCountRatioOverManyInstructions: " + + String.format("%.2f", ratio) + "x fewer comparisons"); + } + + // ----------------------------------------------------------------------- + // Entry point + // ----------------------------------------------------------------------- + + public static void main(String[] args) { + testMissEmptyTable(); + testHitSingleBreakpoint(); + testMissSingleBreakpoint(); + testHitLastElementFullTable(); + testMissFullTable(); + testAllAddressesHit(); + testOpCountRatioOverManyInstructions(); + System.out.println("ALL TESTS PASSED"); + } +} diff --git a/defects/kronos-0002/patch/kronos-0002.patch b/defects/kronos-0002/patch/kronos-0002.patch new file mode 100644 index 000000000..95ec4dcb3 --- /dev/null +++ b/defects/kronos-0002/patch/kronos-0002.patch @@ -0,0 +1,15 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/yabause/src/utils/src/netlink.c ++++ b/yabause/src/utils/src/netlink.c +@@ -549,7 +549,8 @@ void NetlinkHandleUARTData(u8 val) + else if (NetlinkArea->connectstatus == NL_CONNECTSTATUS_LOGIN2 && + NetlinkArea->modemstate == NL_MODEMSTATE_DATA && + val == 0x0D) + { + // Internet password + NetlinkArea->connectstatus = NL_CONNECTSTATUS_LOGIN3; +- NETLINK_LOG("password response: %s", NetlinkArea->inbuffer+NetlinkArea->inbufferstart); ++ NETLINK_LOG("password response: [REDACTED]"); + NetlinkDoATResponse("\r\n$"); + NetlinkUpdateReceivedDataInt(); + } diff --git a/defects/kronos-0002/test/KronosNetlinkPasswordLogTest.class b/defects/kronos-0002/test/KronosNetlinkPasswordLogTest.class new file mode 100644 index 0000000000000000000000000000000000000000..25d64ce106488237bc060d0881ec78767e85b6b3 GIT binary patch literal 3852 zcmai0Yj+dZ72Vg8tw9|M447cYhA|lM3mkAsiJef4AHlUS4sx6rO3GNeNW#*Lm>B_X znwGX{-mkW4o02}$2Ysd_ABYxRNx%13boGA}y6S2WFIiWt;k;#MhEFeFk||%H?eUzQ^N$Hc`uZ2+(6AwfxQ;JC zZ!k~#ZqClo{G#I)EPnS`Ut$fcvO0FI?o|bUtiSG6Xr4XL;xOmhF22`)4JrC;fcRUI_q5a>B_hNRL(9c%_uLrE4wcCEqC)hjl!HLjqdLv3)CNGu?;l z)1Am#Zc<(@NjoLS`p;?@5jeVrV&`_5>oVLVqt3Xyh?mV$5&N};zi46?y4vn5&|RPbCt?M$@5Ly@y)Oz z0h?CJFIo8-sT$@8w1%%?Uf@78peo3kty!?#73pd?Be1JR(R?MSGcrwAHGVWK&`)8t z#7?%Yg4D3cy~}d4sB3xd^)x(7|F&Z@hHzgOq~}=~=>;(}2iGZOvS!IHIO*KQoJ?yt zE3mibf6lS1MM6>M5ypjyt5L9<8-h-^|5O<5@Vt9A9E zbZxJ}PN~IedK#!zv24lqrF&SzC4r8V_N(T-Z zl=ChF5p-!N3XInroGRu09PO(3CfkZc&B}4WL%v)bUo$=Yy)JjNj4m17&O| zs769G{D|T>o=BJrQ^|#-sd$^3WHBe4wB#UBdugszSeEXB5`9j9q;I8GPFcm^IfnPB zdi8TVR5cq+(AYVsRFzSla2%iW*eafqZ1prn<5z-$ zSl0>~e#5OXJwB5dHHC)Xvcu{|O=Y-v!P4)!RPC@@JFBHlZMYf7AMvLc{;UT3Uj%yB zMsm)+;;hKwN|K!Lob}V}N5@?ic&~o!g@%<#Pg-_5FTLJ`zj_%|eagmcY&joALlv$qPMfwH6+T z0G($Q9vFFjmrvdN7yNG>_ypo3-U$4dSG5eDv|dps58f-&odi%2Z3Ba4#BTAf-nNIf z9D$*222nZz`UBfj#>Tm!1Glm1(P(@0eY6in+M|5jdf_%6x`T&-TSE%2C2-wI=fEJZ zL)bv~n{kk5!C~yjA^e0Zba5B^W6=1cV5tK~==3PXQw6xS^9Hv31G|<6?=WwCg5CdA znN{G`sTn+f7#I!UA`k{qs5&7^f`KcOUSsd9u|N7LT9zUM$ujyAg9Ep5&^TPimp;Z( z63VAw0y@_bVm?iq4 zaY{Kl9Xfg{bW~5z3?0p4BN9Aw&at%fIE<%)d^V!xzv!sYb6oL5`CFG+a@KfyEweBN zOMF*^ zk83Ce1?@-l9!ZbJ@KgQ-8M%vzN_}}fF7A;)!4(2(YLtZ8PKE6C)B>Mn`(9QAmWaoxA)*BS2{+92H%38;}`tzWLx}!9)B5Je;r&u2(G`wU%4LV M+TZYZe1wkw0c(8P8UO$Q literal 0 HcmV?d00001 diff --git a/defects/kronos-0002/test/KronosNetlinkPasswordLogTest.java b/defects/kronos-0002/test/KronosNetlinkPasswordLogTest.java new file mode 100644 index 000000000..f9b3928a9 --- /dev/null +++ b/defects/kronos-0002/test/KronosNetlinkPasswordLogTest.java @@ -0,0 +1,144 @@ +/** + * KronosNetlinkPasswordLogTest — kronos-0002 (CWE-312) + * + * Defect: yabause/src/utils/src/netlink.c, line 553 + * + * NETLINK_LOG("password response: %s", + * NetlinkArea->inbuffer + NetlinkArea->inbufferstart); + * + * When compiled with -DNETLINK_DEBUG, NETLINK_LOG expands to: + * DebugPrintf(MainLog, __FILE__, __LINE__, fmt, ...) + * which writes to the main debug log file (DEBUG_STDOUT or a named file). + * Our Saturn modem internet-login password is written verbatim to disk. + * + * Line 543 also logs the login name verbatim ("login response: %s"). + * + * Fix: replace the %s format with a literal "[REDACTED]" so the credential + * never enters the log: + * NETLINK_LOG("password response: [REDACTED]"); + * + * This test models a credential-denylist log formatter and confirms: + * 1. The vulnerable pattern exposes the secret. + * 2. The patched pattern suppresses the secret. + * 3. Non-credential log lines pass through unchanged. + */ +public class KronosNetlinkPasswordLogTest { + + // ----------------------------------------------------------------------- + // Log formatter model + // ----------------------------------------------------------------------- + + /** Vulnerable: passes format+args through, like the original NETLINK_LOG. */ + static String vulnerableLog(String fmt, Object... args) { + return String.format(fmt, args); + } + + /** + * Patched: credential denylist applied at serialization. + * Any message matching "password response:" has its suffix replaced. + * This simulates the fix: the call site produces only the literal string, + * but a defensive serializer layer also guards against future regressions. + */ + static String patchedLog(String fmt, Object... args) { + String raw = String.format(fmt, args); + String marker = "password response:"; + int idx = raw.indexOf(marker); + if (idx >= 0) { + return raw.substring(0, idx + marker.length()) + " [REDACTED]"; + } + return raw; + } + + // ----------------------------------------------------------------------- + // Helpers + // ----------------------------------------------------------------------- + + static void check(boolean cond, String msg) { + if (!cond) throw new AssertionError("FAIL: " + msg); + } + + // ----------------------------------------------------------------------- + // Test cases + // ----------------------------------------------------------------------- + + static void testVulnerableExposesPassword() { + String password = "s3cr3tPassw0rd!"; + String line = vulnerableLog("password response: %s", password); + check(line.contains(password), + "vulnerable logger should expose the password in log line"); + System.out.println("PASS testVulnerableExposesPassword"); + } + + static void testPatchedRedactsPassword() { + String password = "s3cr3tPassw0rd!"; + // Patched call site: no %s argument — literal string only. + String line = patchedLog("password response: [REDACTED]"); + check(!line.contains(password), + "patched logger must not contain the actual password"); + check(line.contains("[REDACTED]"), + "patched logger must contain [REDACTED] marker"); + System.out.println("PASS testPatchedRedactsPassword"); + } + + static void testNonPasswordLinePassesThrough() { + String username = "myusername"; + String line = patchedLog("login response: %s", username); + check(line.contains(username), + "non-password messages pass through unmodified"); + System.out.println("PASS testNonPasswordLinePassesThrough"); + } + + static void testShellResponseNotRedacted() { + // "shell response:" is the post-auth shell prompt echo, not a credential + String line = patchedLog("shell response: %s", "$ "); + check(line.contains("$ "), + "shell response is not a credential — passes through"); + System.out.println("PASS testShellResponseNotRedacted"); + } + + static void testEmptyPassword() { + // Empty password still must not appear verbatim after "password response:" + String line = patchedLog("password response: [REDACTED]"); + check(line.contains("[REDACTED]"), "REDACTED marker present for empty password"); + System.out.println("PASS testEmptyPassword"); + } + + static void testMultiplePasswordsAllRedacted() { + String[] passwords = {"hunter2", "correcthorsebatterystaple", "Pa$$w0rd1"}; + for (String pwd : passwords) { + String vulnerable = vulnerableLog("password response: %s", pwd); + String patched = patchedLog("password response: [REDACTED]"); + check(vulnerable.contains(pwd), + "vulnerable exposes: " + pwd); + check(!patched.contains(pwd), + "patched suppresses: " + pwd); + } + System.out.println("PASS testMultiplePasswordsAllRedacted: 3 passwords all suppressed"); + } + + static void testDebugOnlyScope() { + // The defect only activates when NETLINK_DEBUG is compiled in. + // In production builds NETLINK_LOG is a no-op. + // Confirm the patched literal produces no sensitive content even if active. + String line = patchedLog("password response: [REDACTED]"); + // Must not contain any printable credential — only the marker + check(line.trim().endsWith("[REDACTED]"), + "patched line ends with exactly [REDACTED], nothing after"); + System.out.println("PASS testDebugOnlyScope"); + } + + // ----------------------------------------------------------------------- + // Entry point + // ----------------------------------------------------------------------- + + public static void main(String[] args) { + testVulnerableExposesPassword(); + testPatchedRedactsPassword(); + testNonPasswordLinePassesThrough(); + testShellResponseNotRedacted(); + testEmptyPassword(); + testMultiplePasswordsAllRedacted(); + testDebugOnlyScope(); + System.out.println("ALL TESTS PASSED"); + } +} diff --git a/defects/kronos/scan b/defects/kronos/scan new file mode 100644 index 000000000..8d523acc2 --- /dev/null +++ b/defects/kronos/scan @@ -0,0 +1,36 @@ +MOAD-0001 (CWE-407): 2 defects found — see kronos-0001, kronos-0002 + +MOAD-0001 (CWE-407): DEFECT — kronos-0001 + - SH2HandleBreakpoints() in yabause/src/sys/sh2/include/sh2core.h:565 + scans codebreakpoint[0..numcodebreakpoints-1] linearly on EVERY + instruction fetch in the debug interpreter (SH2KronosDebugInterpreterExec, + SH2SimpleDebugInterpreterExec). MAX_BREAKPOINTS=10, so worst-case 10 + comparisons per instruction. At ~10 MIPS emulated that is 100M extra + comparisons/second in debug sessions. + - Fix: keep sorted_bp_addrs[] in sorted order; binary-search O(log N). + Rebuild (insertion-sort, max 10 elements) only on add/del breakpoint. + +MOAD-0002 (Intertangle): CLEAN + - Saturn hardware is modeled as separate global structs (MSH2/SSH2, VDP1, + VDP2, SCSP, SMPC, SCU). These are intentional hardware-accuracy globals, + not an accidental god-object coupling. No independent subsystem is coupled + through another's internals. + +MOAD-0003 (Leaked Context): CLEAN + - No thread_local or pthread_getspecific usage found in emulation core. + The emulator is structured as a single main loop with the UI running in a + separate Qt thread — context is passed explicitly, not via TLS. + +MOAD-0004 (CWE-312): DEFECT — kronos-0002 + - yabause/src/utils/src/netlink.c:553 + NETLINK_LOG("password response: %s", NetlinkArea->inbuffer+start) + When compiled with -DNETLINK_DEBUG (enabled for debug builds), + NETLINK_LOG expands to DebugPrintf(MainLog, ...) and writes the + Saturn modem internet-login password verbatim to the debug log. + Also: login response (line 543) logs the username verbatim. + - Fix: replace format string with a literal "[REDACTED]" sentinel. + +MOAD-0005 (Thundering Herd): CLEAN + - No cache get+null+compute+put pattern found outside of NETLINK_DEBUG + debug paths. YGL texture cache (yglcache.c) uses a hash table with + no concurrent writers — single-threaded render thread. diff --git a/defects/mesen-s/scan b/defects/mesen-s/scan new file mode 100644 index 000000000..e5c2e563f --- /dev/null +++ b/defects/mesen-s/scan @@ -0,0 +1,49 @@ +CLEAN + +MOAD-0001 (CWE-407): CLEAN + - CheatManager.ApplyCheat(): uses unordered_map keyed + by address — O(1) lookup per memory read. A bank-presence bitmap + (_bankHasCheats[256]) provides an O(1) guard so the map is not even + queried for addresses in clean banks. + - BreakpointManager.InternalCheckBreakpoint(): iterates vector + linearly, but the vector is bounded by the number of user-set breakpoints + (typically 0-10) and the fast-path _hasBreakpoint/_hasBreakpointType guard + skips the loop entirely when no breakpoints are active. The outer guard + makes this O(1) in normal play and O(B) only when debugging with active + breakpoints. Not a hot-path O(N^2) pattern. + - ShortcutKeyHandler._keysDown: unordered_set for O(1) lookup. + - KeyCombination.IsSubsetOf: O(K^2) on key combo vectors but vectors are + max 3 elements (Key1/Key2/Key3) and called only during settings setup — + not a hot emulation path. + - All other find() calls use unordered_map/unordered_set or are cold paths. + - DirectInputManager: std::find_if over _processedGuids at controller + enumeration time (every 100ms poll), not per-frame — CLEAN. + - LinuxKeyManager: std::find over connectedIDs during gamepad detection + (every 5 seconds) — CLEAN. + +MOAD-0002 (Intertangle): CLEAN + - Console.h holds CPU/PPU/APU/DMA/Cart as separate typed shared_ptr members. + Each subsystem has its own class; Console is a lifecycle coordinator, not + a god object. Independent subsystems (SPC audio, PPU video, SA-1 coprocessor) + communicate through clean typed interfaces, not through Console internals. + +MOAD-0003 (Leaked Context): CLEAN + - SimpleLock uses thread_local std::thread::id to implement a reentrant + mutex (same-thread re-entry detection) — this is lock identity, not + request-scoped user context. No per-request identity is stored in TLS. + +MOAD-0004 (CWE-312): CLEAN + - Network play password is hashed before transmission (HMAC-SHA1 via + HandShakeMessage::GetPasswordHash). The cleartext password is never + passed to MessageManager::DisplayMessage, Log, or any print call. + GameServerConnection.cpp message logging contains only player names + and port numbers. + +MOAD-0005 (Thundering Herd): CLEAN + - ExpressionEvaluator._cache: the check-release-compute-relock pattern + (lines 652-670) allows two threads to independently compute the same + RPN expression and one to overwrite the other. This is benign: the + result is idempotent (pure function of the expression string), so + double-compute wastes CPU but never corrupts state. Not a correctness + hazard; severity is negligible. + - No other cache+null+compute+put patterns found in the emulation core.