/** * 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"); } }