import java.util.*; /** * Unit test for yabause CWE-312 defect yabause-0001. * * yabause-0001: In yabause/src/netlink.c, NetlinkWriteByte() handles the * Saturn NetLink modem dial-up login sequence. When NETLINK_DEBUG is * compiled in, two NETLINK_LOG calls write the Saturn internet credentials * verbatim into the debug log: * * Line 582: NETLINK_LOG("login response: %s", inbuffer+inbufferstart) * Line 592: NETLINK_LOG("password response: %s", inbuffer+inbufferstart) * * This test models the C logging logic and verifies the redaction fix. */ public class YabauseNetlinkCredentialLogTest { /** Connection state machine, mirroring NL_CONNECTSTATUS_* enum. */ enum ConnectStatus { IDLE, LOGIN1, LOGIN2, LOGIN3, CONNECTED } /** Simulates the NETLINK_LOG debug output sink. */ static class NetlinkLog { private final List entries = new ArrayList<>(); void log(String msg) { entries.add(msg); } boolean anyEntryContains(String substring) { for (String e : entries) { if (e.contains(substring)) return true; } return false; } List getEntries() { return Collections.unmodifiableList(entries); } } // --------------------------------------------------------------- // DEFECTIVE: logs login name and password verbatim // --------------------------------------------------------------- static ConnectStatus defectiveHandleCarriageReturn( NetlinkLog log, ConnectStatus status, String inbuffer) { if (status == ConnectStatus.LOGIN1) { // Line 582 — logs login name verbatim log.log(String.format("login response: %s", inbuffer)); return ConnectStatus.LOGIN2; } if (status == ConnectStatus.LOGIN2) { // Line 592 — logs password verbatim log.log(String.format("password response: %s", inbuffer)); return ConnectStatus.LOGIN3; } if (status == ConnectStatus.LOGIN3) { log.log(String.format("shell response: %s", inbuffer)); return ConnectStatus.CONNECTED; } return status; } // --------------------------------------------------------------- // FIXED: redacts credentials, logs byte count only // --------------------------------------------------------------- static ConnectStatus fixedHandleCarriageReturn( NetlinkLog log, ConnectStatus status, String inbuffer) { if (status == ConnectStatus.LOGIN1) { // Patched line 582: redacted log.log(String.format("login response: [REDACTED %d bytes]", inbuffer.length())); return ConnectStatus.LOGIN2; } if (status == ConnectStatus.LOGIN2) { // Patched line 592: redacted log.log(String.format("password response: [REDACTED %d bytes]", inbuffer.length())); return ConnectStatus.LOGIN3; } if (status == ConnectStatus.LOGIN3) { log.log(String.format("shell response: %s", inbuffer)); return ConnectStatus.CONNECTED; } return status; } // --------------------------------------------------------------- // Test cases // --------------------------------------------------------------- static class TestCase { final String loginName; final String password; TestCase(String loginName, String password) { this.loginName = loginName; this.password = password; } } static final TestCase[] CASES = { new TestCase("saturn_user", "hunter2"), new TestCase("netlink_player", "S3cr3tP@ss"), new TestCase("segaNet001", "CorrectHorseBattery"), }; public static void main(String[] args) { int pass = 0; int fail = 0; for (TestCase tc : CASES) { // --- DEFECTIVE path --- NetlinkLog defLog = new NetlinkLog(); ConnectStatus s = ConnectStatus.LOGIN1; s = defectiveHandleCarriageReturn(defLog, s, tc.loginName); s = defectiveHandleCarriageReturn(defLog, s, tc.password); defectiveHandleCarriageReturn(defLog, s, "$ "); boolean defExpLogin = defLog.anyEntryContains(tc.loginName); boolean defExpPass = defLog.anyEntryContains(tc.password); if (defExpLogin) { System.out.println("PASS (defect confirmed): login name '" + tc.loginName + "' appears in defective log"); pass++; } else { System.out.println("FAIL (defect NOT confirmed): login name '" + tc.loginName + "' missing from defective log"); fail++; } if (defExpPass) { System.out.println("PASS (defect confirmed): password '" + tc.password + "' appears in defective log"); pass++; } else { System.out.println("FAIL (defect NOT confirmed): password '" + tc.password + "' missing from defective log"); fail++; } // --- FIXED path --- NetlinkLog fixLog = new NetlinkLog(); s = ConnectStatus.LOGIN1; s = fixedHandleCarriageReturn(fixLog, s, tc.loginName); s = fixedHandleCarriageReturn(fixLog, s, tc.password); fixedHandleCarriageReturn(fixLog, s, "$ "); boolean fixExpLogin = fixLog.anyEntryContains(tc.loginName); boolean fixExpPass = fixLog.anyEntryContains(tc.password); if (!fixExpLogin) { System.out.println("PASS (fix confirmed): login name '" + tc.loginName + "' NOT in fixed log"); pass++; } else { System.out.println("FAIL (fix broken): login name '" + tc.loginName + "' still appears in fixed log"); fail++; } if (!fixExpPass) { System.out.println("PASS (fix confirmed): password '" + tc.password + "' NOT in fixed log"); pass++; } else { System.out.println("FAIL (fix broken): password '" + tc.password + "' still appears in fixed log"); fail++; } // Redaction markers must be present boolean hasLoginRedact = fixLog.anyEntryContains("login response: [REDACTED"); boolean hasPassRedact = fixLog.anyEntryContains("password response: [REDACTED"); if (hasLoginRedact) { System.out.println("PASS (login redaction marker present)"); pass++; } else { System.out.println("FAIL (login redaction marker missing)"); fail++; } if (hasPassRedact) { System.out.println("PASS (password redaction marker present)"); pass++; } else { System.out.println("FAIL (password redaction marker missing)"); fail++; } // Shell response (non-credential) must still log verbatim boolean shellVisible = fixLog.anyEntryContains("shell response: $ "); if (shellVisible) { System.out.println("PASS (non-credential shell response visible)"); pass++; } else { System.out.println("FAIL (non-credential shell response missing)"); fail++; } } System.out.println(); System.out.println("Results: " + pass + " passed, " + fail + " failed"); if (fail > 0) { System.exit(1); } } }