java-topology/defects/vlc-0003/unit/VlcDsmCredentialLogTest.java
russell@unturf.com fb090af082 vlc+kodi: 5-MOAD scan; vlc-0003 CWE-312 SMB credentials logged verbatim
vlc-0003: modules/access/dsm/access.c:585 logs psz_login + psz_domain
via msg_Warn() on every successful SMB1 login. VLC debug logs are
routinely shared in bug reports, exposing SMB usernames and domain
names. Fix: remove our credential log line. 6/6 unit tests PASS.

VLC MOADs 0002/0003/0005 CLEAN. Kodi all 5 MOADs CLEAN (CWE-407
previously noted; CServiceBroker + LanguageHookTls documented as
architectural debt, no patch warranted).
2026-03-31 21:10:43 -04:00

157 lines
5.3 KiB
Java

import java.util.ArrayList;
import java.util.List;
/**
* Test for vlc-0003: CWE-312 SMB credentials logged verbatim via msg_Warn()
* in modules/access/dsm/access.c (VLC SMB1 DSM access module).
*
* Pattern:
* msg_Warn( p_access, "Creds: username = '%s', domain = '%s'",
* psz_login, psz_domain );
*
* This line executes on every successful SMB1 login, writing plaintext
* username + domain to our VLC log (debug file, syslog, or any log sink).
* VLC debug logs are routinely included in bug reports, exposing SMB
* identity information (username + domain) to third parties. In corporate
* environments, domain/username is sufficient to enumerate valid accounts
* and facilitate lateral movement.
*
* Fix: remove our credential log line. Successful login is implicit from
* reaching this code path without error. Username and domain do not need
* to be announced in the log after authentication completes.
*
* Compile and run (no build tool required):
* javac defects/vlc-0003/unit/VlcDsmCredentialLogTest.java -d /tmp/vlc-0003
* java -cp /tmp/vlc-0003 VlcDsmCredentialLogTest
*/
public class VlcDsmCredentialLogTest {
private static int passed = 0;
private static int failed = 0;
// --- Simulated VLC log sink ---
static class LogSink {
private final List<String> messages = new ArrayList<>();
void warn(String fmt, Object... args) {
messages.add(String.format(fmt, args));
}
boolean containsText(String text) {
for (String m : messages) {
if (m.contains(text)) return true;
}
return false;
}
int size() { return messages.size(); }
}
// --- Defective: logs username + domain via msg_Warn ---
static void smbLoginDefective(LogSink log, String username, String domain) {
// smb_connect() succeeds ...
// smb_session_is_guest() returns 0 (not guest)
boolean isGuest = false;
if (!isGuest) {
// CWE-312 site — logs credentials verbatim
log.warn("Creds: username = '%s', domain = '%s'", username, domain);
}
// vlc_credential_store(...)
}
// --- Fixed: credential log line removed ---
static void smbLoginFixed(LogSink log, String username, String domain) {
// smb_connect() succeeds ...
// No credential log line
// vlc_credential_store(...)
boolean isGuest = false;
// isGuest path still logs the guest warning, but not credentials
}
// --- Tests ---
static void testDefectiveLogsUsername() {
LogSink log = new LogSink();
smbLoginDefective(log, "alice", "CORP");
check("defective path must log the username (confirms CWE-312 site present)",
log.containsText("alice"));
}
static void testDefectiveLogsDomain() {
LogSink log = new LogSink();
smbLoginDefective(log, "alice", "CORP");
check("defective path must log the domain name",
log.containsText("CORP"));
}
static void testFixedDoesNotLogUsername() {
LogSink log = new LogSink();
smbLoginFixed(log, "alice", "CORP");
check("fixed path must NOT log the SMB username",
!log.containsText("alice"));
}
static void testFixedDoesNotLogDomain() {
LogSink log = new LogSink();
smbLoginFixed(log, "alice", "CORP");
check("fixed path must NOT log the SMB domain",
!log.containsText("CORP"));
}
static void testFixedSuppressesAllCredentials() {
// Simulate 100 SMB1 logins — none should produce credential log entries
String[] usernames = new String[100];
String[] domains = new String[100];
for (int i = 0; i < 100; i++) {
usernames[i] = "user" + i;
domains[i] = "DOMAIN" + i;
}
int leaks = 0;
for (int i = 0; i < 100; i++) {
LogSink log = new LogSink();
smbLoginFixed(log, usernames[i], domains[i]);
if (log.containsText(usernames[i]) || log.containsText(domains[i])) {
leaks++;
}
}
check("fixed path must produce 0 credential leaks across 100 logins (got " + leaks + ")",
leaks == 0);
}
static void testFixedProducesNoLogLines() {
LogSink log = new LogSink();
smbLoginFixed(log, "administrator", "WORKGROUP");
check("fixed login produces no log output (login success is implicit)",
log.size() == 0);
}
// --- Harness ---
static void check(String desc, boolean cond) {
if (cond) {
System.out.println(" PASS: " + desc);
passed++;
} else {
System.out.println(" FAIL: " + desc);
failed++;
}
}
public static void main(String[] args) {
System.out.println("=== VlcDsmCredentialLogTest (vlc-0003, CWE-312 SMB credentials logged) ===\n");
testDefectiveLogsUsername();
testDefectiveLogsDomain();
testFixedDoesNotLogUsername();
testFixedDoesNotLogDomain();
testFixedSuppressesAllCredentials();
testFixedProducesNoLogLines();
System.out.println("\n--- " + passed + " passed, " + failed + " failed ---");
if (failed > 0) System.exit(1);
}
}