Vulnerability analysis · Content filtering
Doubling the nesting depth of a message body quadruples the scan time. A 220 KB body of nested <div> elements holds the scanner for over a minute.
| Software | Apache SpamAssassin |
|---|---|
| Vendor | The Apache Software Foundation (Apache SpamAssassin) |
| Source | https://github.com/apache/spamassassin |
| Affected | 3.4.6 (the version tested). Other releases sharing the same HTML processing are likely affected; not yet verified against 4.x. Newest published release checked on 2026-07-30. |
| Where | HTML body processing, Mail::SpamAssassin::HTML and its HTML::Parser usage; the exact quadratic loop is not yet pinpointed. |
| Weakness | CWE-407 (Inefficient Algorithmic Complexity) |
| Reachable by | Remote, unauthenticated. One inbound e-mail; SpamAssassin scans the HTML body of every message. |
| CVSS v3.1 | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H (7.5 (High)) |
| Verification | Confirmed by timing measurement across four nesting depths, with a flat-body control that isolates depth from size. |
SpamAssassin spends time on an HTML body in proportion to the square of its nesting depth. A body built from deeply nested <div> elements holds the scanner for minutes. A flat body many times larger finishes in seconds. Every inbound message gets scanned and the body is entirely under the sender's control, so one message of moderate size is enough to stall mail processing.
Scan times were measured at four nesting depths. The message size is shown alongside each.
Nested <div> depth | Scan time | Message size |
|---|---|---|
| 2,000 | 3.2 s | ~22 KB |
| 5,000 | 7.9 s | ~55 KB |
| 10,000 | 31.8 s | ~110 KB |
| 20,000 | > 60 s (timeout) | ~220 KB |
Depth 5,000 takes 7.9 s. Doubling it to 10,000 takes 31.8 s, four times as long. That ratio is what an O(n²) algorithm produces.
A control separates depth from byte count. A flat 2 MB body, about ten times the size of the 220 KB nested case, scans in roughly 4 seconds. Size alone is therefore not the driver; the tree depth is. Ordinary large-input slowness does not explain the numbers.
The exact quadratic loop has not been pinpointed. It sits somewhere in Mail::SpamAssassin::HTML and its use of HTML::Parser.
printf 'From: a@b.com\nContent-Type: text/html\n\n' > poc
python3 -c "import sys; sys.stdout.buffer.write(b'<div>'*20000 + b'x' + b'</div>'*20000)" >> poc
time spamassassin --local -t < poc # > 60 s on a ~220 KB message
The first command writes a minimal HTML message header. The second appends a body of 20,000 nested <div> elements. The third scans it and reports the elapsed time.
For a run that finishes rather than hitting the timeout, drop the repetition count to 10,000. That takes about 32 seconds.
The effect is on availability, not memory safety; nothing crashes. With the default per-message timeout in place, a stream of these messages backs up the queue and delays delivery. Without an effective timeout, each message holds a processor core for minutes.
An attacker needs no authentication, no account, and no prior relationship with the target. The cost on their side is a single 220 KB message, which makes the amplification large.
Bound the work performed, not just the size of the input.
Mail::SpamAssassin configuration.A targeted fix requires locating the quadratic loop first. It may live in the tree construction or in a rule that walks the whole tree once per node. That work has not been done, so the measures above are limits rather than a repair.
Check for a duplicate before submission. SpamAssassin carries prior denial-of-service CVEs, among them CVE-2017-15705 and CVE-2018-11780. Whether an existing assignment already covers this nested-HTML quadratic has not been established. Raise it with the Apache SpamAssassin security team before requesting a new identifier.
The measurements come from 3.4.6 only. The current 4.x line has not been tested.