25 Years of Email Server CVEs: Why the Same Bugs Keep Coming Back
If you read CVE databases for a while, you start noticing that the bugs aren't new. They're the same bugs — different memory address, different decade, different codebase, but structurally identical to bugs that were already a known pattern when the affected code was written. Email servers are an unusually good case study for this because they've been around long enough (Sendmail since 1981, Postfix since 1998, Dovecot since 2002) for patterns to repeat several times.
This post walks through four bug classes that account for the vast majority of email-server CVEs, with one or two famous examples each. The goal isn't to be a CVE list — there are databases for that — it's to leave you with pattern recognition so the next mail-server CVE you read, you can place quickly: "ah, that's class three again."
A note: I'll cite CVE IDs where I'm confident in them. Verify any specific CVE ID before using this post as a primary source. CVE numbering occasionally gets rearranged, vendor advisories sometimes use different IDs than NVD, and the half-life of a "definitely real" CVE reference is shorter than you'd think.
Pattern 1: Parser disagreement
The pattern. Two implementations of the same protocol disagree on what some byte sequence means. An attacker straddles the boundary, sending bytes that the sending side parses one way and the receiving side parses another way. The gap is the vulnerability.
The famous example: SMTP smuggling, 2023. Disclosed by SEC Consult in December 2023, SMTP smuggling exploited disagreement between SMTP servers about what terminates the DATA section. The standard says
\r\n.\r\n\n.\nThe attack: a sender writes a single SMTP DATA payload that contains an embedded
\n.\nEnd result: an external attacker can inject mail that appears to originate from inside a victim's authenticated session. Bypasses SPF, DKIM (the signature is valid for the first message but the second message inherits the trusted disposition), DMARC alignment — the works.
CVE-2023-51764 (Postfix) is the canonical CVE for the receiving side. Sendmail, Exim, Cisco IronPort, and others got their own CVEs from the same disclosure. Postfix's mitigation is
smtpd_forbid_bare_newline = yesOther examples of parser disagreement:
- HTTP request smuggling (the same pattern, different protocol)
- Header parsing splits where one parser respects header folding and another doesn't
- Address parser disagreements: parsed as one recipient by one server and two by another
Display Name <real@example.com>, attacker@evil.com
Why it recurs. Network protocols are written in English by humans, not in formal specifications. Where the RFC is ambiguous, implementations make different choices. Where the RFC is clear, implementations sometimes deviate "for compatibility." The class of bug is structural — it can't be fixed by patching one implementation, only by aligning multiple implementations on identical strict parsing. That's slow.
Pattern 2: Pre-auth memory bugs
The pattern. A protocol parser running before authentication has a memory-safety bug — buffer overflow, integer overflow, off-by-one, double free, use-after-free. An attacker who can reach the listening port can trigger the bug without credentials. Severity is usually critical: pre-auth RCE on a network-facing service.
The famous example: Exim, "the Eximinator," CVE-2019-15846. A heap buffer overflow in Exim's TLS handshake handling allowed a remote attacker to execute code as root (Exim historically ran as root for a long time, which made the bug substantially worse). The bug was triggered during the TLS handshake — meaning before SMTP authentication, before even the SMTP protocol began. Just opening a TCP connection and starting a TLS handshake with a crafted SNI was enough.
Exim's history is unfortunately a recurring offender for this pattern. The codebase is older than Postfix, written in pre-modern C, with manual string handling pervasive. Multiple comparable CVEs have shipped over the years.
Postfix's record on this pattern is much better, in part because of Wietse Venema's discipline (he wrote a memory-safe string library for Postfix from the beginning) and in part because of the privilege separation we covered in post 2 — even if
smtpdDovecot has had pre-auth bugs. They've been less catastrophic because of Dovecot's architecture (the
dovenullimap-loginpop3-loginWhy it recurs. C and C++ remain the dominant languages for high-performance network daemons, and writing memory-safe C requires sustained discipline that doesn't survive twenty-year codebases with multiple maintainers. Modern code review and fuzzing have reduced the rate, but every parser change is a chance to ship the next variant.
Pattern 3: TLS state machine bugs
The pattern. A protocol that supports STARTTLS — upgrading an existing plaintext connection to TLS — does the upgrade incorrectly. Specifically, it fails to discard pre-handshake bytes, fails to verify identity, or processes commands across the upgrade boundary as though they came from the post-upgrade authenticated state.
The famous example: STARTTLS plaintext command injection. This is a class of bug, not one CVE. Postfix had it (CVE-2011-0411 and related). Sendmail had it. Cyrus IMAP had it. The mechanism: an attacker establishes a plaintext connection, sends
STARTTLS\r\nAUTH PLAIN <attacker-supplied-credentials>\r\nSTARTTLSAUTH PLAINThe fix is one line: when STARTTLS succeeds, empty the read buffer. Discard any bytes that arrived before the handshake completed. Multiple servers shipped without this for years.
Why it recurs. The bug is invisible in code review unless you're looking specifically for buffer-handling at protocol-state boundaries. It looks like normal "read line, dispatch line" logic. The only visible artifact is what doesn't happen: the buffer flush. Tests that exercise STARTTLS look like a happy-path connection — pre-injection requires sending malformed-by-design traffic.
This pattern also includes:
- STARTTLS stripping (covered in post 4) where an attacker removes the STARTTLS offer from the EHLO response
- STARTTLS downgrade where weak ciphers or old TLS versions are accepted, allowing a MITM to weaken the channel
- Misvalidated server certificates on the client side of SMTP (Postfix's daemon talking to peers) — historically, MTA-to-MTA TLS was loosely validated by default
smtp
Pattern 4: Misuse of restrictions ordering
The pattern. Postfix's
smtpd_*_restrictionsThe classic mistake. A config that started life as:
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_unauth_destination,
reject_rbl_client zen.spamhaus.org…is fine. Local networks and authenticated users can relay; everyone else is rejected before the RBL check (which is a feature — RBL lookups are expensive, skip them for trusted senders).
A future admin adds a rule and accidentally puts it before
reject_unauth_destinationsmtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
check_recipient_access hash:/etc/postfix/recipient_overrides,
reject_unauth_destination,
reject_rbl_client zen.spamhaus.orgIf
recipient_overridesOKOKreject_unauth_destinationThe modern Postfix mitigation. Postfix 2.10+ separated
smtpd_relay_restrictionssmtpd_recipient_restrictionssmtpd_relay_restrictionssmtpd_recipient_restrictionsWhy it recurs. Order-sensitive lists in config files are a footgun. Every popular configuration system that has them — Postfix's restriction lists, Apache's
Order Allow,DenyWhy the same bugs keep coming
Four reasons, ranked by importance.
Protocol age. SMTP, POP3, and IMAP were designed when "be liberal in what you accept" was considered virtue. That posture has aged badly — every "lenient" parser is a future smuggling bug. Modern protocol design (HTTP/2, gRPC) emphasises strict parsing, but mail protocols can't be retrofitted without losing interoperability with the long tail of old implementations.
RFC ambiguity. Where two RFCs disagree (or one RFC contradicts itself in different sections), implementations choose. Different implementations choose differently. The gap is exploitable.
C codebases. The dominant mail servers are written in C. Memory safety in C requires sustained discipline. Most of the catastrophic CVEs in the email server world are memory bugs in C parsers.
Target value. Mail servers are everywhere. Compromising one means access to communications, account-recovery flows for every other service, and a launchpad for further attacks. The economic incentive to find bugs in Postfix and Dovecot is high.
The case for memory-safe rewrites
There have been several attempts at memory-safe mail server implementations. OpenSMTPD (from the OpenBSD project) is C, but the codebase is small, careful, and has had a much better security record than Sendmail or Exim. Maddy is a pure-Go all-in-one mail server. There are at least two Rust SMTP server projects in active development as of 2026.
Why hasn't any of them displaced Postfix? A few reasons:
- Postfix is good. Wietse's discipline was not normal for the era, and Postfix's bug rate per line of C is unusually low.
- Migration costs are huge. Mail server configs are intricate; users don't migrate without strong cause.
- Network effects: every tutorial, every monitoring check, every operator's muscle memory targets Postfix and Dovecot.
The likely future is incremental — Postfix and Dovecot have both adopted modern fuzzing, sanitizer-based testing, and stricter parsing defaults. The bug rate is dropping, just slowly.
What this means for an operator
If you're running Postfix and Dovecot in 2026, the practical takeaways are:
- Patch quickly. Email server CVEs aren't theoretical. SMTP smuggling went from disclosure to active exploitation in days. Subscribe to the relevant security mailing lists.
- Set strict parser options. on Postfix. Strict TLS minimums (
smtpd_forbid_bare_newline = yes). Strict STARTTLS enforcement on submission ports.smtpd_tls_protocols = !SSLv2,!SSLv3,!TLSv1,!TLSv1.1 - Use the architecture. Postfix's chroot and unprivileged users are there for a reason — don't disable them in pursuit of "simpler" configs. Dovecot's user and login-process restrictions are likewise non-optional.
dovenull - Audit your restrictions ordering. Specifically, make sure exists and contains your relay logic (not buried in
smtpd_relay_restrictions). Post 7 covers this in detail.smtpd_recipient_restrictions - Assume the next CVE is coming. Don't run your mail server like an appliance. Patch, monitor, audit. The bugs that exist now might not be public — but they're coming.
Post 7 covers the operator-error misconfigurations that are essentially "self-inflicted CVEs" — bugs in your config rather than bugs in the code, but with the same exploitable consequences. Post 8 turns it around and walks through how to test your own server for all of the above.
What I'd Tell My Past Self
The thing the CVE history actually teaches isn't "patch quickly" (everyone knows that). It's the bugs aren't a personal failing of any one project. The same bug classes appear in Postfix, Dovecot, Exim, Sendmail, Cyrus, Microsoft Exchange, Cisco IronPort. The fault is structural — old protocols, ambiguous specs, unsafe languages, high-value targets. Operators who blame their mail server for being insecure are missing the point. Email is insecure. Postfix is one of the better-defended footholds in an inherently messy landscape. Treat your mail server with the respect that situation demands.
Discussion
0 comments
Share your thoughts
No comments yet. Be the first to share your thoughts!