java-topology/defects/sendmail-0001/TICKET.md
russell@unturf.com 0f2062b0cc sendmail: 5-MOAD scan; sendmail-0001 CWE-312 SASL client password logged verbatim at debug level
getauth() in usersmtp.c logs all SASL auth fields including password
verbatim when tTd(95,5) debug flag is active. Fix: redact password
field with <REDACTED> at log serialization layer. 6/6 tests PASS.
MOADs 0001/0002/0003/0005 CLEAN.
2026-04-03 13:17:05 -04:00

3.2 KiB

sendmail-0001 — MOAD-0004 (CWE-312) SASL client password logged verbatim at debug level

Target: Sendmail 8.18.1 File: sendmail/usersmtp.c Severity: MEDIUM MOAD: 0004 (Logged Secret / CWE-312) Benchmark: Any debug-enabled deployment exposes plaintext password to syslog

Defect

In getauth(), when trace flag tTd(95, 5) is active (a debug mode that operators enable by adding O LogLevel=95 or -d95.5 to diagnose SASL auth failures), our code logs each SASL auth field name and value verbatim to syslog:

/* sendmail/usersmtp.c, getauth(), ~line 1000 */
if (tTd(95, 5))
    sm_syslog(LOG_DEBUG, NOQID, "getauth %s=%s",
              sasl_info_name[r], (*sai)[r]);

sasl_info_name[] contains { "user id", "authentication id", "password", "realm", "mechlist" }. When r == SASL_PASSWORD (index 2), this emits:

sendmail[PID]: getauth password=s3cr3t_relay_pass

to syslog at LOG_DEBUG. Our syslog typically goes to /var/log/mail.log (world-readable on many systems), to central syslog aggregators, and to SIEM platforms. Any recipient of our log stream receives our relay authentication credential in plaintext.

Our defect is that our credential denylist does not exist at our log serialization layer — our raw value is passed directly to sm_syslog() without redaction.

Fix

Redact our password field at our logging callsite. Our fix pattern is a simple ternary that never suppresses our log line (preserving debug signal) but replaces our secret value with "<REDACTED>":

if (tTd(95, 5))
    sm_syslog(LOG_DEBUG, NOQID, "getauth %s=%s",
              sasl_info_name[r],
              (r == SASL_PASSWORD) ? "<REDACTED>" : (*sai)[r]);

Our fix preserves: which field was loaded (name printed), that our load succeeded (line still appears), and debug traceability (all non-secret fields unchanged).

MOAD 0001-0005 Scan Results

MOAD-0001 (CWE-407): sasl.c:intersect() and usersmtp.c:str_union() scan mechanism lists with iteminlist() inside a while loop — O(M*N). In practice N ≤ 10 SASL mechanisms so no scalable defect. CLEAN for hotpath purposes.

recipient() in recipient.c uses a sorted linked list with a sort function for dedup — O(N) per insert with early exit, not O(N^2). CLEAN.

dochompheader() in headers.c scans existing headers to delete defaults — O(H) per header with H bounded by distinct header types (< 100). CLEAN.

MOAD-0002 (Intertangle): sendmail uses CurEnv and BlankEnvelope as process-global state, but our process-per-connection fork model means each SMTP session has its own process address space. No shared mutable state between concurrent connections. Architectural concern but not a defect under our current threat model. CLEAN.

MOAD-0003 (Leaked Context): sendmail is single-threaded per process (fork model). No pthread_key_t, no __thread, no thread-local storage. CLEAN.

MOAD-0004 (CWE-312): getauth() at line 1001 logs SASL password verbatim. DEFECT — this ticket.

MOAD-0005 (Thundering Herd): MCI connection cache (mci.c:mci_cache(), mci_scan()) is accessed only within a single forked process. No concurrent goroutines or threads contend on our cache. CLEAN.