From 20b8a57f899f935d14586a67c2b25578ff56a771 Mon Sep 17 00:00:00 2001 From: "russell@unturf.com" Date: Tue, 31 Mar 2026 19:15:33 -0400 Subject: [PATCH] fuse: all 5 MOADs CLEAN linapple: 1 CWE-312 defect (MOAD-0004), MOAD 0001/0002/0003/0005 CLEAN --- defects/fuse-0001/SUMMARY.md | 33 +++++++ defects/linapple-0001/SUMMARY.md | 44 +++++++++ .../linapple-0001/patch/linapple-0001.patch | 14 +++ .../linapple-0001/test/test_linapple_0001.py | 95 +++++++++++++++++++ 4 files changed, 186 insertions(+) create mode 100644 defects/fuse-0001/SUMMARY.md create mode 100644 defects/linapple-0001/SUMMARY.md create mode 100644 defects/linapple-0001/patch/linapple-0001.patch create mode 100644 defects/linapple-0001/test/test_linapple_0001.py diff --git a/defects/fuse-0001/SUMMARY.md b/defects/fuse-0001/SUMMARY.md new file mode 100644 index 000000000..3ceda53ca --- /dev/null +++ b/defects/fuse-0001/SUMMARY.md @@ -0,0 +1,33 @@ +# fuse: all 5 MOADs CLEAN + +ZX Spectrum emulator, C codebase. Shallow clone scanned 2026-03-31. + +## MOAD-0001 CWE-407 + +CLEAN. Hot paths use GLib GHashTable for all key lookups (O(1)). Port I/O +dispatch uses g_slist_foreach with O(P) per port read/write where P is +registered ports (bounded, typically <20). Breakpoint check iterates GSList +of breakpoints O(B) per instruction only when debugger is ACTIVE mode. +Disk sector search is O(S) bounded by sectors-per-track (9-18). +No list.contains() inside a loop on unbounded data found. + +## MOAD-0002 Intertangle + +CLEAN. Global GHashTable for peripherals and GSList for ports are expected +single-instance emulator design. No evidence of independent subsystems +inappropriately coupled through shared mutable state. + +## MOAD-0003 Leaked Context + +CLEAN. No ThreadLocal, pthread_key, or thread-scoped identity carriers found. +Fuse is single-threaded. + +## MOAD-0004 CWE-312 + +CLEAN. No credentials, API keys, tokens, or passwords found in logging paths. +Spectranet peripheral has no credential-bearing network calls in our codebase. + +## MOAD-0005 Thundering Herd + +CLEAN. No unsynchronized cache get+null+compute+put patterns. Single-threaded +architecture eliminates this class of defect. diff --git a/defects/linapple-0001/SUMMARY.md b/defects/linapple-0001/SUMMARY.md new file mode 100644 index 000000000..8ad361f35 --- /dev/null +++ b/defects/linapple-0001/SUMMARY.md @@ -0,0 +1,44 @@ +# linapple-0001: FTP user:password logged verbatim at startup (CWE-312 / MOAD-0004) + +**Severity:** HIGH +**File:** `src/Applewin.cpp` +**Function:** `LoadConfiguration()` +**Line:** 871 + +## Description + +`LoadConfiguration()` reads `g_sFTPUserPass` (format: `user:password`) from the +registry or config file, then immediately prints the entire credential string +verbatim to stdout via `printf`: + +```c +// Print some debug strings +printf("Ready login = %s\n", g_sFTPUserPass); +``` + +This debug line runs at every startup. Any user who has configured FTP credentials +for disk image access (non-anonymous logins) has their password written to +the terminal, any log file, shell history replay, or screen recording session. + +## Fix + +Extract only our username portion (up to the first `:`), mask our password with `***`: + +```c +{ + /* CWE-312: g_sFTPUserPass is "user:password" — never log verbatim. */ + const char *colon = strchr(g_sFTPUserPass, ':'); + int user_len = colon ? (int)(colon - g_sFTPUserPass) : (int)strlen(g_sFTPUserPass); + printf("Ready login = %.*s:***\n", user_len, g_sFTPUserPass); +} +``` + +## MOADs 0001-0005 + +| MOAD | Status | +|------|--------| +| 0001 CWE-407 | CLEAN — no hot-path O(N^2) linear scan found | +| 0002 Intertangle | CLEAN — global state coupling is expected single-instance emulator design | +| 0003 Leaked Context | CLEAN — no ThreadLocal/thread-scoped identity carriers | +| 0004 CWE-312 | **DEFECT** — FTP password logged verbatim at startup | +| 0005 Thundering Herd | CLEAN — no unsynchronized cache patterns found | diff --git a/defects/linapple-0001/patch/linapple-0001.patch b/defects/linapple-0001/patch/linapple-0001.patch new file mode 100644 index 000000000..3cfca8976 --- /dev/null +++ b/defects/linapple-0001/patch/linapple-0001.patch @@ -0,0 +1,14 @@ +# UNDF: UNDF-2026-XXXXXXXXX +--- a/src/Applewin.cpp ++++ b/src/Applewin.cpp +@@ -868,7 +868,12 @@ static void LoadConfiguration(bool registry) + } + // Print some debug strings +- printf("Ready login = %s\n", g_sFTPUserPass); ++ { ++ /* CWE-312: g_sFTPUserPass is "user:password" — never log verbatim. */ ++ const char *colon = strchr(g_sFTPUserPass, ':'); ++ int user_len = colon ? (int)(colon - g_sFTPUserPass) : (int)strlen(g_sFTPUserPass); ++ printf("Ready login = %.*s:***\n", user_len, g_sFTPUserPass); ++ } + } diff --git a/defects/linapple-0001/test/test_linapple_0001.py b/defects/linapple-0001/test/test_linapple_0001.py new file mode 100644 index 000000000..4f8626c8a --- /dev/null +++ b/defects/linapple-0001/test/test_linapple_0001.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +""" +Unit test for linapple-0001: FTP user:password credential logged verbatim (CWE-312 / MOAD-0004) + +LoadConfiguration() in src/Applewin.cpp reads g_sFTPUserPass (format: "user:password") +from config and immediately prints it verbatim via printf to stdout at every startup. + +Fix: extract only the username portion; mask the password with ***. +""" + +import re +import unittest + +# Original defective line +ORIGINAL_LINE = 'printf("Ready login = %s\\n", g_sFTPUserPass);' + +# Patched lines (the masked version) +PATCHED_FMT = 'printf("Ready login = %.*s:***\\n", user_len, g_sFTPUserPass);' + + +def mask_userpass(userpass: str) -> str: + """Simulate the patched masking logic in C.""" + colon = userpass.find(':') + if colon >= 0: + return userpass[:colon] + ':***' + return userpass + ':***' + + +def original_leak(userpass: str) -> str: + """Simulate the original defective print.""" + return userpass + + +class TestLinapple0001(unittest.TestCase): + """Verify that FTP password is not logged in plaintext.""" + + def test_original_exposes_full_credential(self): + """Original printf prints user:password verbatim.""" + cred = "alice:s3cr3tpass" + output = original_leak(cred) + self.assertIn("s3cr3tpass", output, + "Defect: password visible in original output") + # Confirm the source line uses %s with full g_sFTPUserPass + self.assertIn("%s", ORIGINAL_LINE) + self.assertIn("g_sFTPUserPass", ORIGINAL_LINE) + + def test_patch_masks_password(self): + """Patched version must show only username, not password.""" + cred = "alice:s3cr3tpass" + output = mask_userpass(cred) + self.assertEqual(output, "alice:***", + "Fix: output should be user:***") + self.assertNotIn("s3cr3tpass", output, + "Fix: password must not appear in output") + + def test_patch_preserves_username(self): + """Username must remain visible for diagnostics.""" + cred = "bob:hunter2" + output = mask_userpass(cred) + self.assertTrue(output.startswith("bob:"), + "Fix: username should be preserved") + + def test_patch_handles_no_colon(self): + """If no colon, treat entire string as username.""" + cred = "anonymous" + output = mask_userpass(cred) + self.assertIn("anonymous", output) + self.assertIn("***", output) + + def test_patch_handles_anonymous_default(self): + """Default anonymous:mymail@hotmail.com must have email masked.""" + cred = "anonymous:mymail@hotmail.com" + output = mask_userpass(cred) + self.assertNotIn("mymail@hotmail.com", output, + "Fix: email used as password must not appear in output") + self.assertEqual(output, "anonymous:***") + + def test_patched_fmt_string_uses_precision(self): + """Patched printf uses %%.*s to limit output to username length.""" + self.assertIn("%.*s", PATCHED_FMT, + "Patched line should use %%.*s for length-limited username") + self.assertIn(":***", PATCHED_FMT, + "Patched line should contain literal :*** mask") + + def test_original_format_string_leaks(self): + """Original format string has single %%s — prints entire user:password.""" + fmt_match = re.search(r'"Ready login = ([^"]*)"', ORIGINAL_LINE) + self.assertIsNotNone(fmt_match) + fmt_str = fmt_match.group(1) + self.assertEqual(fmt_str.count("%s"), 1, + "Original has one %%s which prints entire credential string") + + +if __name__ == "__main__": + unittest.main()