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.
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.